Blog Post

How to Bridge Native PSPDFKit for iOS Code to Flutter

Akshat Patel
Oghenerukevwe Henrietta Kofi
Illustration: How to Bridge Native PSPDFKit for iOS Code to Flutter

In this tutorial, we’ll cover how to use the PSPDFKit for Flutter library in two complex use cases by bridging native Objective-C code to Flutter. The Objective-C code we’ll be bridging in this tutorial will make use of some components in the native PSPDFKit for iOS SDK.

There are two main methods of bridging native Android and iOS code with Flutter applications:

PSPDFKit’s Flutter library aims to expose a lot of our native PSPDFKit for iOS framework’s APIs to Dart using both methods. These APIs allow you to easily implement simple use cases in Flutter. We also aimed to keep our Flutter plugin extensible to allow users to bridge native Objective-C code and adapt the plugin to more complex use cases.

ℹ️ Note: This blog post was updated in April 2022.

Prerequisites

Before starting this tutorial, we recommend you first have a fundamental understanding of the following topics:

This post also assumes you’ve already integrated the PSPDFKit for Flutter SDK into your app. Otherwise, you can use our example project from our PSPDFKit Flutter SDK repository on GitHub.

The modifications we’ll make to our SDK and example project in this tutorial are available on this branch of the PSPDFKit Flutter SDK repo.

Background Information

We’ll first cover some background information on some of the Flutter SDK (Dart) and native iOS platform (Objective-C) components in our PSPDFKit for Flutter SDK.

Dart Components Explained

We expose two different components in Dart. Depending on your project’s requirements, you can use one or the other.

  • PspdfkitWidgetController + UiKitView — To display PDF files in Flutter, we use platform views provided by Flutter. On iOS, this is UiKitView. Along with the platform views, we also need to use PspdfkitWidgetController, which is a simple class that uses a method channel on 'com.pspdfkit.widget.$id' to manage the communication between the platform view and Objective-C code (PspdfPlatformView). This platform view must be created with a viewType ('com.pspdfkit.widget') and attached to a new PspdfkitWidgetController after initialization so that it can take care of the communication between Flutter and the Objective-C code. We already provide a ready-to-use widget, PspdfkitWidget, which does both of these things and can be used for simple use cases. The PspdfkitWidgetController component can be composed into more complex widgets and is the recommended way of displaying PDF files within an already present Flutter navigation hierarchy. For more complex use cases, check out the Programmatic Form Filling Example or the Process Annotations Example in our Flutter example app.

  • Pspdfkit Global Plugin — To display a PDF file modally (over all current views, with a navigation bar managed by the platform), we can use the Pspdfkit global plugin. This has the benefit of being much simpler if no customization is needed, and it’s still possible to update several settings during initialization time. Check out the Pspdfkit Global Plugin View Examples section of the Flutter example app to see what’s possible when using the global plugin.

We recommend the PspdfkitWidgetController component for most use cases, whereas the global plugin view should be used for scenarios where a PDF needs to be presented modally.

iOS Platform Components Explained

Corresponding to the two Flutter SDK components described above are their iOS counterparts, which are written in Objective-C: PspdfPlatformView.m and PspdfkitPlugin.m.

PspdfPlatformView.m is an implementation of Flutter’s FlutterPlatformView protocol, and it represents both the main view used to display PDF files and its related view controller. It also manages its own navigation stack, which is set up by default to toggle the navigation bar on and off when tapping.

PspdfPlatformViews are created when required by PspdfPlatformViewFactory, which is an implementation of Flutter’s FlutterPlatformViewFactory protocol. The FlutterPlatformViewFactory protocol requires us to implement the following:

- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
                                   viewIdentifier:(int64_t)viewId
                                        arguments:(id _Nullable)args;

In the above method, we create new PspdfPlatformViews as required by the factory and return them to be used on the Flutter side. For each new PspdfPlatformView we create, we also create a unique FlutterMethodChannel with the viewId so that we can send data back and forth from the Flutter side.

PspdfkitPlugin.m is an implementation of Flutter’s FlutterPlugin protocol, and it manages a single global PSPDFViewController that can be used to modally show one PDF file at a time. The plugin takes care of implementing these methods:

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar;

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;

The methods above are used to register the view factory and the global FlutterMethodChannel, the latter of which is used to send asynchronous method calls between Flutter and the platform code.

