Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 1.4.5

- Upgrade Java source/target compatibility from 1.8 to 17 [#334](https://github.com/endigo/flutter_pdfview/issues/334)
- Add iOS Privacy Manifest (PrivacyInfo.xcprivacy) [#271](https://github.com/endigo/flutter_pdfview/issues/271)
- Fix iOS `onError` callback not firing for invalid documents [#211](https://github.com/endigo/flutter_pdfview/issues/211)
- Fix iOS `onPageChanged` not triggered on first load [#66](https://github.com/endigo/flutter_pdfview/issues/66)
- Fix iOS landscape PDF wrong initial zoom by preserving user-configured max scale factor [#247](https://github.com/endigo/flutter_pdfview/issues/247)
- Add configurable `maxZoom` and `minZoom` parameters [#296](https://github.com/endigo/flutter_pdfview/issues/296)
- Update iOS deployment target to 13.0 in podspec and Package.swift (aligns with Flutter's own iOS 13.0+ requirement)
- Fix example app build error with undefined `nightModeBackgroundColor` parameter

## 1.4.4

- Fixes an Android rendering [#330](https://github.com/endigo/flutter_pdfview/pull/330) @TimelessLin
Expand Down
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ android {
compileSdk = 35

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

defaultConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import com.github.barteksc.pdfviewer.link.LinkHandler;

public class FlutterPDFView implements PlatformView, MethodCallHandler {
private static final float DEFAULT_MAX_ZOOM = 4.0f;
private static final float DEFAULT_MIN_ZOOM = 1.0f;

private final PDFView pdfView;
private final MethodChannel methodChannel;
private final LinkHandler linkHandler;
Expand Down Expand Up @@ -113,6 +116,16 @@ public void onInitiallyRendered(int pages) {
}
})
.load();

Float maxZoom = getFloat(params, "maxZoom");
Float minZoom = getFloat(params, "minZoom");
float effectiveMax = maxZoom != null ? maxZoom : DEFAULT_MAX_ZOOM;
float effectiveMin = minZoom != null ? minZoom : DEFAULT_MIN_ZOOM;
if (effectiveMin > effectiveMax) {
effectiveMin = effectiveMax;
}
pdfView.setMaxZoom(effectiveMax);
pdfView.setMinZoom(effectiveMin);
Comment on lines +120 to +128
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Zoom updates will fail after the first rebuild on Android.

This only applies maxZoom and minZoom during creation. PDFViewController._updateSettings() now forwards those keys on rebuild, but applySettings() still has no maxZoom or minZoom branches, so the first runtime change falls into the default case and throws IllegalArgumentException("Unknown PDFView setting").

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/src/main/java/io/endigo/plugins/pdfviewflutter/FlutterPDFView.java`
around lines 120 - 128, The rebuild path is missing handling for maxZoom/minZoom
so applySettings() throws on runtime updates; update the applySettings(String
key, Object value) logic in FlutterPDFView to add branches for "maxZoom" and
"minZoom" (same parsing as used at creation via getFloat(params, "...")) and
call pdfView.setMaxZoom(effectiveMax) and pdfView.setMinZoom(effectiveMin)
accordingly, ensuring you coerce nulls to DEFAULT_MAX_ZOOM/DEFAULT_MIN_ZOOM and
enforce effectiveMin <= effectiveMax before calling setMinZoom/setMaxZoom; this
aligns applySettings() with PDFViewController._updateSettings() and prevents the
IllegalArgumentException for unknown settings.

}
}

Expand Down
1 change: 0 additions & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ class _PDFScreenState extends State<PDFScreen> with WidgetsBindingObserver {
false, // if set to true the link is handled in flutter
backgroundColor: Color(0xFFFEF7FF),
nightMode: true,
nightModeBackgroundColor: Colors.amber,
onRender: (_pages) {
setState(() {
pages = _pages;
Expand Down
3 changes: 2 additions & 1 deletion ios/flutter_pdfview.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ Pod::Spec.new do |s|
s.public_header_files = 'flutter_pdfview/Sources/flutter_pdfview/include/**/*.h'
s.dependency 'Flutter'

s.ios.deployment_target = '11.0'
s.ios.deployment_target = '13.0'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'armv7 arm64 x86_64' }
s.resource_bundles = { 'flutter_pdfview_privacy' => ['flutter_pdfview/Sources/flutter_pdfview/PrivacyInfo.xcprivacy'] }
end

6 changes: 4 additions & 2 deletions ios/flutter_pdfview/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "flutter_pdfview",
platforms: [
.iOS("12.0"),
.iOS("13.0"),
],
products: [
.library(name: "flutter-pdfview", targets: ["flutter_pdfview"])
Expand All @@ -16,7 +16,9 @@ let package = Package(
.target(
name: "flutter_pdfview",
dependencies: [],
resources: [],
resources: [
.process("PrivacyInfo.xcprivacy"),
],
cSettings: [
.headerSearchPath("include/flutter_pdfview")
]
Expand Down
35 changes: 30 additions & 5 deletions ios/flutter_pdfview/Sources/flutter_pdfview/FlutterPDFView.m
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ @implementation FLTPDFView {
BOOL _defaultPageSet;
BOOL _isIPad;
BOOL _isScrolling;
CGFloat _maxScaleFactor;
CGFloat _minScaleFactor;
BOOL _hasSentInitialPage;
}

- (instancetype)initWithFrame:(CGRect)frame
Expand Down Expand Up @@ -140,7 +143,10 @@ - (instancetype)initWithFrame:(CGRect)frame


if (document == nil) {
[_controller invokeChannelMethod:@"onError" arguments:@{@"error" : @"cannot create document: File not in PDF format or corrupted."}];
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf->_controller invokeChannelMethod:@"onError" arguments:@{@"error" : @"cannot create document: File not in PDF format or corrupted."}];
});
} else {
_pdfView.autoresizesSubviews = YES;
_pdfView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
Expand All @@ -165,7 +171,13 @@ - (instancetype)initWithFrame:(CGRect)frame
}
_pdfView.document = document;

_pdfView.maxScaleFactor = [args[@"maxZoom"] doubleValue];
_maxScaleFactor = [args[@"maxZoom"] doubleValue];
if (_maxScaleFactor <= 0) {
_maxScaleFactor = 4.0;
}
_minScaleFactor = [args[@"minZoom"] doubleValue];

_pdfView.maxScaleFactor = _maxScaleFactor;
Comment on lines +174 to +180
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Zoom bounds are still init-only on iOS.

lib/flutter_pdfview.dart now forwards maxZoom and minZoom through updateSettings(), but this code only reads them during creation. Because onUpdateSettings: still does nothing, rebuilding PDFView with new zoom limits leaves the old bounds active until the platform view is recreated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/flutter_pdfview/Sources/flutter_pdfview/FlutterPDFView.m` around lines
174 - 180, The zoom min/max are only applied at init (_maxScaleFactor,
_minScaleFactor and _pdfView.maxScaleFactor) so updates via updateSettings are
ignored; modify the view's update handler (onUpdateSettings: or whichever method
processes updated args) to read args[@"maxZoom"] and args[@"minZoom"], validate
them (fall back to defaults if <=0), assign to _maxScaleFactor and
_minScaleFactor, and set the platform view's pdfView.maxScaleFactor and
pdfView.minScaleFactor accordingly (ensure UI updates happen on the main
thread).

_pdfView.minScaleFactor = _pdfView.scaleFactorForSizeToFit;

NSString* password = args[@"password"];
Expand Down Expand Up @@ -253,16 +265,29 @@ - (void)layoutSubviews {
// Wrap layout updates in try-catch for safety
@try {
_pdfView.frame = self.frame;
_pdfView.minScaleFactor = _pdfView.scaleFactorForSizeToFit;
_pdfView.maxScaleFactor = 4.0;
CGFloat fitScale = _pdfView.scaleFactorForSizeToFit;
CGFloat minScale = (_minScaleFactor > 0) ? _minScaleFactor : fitScale;
_pdfView.minScaleFactor = minScale;
_pdfView.maxScaleFactor = fmax(_maxScaleFactor, minScale);
if (_autoSpacing) {
_pdfView.scaleFactor = _pdfView.scaleFactorForSizeToFit;
CGFloat clampedScale = fmin(fmax(fitScale, _pdfView.minScaleFactor), _pdfView.maxScaleFactor);
_pdfView.scaleFactor = clampedScale;
}

if (!_defaultPageSet && _defaultPage != nil) {
[_pdfView goToPage: _defaultPage];
_defaultPageSet = true;
}

if (!_hasSentInitialPage && _defaultPageSet && _pdfView.document != nil && _pdfView.currentPage != nil) {
_hasSentInitialPage = YES;
NSUInteger currentPageIndex = [_pdfView.document indexForPage:_pdfView.currentPage];
NSUInteger pageCount = [_pdfView.document pageCount];
[_controller invokeChannelMethod:@"onPageChanged" arguments:@{
@"page" : [NSNumber numberWithUnsignedLong:currentPageIndex],
@"total" : [NSNumber numberWithUnsignedLong:pageCount]
}];
}
} @catch (NSException *exception) {
NSLog(@"Warning: Layout update failed: %@", exception.reason);
}
Expand Down
14 changes: 14 additions & 0 deletions ios/flutter_pdfview/Sources/flutter_pdfview/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
</dict>
</plist>
44 changes: 35 additions & 9 deletions lib/flutter_pdfview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ class PDFView extends StatefulWidget {
this.fitPolicy = FitPolicy.WIDTH,
this.preventLinkNavigation = false,
this.backgroundColor,
this.maxZoom = 4.0,
this.minZoom = 1.0,
}) : assert(filePath != null || pdfData != null),
assert(maxZoom > 0, 'maxZoom must be greater than 0'),
assert(minZoom > 0, 'minZoom must be greater than 0'),
assert(maxZoom >= minZoom, 'maxZoom must be >= minZoom'),
Comment on lines +46 to +51
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Apple's PDFKit, is setting PDFView.minScaleFactor = 1.0equivalent to fit-to-page, or is fit-to-page determined byPDFView.scaleFactorForSizeToFit?

💡 Result:

No.

  • minScaleFactor is just a lower bound for zoom (scaleFactor). Setting pdfView.minScaleFactor = 1.0 means “don’t allow zooming out below 100%,” not “fit the page.” Setting minScaleFactor/maxScaleFactor also implicitly turns off autoScales. [1]
  • The “fit” zoom is computed as scaleFactorForSizeToFit—i.e., the scale factor that would fit the current page(s) in the view / what autoScales would use as its “size to fit” scale. [2][3]

So “fit-to-page” (or whatever fit behavior applies for the current display mode) corresponds to pdfView.scaleFactor = pdfView.scaleFactorForSizeToFit (or autoScales = true), not minScaleFactor = 1.0. [2][3]

Sources
[1] Stack Overflow: Swift PDF viewer zoom? (includes the PDFView doc comments about minScaleFactor/maxScaleFactor and autoScales) (stackoverflow.com)
[2] Microsoft Learn (Xamarin.iOS): PdfView.ScaleFactorForSizeToFit summary (“fit … in the view”) (learn.microsoft.com)
[3] Stack Overflow quoting Apple docs for scaleFactorForSizeToFit (“size to fit” scale factor that autoScales would use) (techqa.club)

Citations:


🏁 Script executed:

# First, check the exact code at lines 46-51 in lib/flutter_pdfview.dart
cat -n lib/flutter_pdfview.dart | head -60 | tail -20

Repository: endigo/flutter_pdfview

Length of output: 892


🏁 Script executed:

# Search for minZoom/maxZoom initialization and defaults
rg "minZoom|maxZoom" lib/flutter_pdfview.dart -A 2 -B 2

Repository: endigo/flutter_pdfview

Length of output: 1478


🏁 Script executed:

# Check iOS implementation that maps minZoom to PDFView.minScaleFactor
rg "minZoom|minScaleFactor" ios/ -A 3 -B 1

Repository: endigo/flutter_pdfview

Length of output: 2279


🏁 Script executed:

# Verify the _PDFViewSettings.toMap() behavior mentioned in the review
ast-grep --pattern 'toMap() { $$$  minZoom $$$  }'

Repository: endigo/flutter_pdfview

Length of output: 48


🏁 Script executed:

# Check git history to see if minZoom was previously optional/nullable
git log --oneline -p lib/flutter_pdfview.dart 2>/dev/null | head -200 | grep -A5 -B5 "minZoom" | head -50

Repository: endigo/flutter_pdfview

Length of output: 893


🏁 Script executed:

# Check if _PDFViewSettings defines minZoom as nullable in its class
rg "class _PDFViewSettings" lib/flutter_pdfview.dart -A 20 | grep -E "(minZoom|maxZoom|double\?)"

Repository: endigo/flutter_pdfview

Length of output: 101


Make minZoom nullable to preserve fit-to-page behavior.

The minZoom: 1.0 default forces a literal lower scale bound of 1.0×, but in Apple's PDFKit, minScaleFactor is only a lower bound—fit-to-page is determined by scaleFactorForSizeToFit. This breaks PDFs where the fit scale is below 1.0 (they open zoomed in instead of fit-to-page).

The iOS code already supports this: it uses (_minScaleFactor > 0) ? _minScaleFactor : fitScale, so passing null or a non-positive value as minZoom would preserve fit-to-page. Change minZoom to nullable with default null, update the assertion to allow null, and correct the doc comment (it is not "fit to page"—it's a zoom lower bound).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/flutter_pdfview.dart` around lines 46 - 51, Make minZoom nullable and
default to null so iOS can preserve fit-to-page: change the constructor
parameter type to allow null (minZoom = null), update the assert that currently
enforces minZoom > 0 to only assert when minZoom is non-null (e.g.,
assert(minZoom == null || minZoom > 0, ...)), and update the assert comparing
maxZoom >= minZoom to only run when minZoom is non-null (e.g., assert(minZoom ==
null || maxZoom >= minZoom, ...)). Also update the parameter/doc comment to
describe minZoom as an optional lower zoom bound (not "fit to page"). Ensure
references to _minScaleFactor/_minZoom in platform code remain compatible with a
null/non-positive value logic.

super(key: key);

@override
Expand Down Expand Up @@ -135,21 +140,27 @@ class PDFView extends StatefulWidget {

/// Use to change the background color. ex : "#FF0000" => red
final Color? backgroundColor;

/// Maximum zoom level. Defaults to 4.0.
final double maxZoom;

/// Minimum zoom level. Defaults to 1.0 (fit to page).
final double minZoom;
}

class _PDFViewState extends State<PDFView> {
final Completer<PDFViewController> _controller =
Completer<PDFViewController>();
Completer<PDFViewController>();

@override
Widget build(BuildContext context) {
if (defaultTargetPlatform == TargetPlatform.android) {
return PlatformViewLink(
viewType: 'plugins.endigo.io/pdfview',
surfaceFactory: (
BuildContext context,
PlatformViewController controller,
) {
BuildContext context,
PlatformViewController controller,
) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: widget.gestureRecognizers ??
Expand Down Expand Up @@ -197,7 +208,7 @@ class _PDFViewState extends State<PDFView> {
void didUpdateWidget(PDFView oldWidget) {
super.didUpdateWidget(oldWidget);
_controller.future.then(
(PDFViewController controller) => controller._updateWidget(widget));
(PDFViewController controller) => controller._updateWidget(widget));
}

@override
Expand Down Expand Up @@ -257,6 +268,8 @@ class _PDFViewSettings {
this.fitPolicy,
this.preventLinkNavigation,
this.backgroundColor,
this.maxZoom,
this.minZoom,
});

static _PDFViewSettings fromWidget(PDFView widget) {
Expand All @@ -276,6 +289,8 @@ class _PDFViewSettings {
fitPolicy: widget.fitPolicy,
preventLinkNavigation: widget.preventLinkNavigation,
backgroundColor: widget.backgroundColor,
maxZoom: widget.maxZoom,
minZoom: widget.minZoom,
);
}

Expand All @@ -296,6 +311,9 @@ class _PDFViewSettings {

final Color? backgroundColor;

final double? maxZoom;
final double? minZoom;

Map<String, dynamic> toMap() {
return <String, dynamic>{
'enableSwipe': enableSwipe,
Expand All @@ -313,6 +331,8 @@ class _PDFViewSettings {
'fitPolicy': fitPolicy.toString(),
'preventLinkNavigation': preventLinkNavigation,
'backgroundColor': backgroundColor?.value,
'maxZoom': maxZoom,
'minZoom': minZoom,
};
}

Expand All @@ -330,15 +350,21 @@ class _PDFViewSettings {
if (preventLinkNavigation != newSettings.preventLinkNavigation) {
updates['preventLinkNavigation'] = newSettings.preventLinkNavigation;
}
if (maxZoom != newSettings.maxZoom) {
updates['maxZoom'] = newSettings.maxZoom;
}
if (minZoom != newSettings.minZoom) {
updates['minZoom'] = newSettings.minZoom;
}
return updates;
}
}

class PDFViewController {
PDFViewController._(
int id,
PDFView widget,
) : _channel = MethodChannel('plugins.endigo.io/pdfview_$id'),
int id,
PDFView widget,
) : _channel = MethodChannel('plugins.endigo.io/pdfview_$id'),
_widget = widget {
_settings = _PDFViewSettings.fromWidget(widget);
_channel.setMethodCallHandler(_onMethodCall);
Expand Down Expand Up @@ -396,7 +422,7 @@ class PDFViewController {

Future<bool?> setPage(int page) async {
final bool? isSet =
await _channel.invokeMethod('setPage', <String, dynamic>{
await _channel.invokeMethod('setPage', <String, dynamic>{
'page': page,
});
return isSet;
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_pdfview
description: A Flutter plugin that provides a PDFView widget on Android and iOS.
version: 1.4.4
version: 1.4.5
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Version this as a breaking release.

This PR also raises the iOS minimum to 13.0 in ios/flutter_pdfview/Package.swift and ios/flutter_pdfview.podspec. That is a compatibility break for consumers still on iOS 12, so shipping it as 1.4.5 means ^1.4.x users can pick up a breaking change on a patch update.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pubspec.yaml` at line 3, The package version must be bumped to a breaking
release because raising the iOS minimum to 13.0 in
ios/flutter_pdfview/Package.swift and ios/flutter_pdfview.podspec is not
backward-compatible; update the version field in pubspec.yaml from 1.4.5 to a
new major release (e.g., 2.0.0), update any related metadata (CHANGELOG entry
and release notes) to indicate the breaking change, and regenerate/update
lockfiles or CI artifacts as needed so consumers cannot accidentally receive
this change via a patch semver range.

homepage: https://github.com/endigo/flutter_pdfview

environment:
Expand Down