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+)
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiNativePdfView() // <--- ADD THIS LINE
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
}
}Option A: Custom Schema (Recommended)
xmlns:pdf="http://eightbot.com/maui/pdfview"Option B: CLR Namespace
xmlns:pdf="clr-namespace:MauiNativePdfView;assembly=MauiNativePdfView"<!-- Simple string binding - auto-converts to PdfSource! -->
<pdf:PdfView Source="https://example.com/document.pdf" />
<!-- Or with full control -->
<pdf:PdfView x:Name="pdfViewer"
EnableZoom="True"
EnableSwipe="True"
DocumentLoaded="OnDocumentLoaded"
PageChanged="OnPageChanged" />The library supports automatic string conversion in XAML:
<!-- URL - automatically becomes UriPdfSource -->
<pdf:PdfView Source="https://example.com/document.pdf" />
<!-- Asset - simple filename becomes AssetPdfSource -->
<pdf:PdfView Source="sample.pdf" />
<!-- Asset with explicit prefix -->
<pdf:PdfView Source="asset://documents/guide.pdf" />
<!-- File URI -->
<pdf:PdfView Source="file:///path/to/document.pdf" />// 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) |
The PdfSource class supports automatic string conversion via implicit operators and TypeConverter, making it easy to use in both XAML and code.
Factory Methods (Code-Behind):
// 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);Implicit Conversion (Convenient):
// String to PdfSource - auto-detects type
PdfSource source = "https://example.com/doc.pdf"; // → UriPdfSource
PdfSource source = "sample.pdf"; // → AssetPdfSource
PdfSource source = "/path/to/file.pdf"; // → FilePdfSource
// Uri to PdfSource
PdfSource source = new Uri("https://example.com/doc.pdf");String Conversion Rules:
| Pattern | Result |
|---|---|
http://... or https://... |
UriPdfSource |
asset://filename.pdf |
AssetPdfSource |
file:///path/to/file.pdf |
FilePdfSource |
filename.pdf (no path separators) |
AssetPdfSource |
/path/to/file.pdf (rooted path) |
FilePdfSource |
// 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
If you find this library helpful, please give it a star! ⭐
Made with ❤️ for the .NET MAUI community