Skip to content

TheEightBot/MauiNativePdfView

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

50 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

MauiNativePdfView

A high-performance, cross-platform PDF viewer for .NET MAUI

NuGet NuGet Downloads

License: MIT .NET MAUI

Native PDF rendering β€’ Zero web dependencies β€’ Full feature parity

Features β€’ Installation β€’ Quick Start β€’ Documentation β€’ Examples


🎯 Overview

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.

Why MauiNativePdfView?

  • πŸš€ 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

✨ Features

Core Functionality

  • βœ… 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

Advanced Features

  • βœ… 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

Events

  • DocumentLoaded - Fires when PDF is loaded with page count and metadata
  • PageChanged - Current page and total page count updates
  • LinkTapped - Intercept link taps before navigation (set e.Handled = true to prevent)
  • Tapped - General tap events with page coordinates (requires EnableTapGestures = true)
  • AnnotationTapped - Annotation tap with type, content, and bounds (iOS)
  • Rendered - Initial rendering complete
  • Error - Error handling with detailed messages

πŸ“¦ Installation

NuGet Package Manager

dotnet add package MauiNativePdfView

Package Manager Console

Install-Package MauiNativePdfView

Requirements

  • .NET 9.0 or later
  • iOS 12.2+ (PDFKit)
  • Android 7.0+ (API 24+)

πŸš€ Quick Start

1. Add Namespace

xmlns:pdf="clr-namespace:MauiNativePdfView;assembly=MauiNativePdfView"

2. Basic XAML

<pdf:PdfView x:Name="pdfViewer"
             EnableZoom="True"
             EnableSwipe="True"
             DocumentLoaded="OnDocumentLoaded"
             PageChanged="OnPageChanged" />

3. Load a 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");

4. Handle Events

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}");
}

πŸ“– Documentation

PdfView Properties

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)

PdfSource Types

// 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);

Enums

// 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
}

Methods

// Navigate to specific page (0-based)
pdfViewer.GoToPage(int pageIndex);

// Reload current document
pdfViewer.Reload();

πŸ’‘ Examples

Complete PDF Viewer

<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);
    }
}

MVVM Pattern

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}" />

Password-Protected PDFs

try
{
    pdfViewer.Source = PdfSource.FromFile("encrypted.pdf", "mypassword");
}
catch (Exception ex)
{
    // Handle incorrect password
    await DisplayAlert("Error", "Invalid password", "OK");
}

Link Interception

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.WillClickOnLink to intercept before navigation
  • Android: Uses LinkHandler.HandleLinkEvent to intercept before navigation

Tap Gesture Handling

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.

Annotation Handling (iOS)

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.

🎯 Common Scenarios

Restrict External Navigation

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
    }
};

Track Link Clicks for Analytics

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;
};

Custom Link Handling with Confirmation

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
    }
};

Deep Link Handling

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;
    }
};

Disable All Link Navigation

// Simple approach
pdfViewer.EnableLinkNavigation = false;

// Or intercept all links
pdfViewer.LinkTapped += (sender, e) =>
{
    e.Handled = true; // Block all navigation
};

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     .NET MAUI Application           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      MauiNativePdfView              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   PdfView (MAUI Control)       β”‚ β”‚
β”‚  β”‚   - Bindable Properties        β”‚ β”‚
β”‚  β”‚   - Event Handlers             β”‚ β”‚
β”‚  β”‚   - Platform Handlers          β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚                β”‚
β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
β”‚  Android   β”‚   β”‚    iOS     β”‚
β”‚  Handler   β”‚   β”‚  Handler   β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚               β”‚
β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
β”‚AhmerPdfium β”‚   β”‚  PDFKit    β”‚
β”‚ (Native)   β”‚   β”‚ (Native)   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”§ Platform Details

iOS (PDFKit)

  • Framework: Apple's native PDFKit
  • Version: iOS 12.2+
  • Features: Full annotation support, smooth rendering, link interception via PdfViewDelegate
  • Link Handling: Native WillClickOnLink delegate method
  • Size: 0 KB (system framework)

Android (AhmerPdfium)

  • 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 ILinkHandler implementation
  • Size: ~16MB (native libraries for all architectures)
  • Note: Annotation tap events not supported by library

πŸ“Š Performance

  • 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

🀝 Contributing

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.

Building from Source

git clone https://github.com/TheEightBot/MauiNativePdfView.git
cd MauiNativePdfView
dotnet restore
dotnet build

Running Tests

cd samples/MauiPdfViewerSample
dotnet build

❓ Troubleshooting

Links Not Working on iOS

If links are not responding on iOS, ensure:

  1. EnableLinkNavigation = true (default)
  2. The PDF actually contains link annotations
  3. You're not setting e.Handled = true for all links in the LinkTapped event

Tapped Event Not Firing

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.

LinkTapped Event Handler Not Called

Ensure you're subscribing to the event:

pdfViewer.LinkTapped += OnLinkTapped;

Or in XAML:

<pdf:PdfView LinkTapped="OnLinkTapped" />

Android Annotation Events

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.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

Third-Party Licenses

  • AhmerPdfium: Apache License 2.0
  • PDFKit: Apple System Framework

πŸ™ Acknowledgments

  • Ahmer Afzal - AhmerPdfium library maintainer
  • barteksc - Original AndroidPdfViewer
  • Apple - PDFKit framework
  • .NET MAUI Team - Excellent framework

πŸ’¬ Support

⭐ Show Your Support

If you find this library helpful, please give it a star! ⭐


Made with ❀️ for the .NET MAUI community

⬆ Back to Top

About

A high-performance, cross-platform PDF viewer for .NET MAUI

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages