@@ -10,7 +10,7 @@ namespace MauiNativePdfView.Platforms.iOS;
1010/// </summary>
1111public class PdfViewiOS : IPdfView , IDisposable
1212{
13- private readonly PdfKit . PdfView _pdfView ;
13+ private readonly NativePdfView _pdfView ;
1414 private PdfSource ? _source ;
1515 private bool _disposed ;
1616 private NSObject ? _pageChangedObserver ;
@@ -21,16 +21,25 @@ public class PdfViewiOS : IPdfView, IDisposable
2121 private bool _documentLoaded = false ;
2222 private bool _enableAnnotationRendering = true ;
2323 private bool _enableTapGestures = true ;
24+ private FitPolicy _fitPolicy = FitPolicy . Width ;
25+ private bool _needsFitReapply = false ;
2426
2527 public PdfViewiOS ( )
2628 {
27- _pdfView = new PdfKit . PdfView
29+ _pdfView = new NativePdfView
2830 {
2931 AutoScales = true ,
3032 DisplayMode = PdfKit . PdfDisplayMode . SinglePageContinuous ,
3133 DisplayDirection = PdfDisplayDirection . Vertical
3234 } ;
3335
36+ // Re-apply deferred fit policy once the view has been laid out and has real bounds.
37+ _pdfView . LayoutSubviewsAction = ( ) =>
38+ {
39+ if ( _needsFitReapply )
40+ ApplyFitPolicy ( ) ;
41+ } ;
42+
3443 // Subscribe to page change notifications
3544 _pageChangedObserver = NSNotificationCenter . DefaultCenter . AddObserver (
3645 PdfKit . PdfView . PageChangedNotification ,
@@ -146,26 +155,81 @@ public int PageSpacing
146155
147156 public FitPolicy FitPolicy
148157 {
149- get => _pdfView . AutoScales ? FitPolicy . Width : FitPolicy . Both ;
158+ get => _fitPolicy ;
150159 set
151160 {
152- switch ( value )
153- {
154- case FitPolicy . Width :
155- _pdfView . AutoScales = true ;
156- _pdfView . DisplayMode = PdfKit . PdfDisplayMode . SinglePageContinuous ;
157- break ;
158- case FitPolicy . Height :
159- _pdfView . AutoScales = true ;
160- _pdfView . DisplayMode = PdfKit . PdfDisplayMode . SinglePage ;
161- break ;
162- case FitPolicy . Both :
163- _pdfView . AutoScales = false ;
164- break ;
165- }
161+ _fitPolicy = value ;
162+ ApplyFitPolicy ( ) ;
163+ }
164+ }
165+
166+ private void ApplyFitPolicy ( )
167+ {
168+ switch ( _fitPolicy )
169+ {
170+ case FitPolicy . Width :
171+ _pdfView . AutoScales = true ;
172+ _pdfView . DisplayMode = PdfKit . PdfDisplayMode . SinglePageContinuous ;
173+ _needsFitReapply = false ;
174+ break ;
175+
176+ case FitPolicy . Height :
177+ // PdfKit AutoScales only fits width; calculate scale manually.
178+ _pdfView . AutoScales = false ;
179+ _pdfView . DisplayMode = PdfKit . PdfDisplayMode . SinglePageContinuous ;
180+ _needsFitReapply = ! SetManualScale ( fitWidth : false , fitHeight : true ) ;
181+ break ;
182+
183+ case FitPolicy . Both :
184+ // Fit to the smaller of the width/height scale factors so the whole page
185+ // is visible. Use SinglePageContinuous to avoid SinglePage's inflated
186+ // internal scroll content size.
187+ _pdfView . AutoScales = false ;
188+ _pdfView . DisplayMode = PdfKit . PdfDisplayMode . SinglePageContinuous ;
189+ _needsFitReapply = ! SetManualScale ( fitWidth : true , fitHeight : true ) ;
190+ break ;
166191 }
167192 }
168193
194+ /// <summary>
195+ /// Computes and applies ScaleFactor so the current page fits the requested axes.
196+ /// Returns <c>true</c> on success, <c>false</c> if the view or page isn't ready yet
197+ /// (caller should set <see cref="_needsFitReapply"/> and retry on next layout pass).
198+ /// </summary>
199+ private bool SetManualScale ( bool fitWidth , bool fitHeight )
200+ {
201+ var page = _pdfView . CurrentPage ;
202+ if ( page == null )
203+ return false ;
204+
205+ var viewBounds = _pdfView . Bounds ;
206+ if ( viewBounds . Width <= 0 || viewBounds . Height <= 0 )
207+ return false ;
208+
209+ var pageRect = page . GetBoundsForBox ( PdfDisplayBox . Media ) ;
210+ if ( pageRect . Width <= 0 || pageRect . Height <= 0 )
211+ return false ;
212+
213+ nfloat scale ;
214+ if ( fitWidth && fitHeight )
215+ {
216+ var scaleW = ( nfloat ) ( viewBounds . Width / pageRect . Width ) ;
217+ var scaleH = ( nfloat ) ( viewBounds . Height / pageRect . Height ) ;
218+ scale = scaleW < scaleH ? scaleW : scaleH ;
219+ }
220+ else if ( fitHeight )
221+ {
222+ scale = ( nfloat ) ( viewBounds . Height / pageRect . Height ) ;
223+ }
224+ else
225+ {
226+ scale = ( nfloat ) ( viewBounds . Width / pageRect . Width ) ;
227+ }
228+
229+ _pdfView . ScaleFactor = scale ;
230+ return true ;
231+ }
232+
169233 public Abstractions . PdfDisplayMode DisplayMode
170234 {
171235 get
@@ -358,7 +422,13 @@ private void LoadDocument()
358422 }
359423 }
360424
361- MainThread . BeginInvokeOnMainThread ( ( ) => _pdfView . Document = document ) ;
425+ MainThread . BeginInvokeOnMainThread ( ( ) =>
426+ {
427+ _pdfView . Document = document ;
428+ // Re-apply fit policy now that the document is loaded and
429+ // the view has valid bounds to calculate scale against.
430+ ApplyFitPolicy ( ) ;
431+ } ) ;
362432
363433 // Get document metadata
364434 var pageCount = ( int ) document . PageCount ;
@@ -553,6 +623,22 @@ public void Dispose()
553623 _pdfView ? . Dispose ( ) ;
554624 }
555625
626+ /// <summary>
627+ /// Custom PdfView subclass that fires a callback on each layout pass, allowing
628+ /// <see cref="PdfViewiOS"/> to defer fit-policy scale calculations until the view
629+ /// has non-zero bounds (which isn't guaranteed when the document first loads).
630+ /// </summary>
631+ private class NativePdfView : PdfKit . PdfView
632+ {
633+ internal Action ? LayoutSubviewsAction { get ; set ; }
634+
635+ public override void LayoutSubviews ( )
636+ {
637+ base . LayoutSubviews ( ) ;
638+ LayoutSubviewsAction ? . Invoke ( ) ;
639+ }
640+ }
641+
556642 /// <summary>
557643 /// Delegate implementation to intercept link clicks in PDFView.
558644 /// </summary>
0 commit comments