The .NET Multi-platform App UI (.NET MAUI) is a cross-platform framework for building native mobile and desktop apps with C# and XAML. It is open-source and is the evolution of Xamarin.Forms
. Using .NET MAUI, multi-platform apps can be developed using a single project that can contain platform-specfic code and resources.
Both the Android and iOS components of the Apryse Mobile SDK can be implemented into a .NET MAUI code-base by adding the respective PDFTron .Nuget packages to the project and leveraging the C# code to implement the native views for each platform.
This sample implements a custom DocumentViewerControl
.NET MAUI control that handles displaying the native view on the Android and iOS mobile platforms, depending on the platform targetted by the project.
For Android, the DocumentView2
class from the Apryse PDFTron.Android.Tools NuGet package is utilized.
For iOS, the PTTabbedDocumentViewController
class from the Apryse PDFTron.iOS.Tools NuGet package is utilized.
Android Native View | iOS Native View |
---|---|
![]() |
![]() |
The DocumentViewerControl
control is derived from the View
control, which represents a visual element used to arrange layouts and views on the screen.
The code for this control can be found under Controls\DocumentViewerControl.cs
It provides a public API that is accessed by its handler and control consumers.
This control is designed to provide two public properties:
Uri
: A string value that contains the URI for the document to be loaded in the viewerPassword
: A string value that contains the password (if any) to open the document
The DocumentViewerControl
also has two public methods:
Open()
: Loads the document by invoking the platform-specificOpenDocument()
with the current assigned values forUri
andPassword
Open(string uri, string password)
: Sets the values forUri
andPassword
then callsOpen()
The DocumentViewerHandler
is a partial class whose implementation will depend on which platform is being targetted.
It uses a conditional using
to initialize the platformView
native view for the specific platform.
#if IOS
using PlatformView = DocumentViewer.Platforms.iOS.DocumentViewerNativeView;
#elif ANDROID
using PlatformView = DocumentViewer.Platforms.Android.DocumentViewerNativeView;
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
using PlatformView = System.Object;
#endif
In addition, the handler uses the PropertyMapper
and CommandMapper
classes to connect each member of the DocumentViewerControl
's public API to a generic static Action
.
Each specific platform will then have a partial class that gives the specific implementation of every mapped Action
.
For this sample:
- Main partial handler: Handlers\DocumentViewerHandler.cs
- Android implementation: Handlers\DocumentViewerHandler.Android.cs
- iOS implmentation: Handlers\DocumentViewerHandler.iOS.cs
To ensure the correct loading of partial handler files based on the platform, the project uses filename-based multi-targeting, configured by adding the following XML to the project file, as children of the node.
<!-- Android -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net9.0-android')) != true">
<Compile Remove="**\*.Android.cs" />
<None Include="**\*.Android.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<!-- iOS -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net9.0-ios')) != true">
<Compile Remove="**\*.iOS.cs" />
<None Include="**\*.iOS.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
The PDFNet library is first initialized in Platforms\Android\MainActivity.cs by calling pdftron.PDF.Tools.Utils.AppUtils.InitializePDFNetApplication(this)
in the OnCreate()
method.
In the Platforms\Android\DocumentViewerNativeView.cs, the DocumentView2
is created and added to the View
using the mapped DocumentViewerControl
control properties.
public DocumentViewerNativeView(Context context, DocumentViewerControl documentViewerControl) : base(context)
{
_context = context;
_documentViewerControl = documentViewerControl;
SetBackgroundColor(Color.Black);
// Create a RelativeLayout for sizing the document viewer
RelativeLayout relativeLayout = new RelativeLayout(_context)
{
LayoutParameters = new CoordinatorLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
{
Gravity = (int)GravityFlags.Center
}
};
// Create DocumentView and position it in the RelativeLayout
_documentView = new DocumentView2(context)
{
LayoutParameters = new RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
};
// Add to the layouts
relativeLayout.AddView(_documentView);
AddView(relativeLayout);
}
The Uri
and Password
properties are managed as follows:
public void UpdateUri()
{
if (!string.IsNullOrEmpty(_documentViewerControl.Uri))
{
_documentView.SetDocumentUri(Uri.Parse(_documentViewerControl.Uri));
}
}
public void UpdatePassword()
{
if (!string.IsNullOrEmpty(_documentViewerControl.Password))
{
_documentView.SetPassword(_documentViewerControl.Password);
}
}
The OpenDocument()
command is implemented as follows:
public void OpenDocument()
{
if (string.IsNullOrEmpty(_documentViewerControl.Uri))
{
return;
}
_documentView.SetViewerConfig(GetConfig());
if (Context != null)
_documentView.SetSupportFragmentManager(GetManager(Context));
FragmentManager? GetManager(Context context)
{
FragmentManager? childManager = null;
if (context is FragmentActivity)
{
var activity = context as FragmentActivity;
var manager = activity?.SupportFragmentManager;
var fragments = manager?.Fragments;
if (fragments?.Count > 0)
childManager = fragments[0].ChildFragmentManager;
if (childManager != null)
return childManager;
}
return childManager;
}
ViewerConfig? GetConfig()
{
var toolmanagerBuilder = ToolManagerBuilder.From()?.SetAutoSelect(true);
var builder = new ViewerConfig.Builder();
var config = builder
?.MultiTabEnabled(true)
?.FullscreenModeEnabled(false)
?.UseSupportActionBar(false)
?.ToolManagerBuilder(toolmanagerBuilder)
?.SaveCopyExportPath(this.Context?.FilesDir?.AbsolutePath)
?.Build();
return config;
}
}
The PDFNet library is first initialized in Platforms\iOS\AppDelegate.cs by calling pdftron.PDFNet.Initialize("")
in the FinishedLaunching()
method.
In the Platforms\iOS\DocumentViewerNativeView.cs, the PTTabbedDocumentViewController
is created and added to the View
using the mapped DocumentViewerControl
's properties.
public DocumentViewerNativeView(DocumentViewerControl documentViewerControl)
{
_documentViewerControl = documentViewerControl;
// Create the Mutli-Tab Document View Controller
mTabViewController = new PTTabbedDocumentViewController();
// Add to a navigation controller
UINavigationController navigationController = new UINavigationController(mTabViewController);
// Add the navigation controller to the current view
if (navigationController.View != null)
{
AddSubview(navigationController.View);
// Define the layout for the navigation controller's view
navigationController.View.Frame = Bounds;
navigationController.View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
}
}
The Uri
and Password
properties are managed as follows:
public void UpdateUri()
{
if (string.IsNullOrEmpty(_documentViewerControl.Uri) || _uri.ToString() == _documentViewerControl.Uri)
return;
_uri = new Uri(_documentViewerControl.Uri);
}
public void UpdatePassword()
{
if (_password == _documentViewerControl.Password)
return;
_password = _documentViewerControl.Password;
}
The OpenDocument()
command is implemented as follows:
public void OpenDocument()
{
mTabViewController.OpenDocumentWithURL(_uri, _password);
}
The project implements an Model-View-ViewModel (MVVM) pattern.
The Models\DefaultDocument.cs model encapsulates the default document that is loaded in the app, which can be found here
The main view for the app is Views\DocumentViewerPage.xaml. In addition to defining how the component's layout, the Views\DocumentViewerPage.xaml,cs also calls the DisconnectHandler()
method in the OnPageUnloaded()
handler.
void OnPageUnloaded(object sender, EventArgs e)
{
documentViewer.Handler?.DisconnectHandler();
}
The ViewModels\DocumentViewerViewModel.cs view model connects the default document data to the view and handles any changes to the properties.
The trial of the Apryse Mobile SDK does not require a trial key. A commercial license key is required for use in a production environment. Please contact sales to purchase a commerical key or if you need any other license key assistance.
Open the solution in an IDE like JetBrains Rider or Microsoft Visual Studio then build, deploy, and run the project to an Android device or emulator.
If using an IDE supported on Mac OS (e.g. JetBrains Rider), open the solution then build, deploy, and run the project to the Xcode iOS simulator or a connected iOS device.
Otherwise, if the solution is opened on an IDE running on Windows, a Mac machine needs to be paired to the IDE and an active Apple developer account needs to generate a provisioning profile in order to build, deploy, and run the project to an iOS device connected to the Mac machine or remotely to the Xcode iOS simulator.
Note
We are currently using the
arm64
iOS slice to integrate with the JetBrains Rider emulator. To enable this, we have addedRuntimeIdentifier
conditions to the.csproj
file, which link the emulator asiossimulator-x64
:
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == 'iossimulator-arm64'">iossimulator-x64</RuntimeIdentifier>
<ForceSimulatorX64ArchitectureInIDE>true</ForceSimulatorX64ArchitectureInIDE>
Depending on your development environment, you may need to make additional adjustments to your
.csproj
file to meet your specific goals. Note that this workaround will soon become unnecessary. An upcoming release will include a slice that directly targets the JetBrains Rider emulator forarm64
, streamlining the setup process.
- Apryse Android SDK: DocumentView2
- Apryse iOS SDK: PTTabbedDocumentViewController
- Apryse PDFTron NuGets: PDFTron.Android.Tools
- Apryse PDFTron NuGets: PDFTron.iOS.Tools
- .NET MAUI: Create a custom control using handlers
- .NET MAUI: Configure multi-targeting
- .NET MAUI: Model-View-ViewModel (MVVM)
- .NET MAUI: Set up Android device for debugging
- .NET MAUI: Android Emulator
- .NET MAUI: Pair to Mac for iOS development
- JetBrains Rider: Languages and frameworks: MAUI
- JetBrains Rider: Run and debug MAUI and Xamarin projects on macOS