Some test text!
iOS / Guides / Add an API
The React Native API for Apryse Mobile SDK includes all of the most used functions and methods for viewing, annotating and saving PDF documents. However, it is possible your app may need access to APIs that are available as part of the native API, but are not directly available to React Native.
This guide provides an example of how to add the following to the React Native interface:
pageChangeOnTap
prop that determines whether page will turn left or right when tapping corresponding edges.getField
function which retrieves information about a field using its name.onLayoutChanged
event listener that is raised when the layout of viewer has changed.You can follow the same pattern to add new functions and props that your React Native app may need. The new additions could be simple ones, which expose one piece of functionality, or custom ones, that expose a series of native commands under the hood.
Prior to following this guide, we highly recommend you to go through the official guide here: Native UI Components to have a better understanding of the system.
The source is hosted on GitHub here: https://github.com/ApryseSDK/pdftron-react-native
Fork the project and clone a copy of the repository to your disk.
pageChangeOnTap
propThe DocumentView.tsx
interface file lists all of the React Native props on the DocumentView component.
Add the prop declaration:
static propTypes = {
pageChangeOnTap: PropTypes.bool,
};
For props that accept Config
constants or arrays of Config
constants, use the oneOf
and arrayOf
helper methods. These helpers return flexible types that allow custom checks for TypeScript users, while maintaining standard run-time checks for all users.
Open file ios/RNTPTDocumentViewManager.m
.
Add the method key that matches the TS declaration:
RCT_CUSTOM_VIEW_PROPERTY(pageChangeOnTap, BOOL, RNTPTDocumentView)
{
if (json) {
view.pageChangeOnTap = [RCTConvert BOOL:json];
}
}
Open the ios/RNTPTDocumentView.h
interface file and add a new property to the RNTPTDocumentView
class:
@interface RNTPTDocumentView
...
@property (nonatomic, assign) BOOL pageChangeOnTap;
...
@end
Open the ios/RNTPTDocumentView.m
implementation file, and add the property setter:
- (void)setPageChangeOnTap:(BOOL)pageChangeOnTap
{
_pageChangeOnTap = pageChangeOnTap;
if (self.documentViewController) {
[self applyViewerSettings];
}
}
In this case, we call the applyViewerSettings
method and access the changesPageOnTap
property to complete our implementation:
- (void)applyViewerSettings
{
...
// Page change on tap.
self.documentViewController.changesPageOnTap = self.pageChangeOnTap;
...
}
The actual implementation will depend on the actual functionality.
getField
functionThe DocumentView.tsx
interface file lists all of the React Native functions on the DocumentView component.
Add the function declaration:
getField = (fieldName: string): Promise<void | {fieldName: string, fieldValue?: any, fieldType?: string}> => {
const tag = findNodeHandle(this._viewerRef);
if(tag != null) {
return DocumentViewManager.getField(tag, fieldName);
}
return Promise.resolve();
}
All functions return Promise<void | T>
where T
is the expected return type upon success. If the function returns void
upon success, simply put Promise<void>
.
While you are adding types, consider representing objects with reusable object types. You can use existing ones, or create a new type alias or interface in AnnotOptions.ts
(see Object Types).
The JS name of the new method is getField
and the native method is getFieldForDocumentViewTag:fieldname:
.
In ios/RNTPTDocumentViewModule.m
, use the RCT_REMAP_METHOD
macro to expose the native method to JS and simultaneously specify the JS name of the method.
RCT_REMAP_METHOD(getField,
getFieldForDocumentViewTag:(nonnull NSNumber *)tag
fieldName:(NSString *)fieldName
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
@try {
NSDictionary *field = [[self documentViewManager] getFieldForDocumentViewTag:tag fieldName:fieldName];
resolve(field);
}
@catch (NSException *exception) {
reject(@"set_value_for_fields", @"Failed to set value on fields", [self errorFromException:exception]);
}
}
Add the native method to ios/RNTPTDocumentViewManager.h
:
- (NSDictionary *)getFieldForDocumentViewTag:(NSNumber *)tag fieldName:(NSString *)fieldName;
Implement the native method in ios/RNTPTDocumentViewManager.m
:
- (NSDictionary *)getFieldForDocumentViewTag:(NSNumber *)tag fieldName:(NSString *)fieldName
{
RNTPTDocumentView *documentView = self.documentViews[tag];
if (documentView) {
return [documentView getField:fieldName];
} else {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Unable to get field for tag" userInfo:nil];
return nil;
}
}
Add the following to ios/RNTPTDocumentView.h
:
- (NSDictionary *)getField:(NSString *)fieldName;
Implement the method in ios/RNTPTDocumentView.m
:
- (NSDictionary *)getField:(NSString *)fieldName
{
PTPDFViewCtrl *pdfViewCtrl = self.currentDocumentViewController.pdfViewCtrl;
if (!pdfViewCtrl) {
return nil;
}
NSMutableDictionary <NSString *, NSObject *> *fieldMap = [[NSMutableDictionary alloc] init];
NSError *error;
[pdfViewCtrl DocLockReadWithBlock:^(PTPDFDoc * _Nullable doc) {
PTField *field = [doc GetField:fieldName];
if (field && [field IsValid]) {
PTFieldType fieldType = [field GetType];
NSString* typeString;
if (fieldType == e_ptbutton) {
typeString = PTFieldTypeButtonKey;
} else if (fieldType == e_ptcheck) {
typeString = PTFieldTypeCheckboxKey;
[fieldMap setValue:[[NSNumber alloc] initWithBool:[field GetValueAsBool]] forKey:PTFormFieldValueKey];
} else if (fieldType == e_ptradio) {
typeString = PTFieldTypeRadioKey;
[fieldMap setValue:[field GetValueAsString] forKey:PTFormFieldValueKey];
} else if (fieldType == e_pttext) {
typeString = PTFieldTypeTextKey;
[fieldMap setValue:[field GetValueAsString] forKey:PTFormFieldValueKey];
} else if (fieldType == e_ptchoice) {
typeString = PTFieldTypeChoiceKey;
[fieldMap setValue:[field GetValueAsString] forKey:PTFormFieldValueKey];
} else if (fieldType == e_ptsignature) {
typeString = PTFieldTypeSignatureKey;
} else {
typeString = PTFieldTypeUnknownKey;
}
[fieldMap setValue:typeString forKey:PTFormFieldTypeKey];
[fieldMap setValue:fieldName forKey:PTFormFieldNameKey];
}
} error:&error];
if (error) {
NSLog(@"Error: There was an error while trying to get field. %@", error.localizedDescription);
}
return [[fieldMap allKeys] count] == 0 ? nil : fieldMap;
}
The logic is to get the PDF view controller and if the field names are valid, use the controller to get the fields from the current document.
onLayoutChanged
event listenerEvent listeners are handled in DocumentView.tsx
. Add the following to the file:
static propTypes = {
...
onLayoutChanged: func<() => void>(),
}
onChange = (event) => {
if (event.nativeEvent.onLeadingNavButtonPressed) {
...
} else if (event.nativeEvent.onLayoutChanged) {
if (this.props.onLayoutChanged) {
this.props.onLayoutChanged();
}
}
}
The onLayoutChanged
event listener does not require any arguments. If your event listener requires arguments, access them using event.nativeEvent.exampleArg
.
For event listeners and other props that accept functions, use the func
helper method. This helper returns flexible types that allow custom checks for TypeScript users, while maintaining standard run-time checks for all users.
While you are adding types, consider representing objects with reusable object types. You can use existing ones, or create a new type alias or interface in AnnotOptions.ts
(see Object Types).
Add the following protocol method to ios/RNTPTDocumentView.h
:
@protocol RNTPTDocumentViewDelegate <NSObject>
@optional
...
- (void)layoutChanged:(RNTPTDocumentView *)sender;
...
@end
Implement the protocol method in ios/RNTPTDocumentViewManager.m
, under the Events pragma mark. Notice that we call RNTPTDocumentView
's RCTBubblingEventBlock onChange
event handler block, which will call the callback prop in JS.
#pragma mark - Events
...
- (void)layoutChanged:(RNTPTDocumentView *)sender
{
if (sender.onChange) {
sender.onChange(@{
@"onLayoutChanged" : @"onLayoutChanged",
});
}
}
layoutChanged
protocol method in iOSIn step 3, we implemented the protocol method layoutChanged
to send events to JS, but we haven't used it yet.
Add the following protocol method to ios/DocumentViewController/RNTPTDocumentViewController.h
:
@protocol RNTPTDocumentBaseViewControllerDelegate <PTToolManagerDelegate>
@required
...
- (void)rnt_documentViewControllerLayoutDidChange:(PTDocumentBaseViewController *)documentViewController;
...
@end
Implement this protocol method in ios/RNTPTDocumentView.m
, under the RNTPTDocumentViewControllerDelegate pragma mark. This is where we use layoutChanged
.
#pragma mark - <RNTPTDocumentViewControllerDelegate>
...
- (void)rnt_documentViewControllerLayoutDidChange:(PTDocumentBaseViewController *)documentViewController
{
if ([self.delegate respondsToSelector:@selector(layoutChanged:)]) {
[self.delegate layoutChanged:self];
}
}
The PTPDFViewCtrlDelegate
protocol contains the method pdfViewCtrlOnLayoutChanged
. This method allows adopting delegates to respond to the PTPDFViewCtrl
class when page layout has changed.
Implement pdfViewCtrlOnLayoutChanged
in the following files:
ios/DocumentViewController/RNTPTDocumentViewController.m
ios/DocumentViewController/RNTPTDocumentController.m
ios/DocumentViewController/RNTPTCollaborationDocumentViewController.m
ios/DocumentViewController/RNTPTCollaborationDocumentController.m
#pragma mark - <PTPDFViewCtrlDelegate>
...
- (void)pdfViewCtrlOnLayoutChanged:(PTPDFViewCtrl *)pdfViewCtrl
{
if ([self.delegate respondsToSelector:@selector(rnt_documentViewControllerLayoutDidChange:)]) {
[self.delegate rnt_documentViewControllerLayoutDidChange:self];
}
}
The actual implementation will depend on the actual functionality.
Now npm install
your forked version to your application.
The new functionality is now ready to use.
The app can now access the new API as follows:
onDocumentLoaded = () => {
this._viewer.getField('someFieldName').then((field) => {
if (field !== undefined) {
console.log('field name:', field.fieldName);
console.log('field value:', field.fieldValue);
console.log('field type:', field.fieldType);
}
});
}
onLayoutChanged = () => {
console.log("Layout changed");
}
<DocumentView
ref={(c) => this._viewer = c}
pageChangeOnTap={false}
onDocumentLoaded={this.onDocumentLoaded}
onLayoutChanged={this.onLayoutChanged}
document={this.state.document}
/>
If you're only developing for iOS, then you're all done!
If you're also deploying on Android, you'll need to complete the necessary steps for Android.
If you're developing for both iOS and Android, please consider submitting a PR, as upstreaming the change will simplify your developing and make the APIs available for other Apryse customers.
Trial setup questions? Ask experts on Discord
Need other help? Contact Support
Pricing or product questions? Contact Sales