Now that we’ve covered the basics of the Dart and platform components used in the Flutter widget, let’s get started with the tutorial!

Use Case 1 — Adding a Delete Button

We’ll add a custom delete button to the annotation toolbar, and this button will remove all annotations from the current document. In the video below, you can see it in action.

Step 1 — Get the Objective-C Code for the Custom Delete Button

We created a branch in Flutter repository for this tutorial. You’ll need to add two Objective-C files from that branch to the ios directory of your Flutter project:

If you’re following along using the PSPDFKit for Flutter example project, add these two files from the branch to the ios/Classes folder in your local copy of the repository.

Step 2 — Modify the Objective-C Code in the iOS Folder

For this use case, modify both the PspdfPlatformView.m class, which is the Objective-C counterpart of the Flutter platform view component, and the PspdfkitPlugin.m file, which is the Objective-C counterpart of the Pspdfkit global plugin in Flutter.

find-objc-code-to-update

Changes to PspdfPlatformView.m

Import the custom delete button at the top of the file:

// ...
// These lines are already present in the file:
#import "PspdfPlatformView.h"
#import "PspdfkitFlutterHelper.h"
#import "PspdfkitFlutterConverter.h"

// Import the custom toolbar here.
#import "PspdfkitCustomButtonAnnotationToolbar.h"

@import PSPDFKit;
@import PSPDFKitUI;
// ...

In the initWithFrame method, override the default annotation toolbar by telling PSPDFKit to use the custom annotation toolbar instead. This is done by updating the PSPDFViewController’s configuration:

