Native PDF rendering β’ Zero web dependencies β’ Full feature parity
Features β’ Installation β’ Quick Start β’ Documentation β’ Examples
MauiNativePdfView brings native PDF viewing capabilities to your .NET MAUI applications by wrapping platform-native controls. Unlike WebView-based solutions, this library provides true native performance with full access to platform-specific PDF features.
- π Native Performance - Uses PDFKit (iOS) and AhmerPdfium (Android) for optimal speed
- πͺ Feature Complete - Comprehensive API with zoom, navigation, annotations, and events
- π¨ Consistent API - Write once, works on both iOS and Android
- π¦ Zero Web Dependencies - No WebView, no JavaScript, pure native rendering
- π§ Highly Configurable - Extensive properties for customization
- π± Production Ready - Battle-tested with full annotation support
- β Multiple PDF Sources - Load from files, URLs, streams, byte arrays, or embedded assets
- β Password Protection - Full support for encrypted PDFs
- β Zoom & Gestures - Pinch-to-zoom, double-tap zoom, with configurable min/max levels
- β Page Navigation - Swipe between pages, programmatic navigation, page events
- β Link Interception - Intercept and handle link taps before navigation (both platforms)
- β Link Handling - Automatic detection and handling of internal/external links
- β Display Modes - Single page or continuous scrolling
- β Scroll Orientation - Vertical or horizontal page layout
- β Annotation Rendering - Toggle PDF annotations on/off
- β Annotation Events - Tap detection with annotation details (iOS)
- β Tap Gesture Control - Enable/disable custom tap interception
- β Quality Control - Antialiasing and rendering quality settings
- β Background Color - Customizable viewer background
- β Page Spacing - Adjustable spacing between pages
- β Event System - Comprehensive events for document lifecycle
DocumentLoaded- Fires when PDF is loaded with page count and metadataPageChanged- Current page and total page count updatesLinkTapped- Intercept link taps before navigation (sete.Handled = trueto prevent)Tapped- General tap events with page coordinates (requiresEnableTapGestures = true)AnnotationTapped- Annotation tap with type, content, and bounds (iOS)Rendered- Initial rendering completeError- Error handling with detailed messages
dotnet add package MauiNativePdfViewInstall-Package MauiNativePdfView- .NET 9.0 or later
- iOS 12.2+ (PDFKit)
- Android 7.0+ (API 24+)
xmlns:pdf="clr-namespace:MauiNativePdfView;assembly=MauiNativePdfView"<pdf:PdfView x:Name="pdfViewer"
EnableZoom="True"
EnableSwipe="True"
DocumentLoaded="OnDocumentLoaded"
PageChanged="OnPageChanged" />// From file
pdfViewer.Source = PdfSource.FromFile("/path/to/document.pdf");
// From URL
pdfViewer.Source = PdfSource.FromUri(new Uri("https://example.com/doc.pdf"));
// From embedded asset
pdfViewer.Source = PdfSource.FromAsset("sample.pdf");
// From stream
pdfViewer.Source = PdfSource.FromStream(myStream);
// From byte array
pdfViewer.Source = PdfSource.FromBytes(pdfBytes);
// With password
pdfViewer.Source = PdfSource.FromFile("/path/to/encrypted.pdf", "password");private void OnDocumentLoaded(object sender, DocumentLoadedEventArgs e)
{
Console.WriteLine($"Loaded: {e.PageCount} pages");
Console.WriteLine($"Title: {e.Title}");
Console.WriteLine($"Author: {e.Author}");
}
private void OnPageChanged(object sender, PageChangedEventArgs e)
{
Console.WriteLine($"Page {e.PageIndex + 1} of {e.PageCount}");
}| Property | Type | Default | Description |
|---|---|---|---|
Source |
PdfSource |
null |
PDF source to display |
EnableZoom |
bool |
true |
Enable pinch-to-zoom |
EnableSwipe |
bool |
true |
Enable swipe gestures |
EnableTapGestures |
bool |
false |
Enable tap interception |
EnableLinkNavigation |
bool |
true |
Enable clickable links |
Zoom |
float |
1.0f |
Current zoom level |
MinZoom |
float |
1.0f |
Minimum zoom level |
MaxZoom |
float |
3.0f |
Maximum zoom level |
PageSpacing |
int |
10 |
Spacing between pages (pixels) |
FitPolicy |
FitPolicy |
Width |
How pages fit on screen |
DisplayMode |
PdfDisplayMode |
SinglePageContinuous |
Page display mode |
ScrollOrientation |
PdfScrollOrientation |
Vertical |
Scroll direction |
DefaultPage |
int |
0 |
Initial page (0-based) |
EnableAntialiasing |
bool |
true |
Antialiasing (Android only) |
UseBestQuality |
bool |
true |
Best quality rendering |
BackgroundColor |
Color |
null |
Viewer background color |
EnableAnnotationRendering |
bool |
true |
Show PDF annotations |
CurrentPage |
int |
0 |
Current page (readonly) |
PageCount |
int |
0 |
Total pages (readonly) |
// File path
var source = PdfSource.FromFile(string filePath, string? password = null);
// URI/URL
var source = PdfSource.FromUri(Uri uri, string? password = null);
// Stream
var source = PdfSource.FromStream(Stream stream, string? password = null);
// Byte array
var source = PdfSource.FromBytes(byte[] data, string? password = null);
// Asset/Resource
var source = PdfSource.FromAsset(string assetName, string? password = null);// How pages fit on screen
public enum FitPolicy
{
Width, // Fit to width
Height, // Fit to height
Both // Fit both dimensions
}
// Page display mode
public enum PdfDisplayMode
{
SinglePage, // One page at a time
SinglePageContinuous // Continuous scrolling
}
// Scroll direction
public enum PdfScrollOrientation
{
Vertical, // Scroll up/down
Horizontal // Scroll left/right
}// Navigate to specific page (0-based)
pdfViewer.GoToPage(int pageIndex);
// Reload current document
pdfViewer.Reload();<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pdf="clr-namespace:MauiNativePdfView;assembly=MauiNativePdfView"
x:Class="MyApp.PdfPage">
<Grid RowDefinitions="Auto,*,Auto">
<!-- Toolbar -->
<HorizontalStackLayout Grid.Row="0" Padding="10" Spacing="10">
<Button Text="β" Clicked="OnPreviousPage" />
<Button Text="βΆ" Clicked="OnNextPage" />
<Button Text="Zoom In" Clicked="OnZoomIn" />
<Button Text="Zoom Out" Clicked="OnZoomOut" />
</HorizontalStackLayout>
<!-- PDF Viewer -->
<pdf:PdfView x:Name="pdfViewer"
Grid.Row="1"
EnableZoom="True"
EnableSwipe="True"
EnableLinkNavigation="True"
EnableAnnotationRendering="True"
PageSpacing="10"
BackgroundColor="#F5F5F5"
DocumentLoaded="OnDocumentLoaded"
PageChanged="OnPageChanged"
LinkTapped="OnLinkTapped"
Error="OnError" />
<!-- Status Bar -->
<Label x:Name="statusLabel"
Grid.Row="2"
Padding="10"
HorizontalOptions="Center" />
</Grid>
</ContentPage>public partial class PdfPage : ContentPage
{
public PdfPage()
{
InitializeComponent();
pdfViewer.Source = PdfSource.FromAsset("sample.pdf");
}
private void OnDocumentLoaded(object sender, DocumentLoadedEventArgs e)
{
statusLabel.Text = $"Loaded: {e.PageCount} pages - {e.Title}";
}
private void OnPageChanged(object sender, PageChangedEventArgs e)
{
statusLabel.Text = $"Page {e.PageIndex + 1} of {e.PageCount}";
}
private void OnLinkTapped(object sender, LinkTappedEventArgs e)
{
if (e.Uri != null)
{
// Intercept and handle external link yourself
DisplayAlert("Link Tapped", $"Opening: {e.Uri}", "OK");
Launcher.OpenAsync(e.Uri);
// Prevent default navigation
e.Handled = true;
}
else if (e.DestinationPage.HasValue)
{
// Internal link - allow default navigation
// Or set e.Handled = true to prevent it
}
}
private void OnError(object sender, PdfErrorEventArgs e)
{
DisplayAlert("Error", e.Message, "OK");
}
private void OnPreviousPage(object sender, EventArgs e)
{
if (pdfViewer.CurrentPage > 0)
pdfViewer.GoToPage(pdfViewer.CurrentPage - 1);
}
private void OnNextPage(object sender, EventArgs e)
{
if (pdfViewer.CurrentPage < pdfViewer.PageCount - 1)
pdfViewer.GoToPage(pdfViewer.CurrentPage + 1);
}
private void OnZoomIn(object sender, EventArgs e)
{
pdfViewer.Zoom = Math.Min(pdfViewer.Zoom + 0.5f, pdfViewer.MaxZoom);
}
private void OnZoomOut(object sender, EventArgs e)
{
pdfViewer.Zoom = Math.Max(pdfViewer.Zoom - 0.5f, pdfViewer.MinZoom);
}
}public class PdfViewModel : INotifyPropertyChanged
{
private PdfSource _pdfSource;
private int _currentPage;
private int _pageCount;
public PdfSource PdfSource
{
get => _pdfSource;
set => SetProperty(ref _pdfSource, value);
}
public int CurrentPage
{
get => _currentPage;
set => SetProperty(ref _currentPage, value);
}
public int PageCount
{
get => _pageCount;
set => SetProperty(ref _pageCount, value);
}
public Command LoadPdfCommand { get; }
public Command<int> GoToPageCommand { get; }
public PdfViewModel()
{
LoadPdfCommand = new Command(LoadPdf);
GoToPageCommand = new Command<int>(GoToPage);
}
private void LoadPdf()
{
PdfSource = PdfSource.FromAsset("document.pdf");
}
private void GoToPage(int pageIndex)
{
// Page navigation handled by binding
}
}<pdf:PdfView Source="{Binding PdfSource}"
CurrentPage="{Binding CurrentPage}"
PageCount="{Binding PageCount}" />try
{
pdfViewer.Source = PdfSource.FromFile("encrypted.pdf", "mypassword");
}
catch (Exception ex)
{
// Handle incorrect password
await DisplayAlert("Error", "Invalid password", "OK");
}Both iOS and Android support intercepting link taps before navigation occurs. This allows you to handle links yourself or prevent navigation entirely.
pdfViewer.LinkTapped += (sender, e) =>
{
Console.WriteLine($"Link tapped: {e.Uri}");
if (e.Uri?.Contains("example.com") == true)
{
// Custom handling for specific domain
DisplayAlert("Info", "This link is not allowed", "OK");
e.Handled = true; // Prevent navigation
}
else if (e.Uri != null)
{
// Log analytics before opening
Analytics.TrackEvent("PDF_Link_Clicked", new Dictionary<string, string>
{
{ "Uri", e.Uri }
});
// Allow default navigation (or handle manually)
e.Handled = false;
}
};Platform Implementation:
- iOS: Uses
PdfViewDelegate.WillClickOnLinkto intercept before navigation - Android: Uses
LinkHandler.HandleLinkEventto intercept before navigation
Enable custom tap detection with page coordinates:
pdfViewer.EnableTapGestures = true;
pdfViewer.Tapped += (sender, e) =>
{
Console.WriteLine($"Tapped page {e.PageIndex} at ({e.X}, {e.Y})");
// Add your custom tap handling logic
// For example: show a custom menu, add annotations, etc.
};Note: When EnableTapGestures = false (default), the PDF viewer uses native platform tap handling which is optimized for link detection.
private void OnAnnotationTapped(object sender, AnnotationTappedEventArgs e)
{
Console.WriteLine($"Annotation on page {e.PageIndex + 1}");
Console.WriteLine($"Type: {e.AnnotationType}");
Console.WriteLine($"Contents: {e.Contents}");
Console.WriteLine($"Bounds: {e.Bounds}");
// Prevent default behavior if needed
e.Handled = true;
}Note: Annotation tap detection is only supported on iOS with PDFKit. Android's AhmerPdfium library does not expose annotation tap events.
pdfViewer.LinkTapped += (sender, e) =>
{
if (e.Uri != null && e.Uri.StartsWith("http"))
{
DisplayAlert("Restricted", "External links are not allowed", "OK");
e.Handled = true; // Block navigation
}
};pdfViewer.LinkTapped += (sender, e) =>
{
// Log the link click
Analytics.TrackEvent("PDF_Link_Clicked", new Dictionary<string, string>
{
{ "Document", pdfViewer.Source?.ToString() ?? "Unknown" },
{ "Link", e.Uri ?? $"Page {e.DestinationPage}" },
{ "CurrentPage", pdfViewer.CurrentPage.ToString() }
});
// Allow normal navigation
e.Handled = false;
};pdfViewer.LinkTapped += async (sender, e) =>
{
if (e.Uri != null)
{
var result = await DisplayAlert(
"Open Link?",
$"Do you want to open {e.Uri}?",
"Yes",
"No"
);
if (result)
{
await Launcher.OpenAsync(e.Uri);
}
e.Handled = true; // Prevent default navigation
}
};pdfViewer.LinkTapped += async (sender, e) =>
{
if (e.Uri?.StartsWith("myapp://") == true)
{
// Handle custom URL scheme
await Shell.Current.GoToAsync(e.Uri.Replace("myapp://", ""));
e.Handled = true;
}
};// Simple approach
pdfViewer.EnableLinkNavigation = false;
// Or intercept all links
pdfViewer.LinkTapped += (sender, e) =>
{
e.Handled = true; // Block all navigation
};βββββββββββββββββββββββββββββββββββββββ
β .NET MAUI Application β
βββββββββββββββ¬ββββββββββββββββββββββββ
β
βββββββββββββββΌββββββββββββββββββββββββ
β MauiNativePdfView β
β ββββββββββββββββββββββββββββββββββ β
β β PdfView (MAUI Control) β β
β β - Bindable Properties β β
β β - Event Handlers β β
β β - Platform Handlers β β
β ββββββββββββββββββββββββββββββββββ β
βββββββββββββββ¬ββββββββββββββββββββββββ
β
βββββββββ΄βββββββββ
β β
βββββββΌβββββββ ββββββΌββββββββ
β Android β β iOS β
β Handler β β Handler β
βββββββ¬βββββββ ββββββ¬ββββββββ
β β
βββββββΌβββββββ ββββββΌββββββββ
βAhmerPdfium β β PDFKit β
β (Native) β β (Native) β
ββββββββββββββ ββββββββββββββ
- Framework: Apple's native PDFKit
- Version: iOS 12.2+
- Features: Full annotation support, smooth rendering, link interception via
PdfViewDelegate - Link Handling: Native
WillClickOnLinkdelegate method - Size: 0 KB (system framework)
- Library: AhmerPdfium by Ahmer Afzal
- Base: Enhanced fork of AndroidPdfViewer
- Version: 2.0.1 (viewer) + 1.9.2 (pdfium)
- Features: 16KB page size support, reliable rendering, link interception via
LinkHandler - Link Handling: Custom
ILinkHandlerimplementation - Size: ~16MB (native libraries for all architectures)
- Note: Annotation tap events not supported by library
- Memory Efficient: Native rendering engines handle memory management
- Fast Loading: Platform-optimized PDF parsing
- Smooth Scrolling: Hardware-accelerated rendering
- Large Files: Tested with 100+ page documents
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
git clone https://github.com/TheEightBot/MauiNativePdfView.git
cd MauiNativePdfView
dotnet restore
dotnet buildcd samples/MauiPdfViewerSample
dotnet buildIf links are not responding on iOS, ensure:
EnableLinkNavigation = true(default)- The PDF actually contains link annotations
- You're not setting
e.Handled = truefor all links in theLinkTappedevent
The Tapped event requires:
pdfViewer.EnableTapGestures = true;Note: When EnableTapGestures = true, it may interfere with native link handling on some platforms. For link detection only, keep it false (default) and use the LinkTapped event.
Ensure you're subscribing to the event:
pdfViewer.LinkTapped += OnLinkTapped;Or in XAML:
<pdf:PdfView LinkTapped="OnLinkTapped" />Annotation tap events (AnnotationTapped) are only supported on iOS. The Android AhmerPdfium library does not expose annotation-level tap detection. Use the Tapped event as an alternative for Android.
This project is licensed under the MIT License - see the LICENSE file for details.
- AhmerPdfium: Apache License 2.0
- PDFKit: Apple System Framework
- Ahmer Afzal - AhmerPdfium library maintainer
- barteksc - Original AndroidPdfViewer
- Apple - PDFKit framework
- .NET MAUI Team - Excellent framework
- π Issue Tracker
If you find this library helpful, please give it a star! β
Made with β€οΈ for the .NET MAUI community