Skip to content

Commit 26ee7e1

Browse files
committed
Adds robust link interception and tap control
- Implements cross-platform link interception, enabling custom handling or cancellation of navigation via the `LinkTapped` event. - Introduces the `EnableTapGestures` property to explicitly control custom tap detection, ensuring native link handling takes precedence by default. - Updates iOS with `PdfViewDelegate` and refines Android's `TapListener` to support the new event flow. - Enhances README with comprehensive guides and examples for link handling, tap gestures, and troubleshooting.
1 parent aaae044 commit 26ee7e1

File tree

5 files changed

+235
-36
lines changed

5 files changed

+235
-36
lines changed

README.md

Lines changed: 189 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
[![NuGet](https://img.shields.io/nuget/v/Eightbot.MauiNativePdfView.svg)](https://www.nuget.org/packages/MauiNativePdfView/)
1010
[![NuGet Downloads](https://img.shields.io/nuget/dt/Eightbot.MauiNativePdfView.svg)](https://www.nuget.org/packages/MauiNativePdfView/)
11+
1112
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
1213
[![.NET](https://img.shields.io/badge/.NET-9.0-purple.svg)](https://dotnet.microsoft.com/download)
1314
[![MAUI](https://img.shields.io/badge/MAUI-Latest-green.svg)](https://github.com/dotnet/maui)
@@ -41,6 +42,7 @@ MauiNativePdfView brings native PDF viewing capabilities to your .NET MAUI appli
4142
-**Password Protection** - Full support for encrypted PDFs
4243
-**Zoom & Gestures** - Pinch-to-zoom, double-tap zoom, with configurable min/max levels
4344
-**Page Navigation** - Swipe between pages, programmatic navigation, page events
45+
-**Link Interception** - Intercept and handle link taps before navigation (both platforms)
4446
-**Link Handling** - Automatic detection and handling of internal/external links
4547
-**Display Modes** - Single page or continuous scrolling
4648
-**Scroll Orientation** - Vertical or horizontal page layout
@@ -49,6 +51,7 @@ MauiNativePdfView brings native PDF viewing capabilities to your .NET MAUI appli
4951

5052
-**Annotation Rendering** - Toggle PDF annotations on/off
5153
-**Annotation Events** - Tap detection with annotation details (iOS)
54+
-**Tap Gesture Control** - Enable/disable custom tap interception
5255
-**Quality Control** - Antialiasing and rendering quality settings
5356
-**Background Color** - Customizable viewer background
5457
-**Page Spacing** - Adjustable spacing between pages
@@ -58,8 +61,8 @@ MauiNativePdfView brings native PDF viewing capabilities to your .NET MAUI appli
5861

5962
- `DocumentLoaded` - Fires when PDF is loaded with page count and metadata
6063
- `PageChanged` - Current page and total page count updates
61-
- `LinkTapped` - User taps on a link with URI and destination
62-
- `Tapped` - General tap events with page coordinates
64+
- `LinkTapped` - Intercept link taps before navigation (set `e.Handled = true` to prevent)
65+
- `Tapped` - General tap events with page coordinates (requires `EnableTapGestures = true`)
6366
- `AnnotationTapped` - Annotation tap with type, content, and bounds (iOS)
6467
- `Rendered` - Initial rendering complete
6568
- `Error` - Error handling with detailed messages
@@ -149,6 +152,7 @@ private void OnPageChanged(object sender, PageChangedEventArgs e)
149152
| `Source` | `PdfSource` | `null` | PDF source to display |
150153
| `EnableZoom` | `bool` | `true` | Enable pinch-to-zoom |
151154
| `EnableSwipe` | `bool` | `true` | Enable swipe gestures |
155+
| `EnableTapGestures` | `bool` | `false` | Enable tap interception |
152156
| `EnableLinkNavigation` | `bool` | `true` | Enable clickable links |
153157
| `Zoom` | `float` | `1.0f` | Current zoom level |
154158
| `MinZoom` | `float` | `1.0f` | Minimum zoom level |
@@ -287,13 +291,17 @@ public partial class PdfPage : ContentPage
287291
{
288292
if (e.Uri != null)
289293
{
290-
// Handle external link
294+
// Intercept and handle external link yourself
295+
DisplayAlert("Link Tapped", $"Opening: {e.Uri}", "OK");
291296
Launcher.OpenAsync(e.Uri);
297+
298+
// Prevent default navigation
292299
e.Handled = true;
293300
}
294301
else if (e.DestinationPage.HasValue)
295302
{
296-
// Internal link - handled automatically
303+
// Internal link - allow default navigation
304+
// Or set e.Handled = true to prevent it
297305
}
298306
}
299307

@@ -394,6 +402,57 @@ catch (Exception ex)
394402
}
395403
```
396404

405+
### Link Interception
406+
407+
Both iOS and Android support intercepting link taps before navigation occurs. This allows you to handle links yourself or prevent navigation entirely.
408+
409+
```csharp
410+
pdfViewer.LinkTapped += (sender, e) =>
411+
{
412+
Console.WriteLine($"Link tapped: {e.Uri}");
413+
414+
if (e.Uri?.Contains("example.com") == true)
415+
{
416+
// Custom handling for specific domain
417+
DisplayAlert("Info", "This link is not allowed", "OK");
418+
e.Handled = true; // Prevent navigation
419+
}
420+
else if (e.Uri != null)
421+
{
422+
// Log analytics before opening
423+
Analytics.TrackEvent("PDF_Link_Clicked", new Dictionary<string, string>
424+
{
425+
{ "Uri", e.Uri }
426+
});
427+
428+
// Allow default navigation (or handle manually)
429+
e.Handled = false;
430+
}
431+
};
432+
```
433+
434+
**Platform Implementation:**
435+
- **iOS**: Uses `PdfViewDelegate.WillClickOnLink` to intercept before navigation
436+
- **Android**: Uses `LinkHandler.HandleLinkEvent` to intercept before navigation
437+
438+
### Tap Gesture Handling
439+
440+
Enable custom tap detection with page coordinates:
441+
442+
```csharp
443+
pdfViewer.EnableTapGestures = true;
444+
445+
pdfViewer.Tapped += (sender, e) =>
446+
{
447+
Console.WriteLine($"Tapped page {e.PageIndex} at ({e.X}, {e.Y})");
448+
449+
// Add your custom tap handling logic
450+
// For example: show a custom menu, add annotations, etc.
451+
};
452+
```
453+
454+
**Note**: When `EnableTapGestures = false` (default), the PDF viewer uses native platform tap handling which is optimized for link detection.
455+
397456
### Annotation Handling (iOS)
398457

399458
```csharp
@@ -404,11 +463,97 @@ private void OnAnnotationTapped(object sender, AnnotationTappedEventArgs e)
404463
Console.WriteLine($"Contents: {e.Contents}");
405464
Console.WriteLine($"Bounds: {e.Bounds}");
406465

407-
// Prevent default behavior
466+
// Prevent default behavior if needed
408467
e.Handled = true;
409468
}
410469
```
411470

471+
**Note**: Annotation tap detection is only supported on iOS with PDFKit. Android's AhmerPdfium library does not expose annotation tap events.
472+
473+
## 🎯 Common Scenarios
474+
475+
### Restrict External Navigation
476+
477+
```csharp
478+
pdfViewer.LinkTapped += (sender, e) =>
479+
{
480+
if (e.Uri != null && e.Uri.StartsWith("http"))
481+
{
482+
DisplayAlert("Restricted", "External links are not allowed", "OK");
483+
e.Handled = true; // Block navigation
484+
}
485+
};
486+
```
487+
488+
### Track Link Clicks for Analytics
489+
490+
```csharp
491+
pdfViewer.LinkTapped += (sender, e) =>
492+
{
493+
// Log the link click
494+
Analytics.TrackEvent("PDF_Link_Clicked", new Dictionary<string, string>
495+
{
496+
{ "Document", pdfViewer.Source?.ToString() ?? "Unknown" },
497+
{ "Link", e.Uri ?? $"Page {e.DestinationPage}" },
498+
{ "CurrentPage", pdfViewer.CurrentPage.ToString() }
499+
});
500+
501+
// Allow normal navigation
502+
e.Handled = false;
503+
};
504+
```
505+
506+
### Custom Link Handling with Confirmation
507+
508+
```csharp
509+
pdfViewer.LinkTapped += async (sender, e) =>
510+
{
511+
if (e.Uri != null)
512+
{
513+
var result = await DisplayAlert(
514+
"Open Link?",
515+
$"Do you want to open {e.Uri}?",
516+
"Yes",
517+
"No"
518+
);
519+
520+
if (result)
521+
{
522+
await Launcher.OpenAsync(e.Uri);
523+
}
524+
525+
e.Handled = true; // Prevent default navigation
526+
}
527+
};
528+
```
529+
530+
### Deep Link Handling
531+
532+
```csharp
533+
pdfViewer.LinkTapped += async (sender, e) =>
534+
{
535+
if (e.Uri?.StartsWith("myapp://") == true)
536+
{
537+
// Handle custom URL scheme
538+
await Shell.Current.GoToAsync(e.Uri.Replace("myapp://", ""));
539+
e.Handled = true;
540+
}
541+
};
542+
```
543+
544+
### Disable All Link Navigation
545+
546+
```csharp
547+
// Simple approach
548+
pdfViewer.EnableLinkNavigation = false;
549+
550+
// Or intercept all links
551+
pdfViewer.LinkTapped += (sender, e) =>
552+
{
553+
e.Handled = true; // Block all navigation
554+
};
555+
```
556+
412557
## 🏗️ Architecture
413558

414559
```
@@ -445,15 +590,17 @@ private void OnAnnotationTapped(object sender, AnnotationTappedEventArgs e)
445590

446591
- **Framework**: Apple's native PDFKit
447592
- **Version**: iOS 12.2+
448-
- **Features**: Full annotation support, smooth rendering
593+
- **Features**: Full annotation support, smooth rendering, link interception via `PdfViewDelegate`
594+
- **Link Handling**: Native `WillClickOnLink` delegate method
449595
- **Size**: 0 KB (system framework)
450596

451597
### Android (AhmerPdfium)
452598

453599
- **Library**: [AhmerPdfium](https://github.com/AhmerAfzal1/AhmerPdfium) by Ahmer Afzal
454600
- **Base**: Enhanced fork of [AndroidPdfViewer](https://github.com/barteksc/AndroidPdfViewer)
455601
- **Version**: 2.0.1 (viewer) + 1.9.2 (pdfium)
456-
- **Features**: 16KB page size support, reliable rendering
602+
- **Features**: 16KB page size support, reliable rendering, link interception via `LinkHandler`
603+
- **Link Handling**: Custom `ILinkHandler` implementation
457604
- **Size**: ~16MB (native libraries for all architectures)
458605
- **Note**: Annotation tap events not supported by library
459606

@@ -471,7 +618,7 @@ Contributions are welcome! Please feel free to submit a Pull Request. For major
471618
### Building from Source
472619

473620
```bash
474-
git clone https://github.com/yourusername/MauiNativePdfView.git
621+
git clone https://github.com/TheEightBot/MauiNativePdfView.git
475622
cd MauiNativePdfView
476623
dotnet restore
477624
dotnet build
@@ -484,9 +631,40 @@ cd samples/MauiPdfViewerSample
484631
dotnet build
485632
```
486633

487-
## 📝 Changelog
634+
## ❓ Troubleshooting
635+
636+
### Links Not Working on iOS
637+
638+
If links are not responding on iOS, ensure:
639+
1. `EnableLinkNavigation = true` (default)
640+
2. The PDF actually contains link annotations
641+
3. You're not setting `e.Handled = true` for all links in the `LinkTapped` event
642+
643+
### Tapped Event Not Firing
644+
645+
The `Tapped` event requires:
646+
```csharp
647+
pdfViewer.EnableTapGestures = true;
648+
```
649+
650+
**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.
651+
652+
### LinkTapped Event Handler Not Called
653+
654+
Ensure you're subscribing to the event:
655+
```csharp
656+
pdfViewer.LinkTapped += OnLinkTapped;
657+
```
658+
659+
Or in XAML:
660+
```xml
661+
<pdf:PdfView LinkTapped="OnLinkTapped" />
662+
```
663+
664+
### Android Annotation Events
665+
666+
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.
488667

489-
See [FEATURE_ENHANCEMENT_PLAN.md](FEATURE_ENHANCEMENT_PLAN.md) for detailed implementation history and feature roadmap.
490668

491669
## 📄 License
492670

@@ -506,9 +684,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
506684

507685
## 💬 Support
508686

509-
- 📖 [Documentation](https://github.com/yourusername/MauiNativePdfView/wiki)
510-
- 🐛 [Issue Tracker](https://github.com/yourusername/MauiNativePdfView/issues)
511-
- 💬 [Discussions](https://github.com/yourusername/MauiNativePdfView/discussions)
687+
- 🐛 [Issue Tracker](https://github.com/TheEightBot/MauiNativePdfView/issues)
512688

513689
## ⭐ Show Your Support
514690

samples/MauiPdfViewerSample/PdfTestPage.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ private void OnAnnotationTapped(object? sender, AnnotationTappedEventArgs e)
175175

176176
private async void OnLinkTapped(object? sender, LinkTappedEventArgs e)
177177
{
178+
e.Handled = true;
179+
178180
var linkDescription = !string.IsNullOrEmpty(e.Uri)
179181
? e.Uri
180182
: e.DestinationPage.HasValue

src/MauiNativePdfView/PdfView.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class PdfView : View
4646
nameof(EnableTapGestures),
4747
typeof(bool),
4848
typeof(PdfView),
49-
true);
49+
false);
5050

5151
/// <summary>
5252
/// Bindable property for enabling link navigation.

src/MauiNativePdfView/Platforms/Android/PdfViewAndroid.cs

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,9 @@ public bool EnableTapGestures
9898

9999
_enableTapGestures = value;
100100

101-
if (!_enableTapGestures)
101+
if (_pageCount > 0)
102102
{
103-
_pdfView.SetOnTapListener(null);
104-
}
105-
else if (_tapListener != null)
106-
{
107-
_pdfView.SetOnTapListener(_tapListener);
103+
Reload();
108104
}
109105
}
110106
}
@@ -341,14 +337,6 @@ private void ConfigureAndLoad(PDFView.Configurator configurator, int pageToResto
341337
.OnError(new ErrorListener(this))
342338
.OnTap(_enableTapGestures ? _tapListener ??= new TapListener(this) : null)
343339
.OnRender(new RenderListener(this));
344-
if (_enableTapGestures && _tapListener == null)
345-
{
346-
_tapListener = new TapListener(this);
347-
}
348-
if (_enableTapGestures && _tapListener != null)
349-
{
350-
_pdfView.SetOnTapListener(_tapListener);
351-
}
352340

353341
// Note: UseBestQuality sets rendering quality (ARGB_8888 vs RGB_565)
354342
// This is handled by the PDFView configuration automatically based on device capabilities
@@ -522,12 +510,10 @@ public void HandleLinkEvent(LinkTapEvent? linkTapEvent)
522510
private class TapListener : Java.Lang.Object, IOnTapListener
523511
{
524512
private readonly WeakReference<PdfViewAndroid> _viewRef;
525-
private readonly WeakReference<PDFView> _nativeViewRef;
526513

527514
public TapListener(PdfViewAndroid view)
528515
{
529516
_viewRef = new WeakReference<PdfViewAndroid>(view);
530-
_nativeViewRef = new WeakReference<PDFView>(view._pdfView);
531517
}
532518

533519
public bool OnTap(MotionEvent? e)
@@ -536,12 +522,8 @@ public bool OnTap(MotionEvent? e)
536522
{
537523
view.OnTapped(view.CurrentPage, e.GetX(), e.GetY());
538524
}
539-
if (_nativeViewRef.TryGetTarget(out var nativeView))
540-
{
541-
return nativeView.HandleTap(e);
542-
}
543525

544-
return false; // fallback to default gesture pipeline
526+
return false; // allow PDFView to continue default handling (links, etc.)
545527
}
546528
}
547529

@@ -569,7 +551,6 @@ public void Dispose()
569551
{
570552
if (_tapListener != null)
571553
{
572-
_pdfView.SetOnTapListener(null);
573554
_tapListener.Dispose();
574555
_tapListener = null;
575556
}

0 commit comments

Comments
 (0)