- (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args messenger:(NSObject<FlutterBinaryMessenger> *)messenger {
    NSString *name = [NSString stringWithFormat:@"com.pspdfkit.widget.%lld",viewId];
    _platformViewId = viewId;

    // ...

    NSString *documentPath = args[@"document"];
    if (documentPath != nil && [documentPath  isKindOfClass:[NSString class]] && [documentPath length] > 0) {

        // ...

        // These lines are already in the file.
        PSPDFConfiguration *configuration = [PspdfkitFlutterConverter configuration:configurationDictionary isImageDocument:isImageDocument];

        // Update the configuration to override the default class with the custom one.
        configuration = [configuration configurationUpdatedWithBuilder:^(PSPDFConfigurationBuilder * _Nonnull builder) {
            [builder overrideClass:PSPDFAnnotationToolbar.class withClass:PspdfkitCustomButtonAnnotationToolbar.class];
        }];
// ...
}

The custom delete button is now part of the annotation toolbar of the Flutter app! Note that at this point, this customization will only be applied to the PspdfPlatformView component, and not to the global plugin component, as we didn’t make any changes to the global PSPDFViewController in PspdfkitPlugin.m.

If you’re following along with the PSPDFKit for Flutter example project, you can see the custom delete button in action by following these steps:

  • Run the project in the iOS simulator using flutter run.

  • Under PSPDFKit Widget Examples, select the Basic Example.

  • Click the annotation button in the toolbar.

To directly check out the code described in this blog, use this branch on our Flutter repo.

Changes to PspdfkitPlugin.m

The changes here are similar to the changes we already made to PspdfPlatformView.m.

Import the custom delete button at the top of the file:

// ...
// These lines are already present in the file:
#import "PspdfPlatformView.h"
#import "PspdfkitFlutterHelper.h"
#import "PspdfkitFlutterConverter.h"

// Import the custom toolbar here.
#import "PspdfkitCustomButtonAnnotationToolbar.h"

@import PSPDFKit;
@import PSPDFKitUI;
// ...

In the handleMethodCall method, override the default annotation toolbar by telling PSPDFKit to use the custom annotation toolbar instead. This is done by updating the PSPDFViewController’s configuration in the handler for calls to present through the com.pspdfkit.global method channel:

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    if ([@"frameworkVersion" isEqualToString:call.method]) {
        // ...
    }else if ([@"present" isEqualToString:call.method]) {
        // ...
        // These lines are already in the file
        PSPDFConfiguration *configuration = [PspdfkitFlutterConverter configuration:configurationDictionary isImageDocument:isImageDocument];

        // Update the configuration to override the default class with the custom one.
        configuration = [configuration configurationUpdatedWithBuilder:^(PSPDFConfigurationBuilder * _Nonnull builder) {
            [builder overrideClass:PSPDFAnnotationToolbar.class withClass:PspdfkitCustomButtonAnnotationToolbar.class];
        }];
// ...
}

That’s all! The custom delete button is now part of the annotation toolbar that’s used by both the PspdfPlatformView component and the PspdfkitPlugin global plugin.

Use Case 2 — Sending Notifications from iOS to Dart

Sending data from iOS to Flutter is also a common use case. To send data back to the Flutter side from iOS, you can make use of FlutterMethodChannel. If you look at our Flutter example project, you’ll notice we have two callbacks already: pdfViewControllerWillDismiss and pdfViewControllerDidDismiss. However, those callbacks don’t send any data back to the Flutter side. To send some data back, we can use the channel’s arguments. Let’s implement this by modifying our Flutter example (clone or download the Flutter project if you don’t have it already; we’ll make the changes there).

Here, we’ll implement a callback to notify us in the Dart code whenever the user scrolls the document and changes the document’s spread index. In this example, we’ll use PSPDFDocumentViewControllerSpreadIndexDidChangeNotification, a notification provided by PSPDFKit, but we can use any sort of callback here, such as a delegate method. For the same spread index change event, we can also use -documentViewController:didChangeSpreadIndex:, which is a method of PSPDFDocumentViewControllerDelegate.

Here’s a list of changes we’ll need to make to the example:

  1. In PspdfkitPlugin.m, in the handleMethodCall method, insert an observer for the spread index right after calling presentingViewController presentViewController:navigationController animated:YES completion:nil, like so:

[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(spreadIndexDidChange:) name:PSPDFDocumentViewControllerSpreadIndexDidChangeNotification object:nil];

Then, implement the spreadIndexDidChange method at the bottom of the same file, like this:

- (void)spreadIndexDidChange:(NSNotification *)notification {
    long oldPageIndex = [notification.userInfo[@"PSPDFDocumentViewControllerOldSpreadIndexKey"] longValue];
    long currentPageIndex = [notification.userInfo[@"PSPDFDocumentViewControllerSpreadIndexKey"] longValue];
    NSMutableDictionary *pageIndices = @{@"oldPageIndex": @(oldPageIndex), @"currentPageIndex": @(currentPageIndex)};
    [channel invokeMethod:@"spreadIndexDidChange" arguments:pageIndices];
}
  1. In the lib/src/main.dart file of the PSPDFKit Flutter SDK, we need to add another callback function right after pdfViewControllerDidDismiss:

static late void Function(dynamic) spreadIndexDidChange;

Then we add a new case to handle it in the _platformCallHandler function:

case 'spreadIndexDidChange':
    spreadIndexDidChange(call.arguments);
    break;

Here, we’re passing along the call arguments that we’ll use in our example.

  1. Finally, in example/lib/main.dart, we can use these arguments by creating a handler function like this:

void spreadIndexDidChangeHandler(dynamic arguments) {
    print(arguments);
}

The last missing piece is to connect this function we created with the plugin. We can do that in the widget’s build method after we set the other handlers (for will dismiss and did dismiss):

Pspdfkit.spreadIndexDidChange = (dynamic arguments) => spreadIndexDidChangeHandler(arguments);

After editing the example like shown here, run the Flutter example by following the steps in the example. You’ll see that on changing pages, the new and old page index will get printed from the Dart side. You can follow this pattern for different kinds of callbacks and update the plugin to adapt to your use case.

Conclusion

This post covered how to bridge native iOS code to Flutter. We also modified the PSPDFKit Flutter example to send data from the platform side to Flutter. We hope this tutorial helps you with implementing more complex use cases in your app.

If you have any questions about PSPDFKit for Flutter, please don’t hesitate to reach out to us. We’re happy to help.

Related Products
PSPDFKit for Flutter

Product Page
Guides
Example Projects

Share Post
Free 60-Day Trial Try PSPDFKit in your app today.
Free Trial

Related Articles

Explore more
DEVELOPMENT  |  iOS • Swift • Tips

Adding a Document Viewer to a Mac Catalyst App

PRODUCTS  |  iOS • Mac Catalyst • Releases

PSPDFKit 11.5 for iOS Adds Measurement Tools

PRODUCTS  |  iOS • Android • Releases • Components

Measurement Tools — Measure Distance, Perimeter, and Area in PDFs