Building a Custom iOS WebView and Bridging it to React Native: A Step-by-Step Guide
Dinuka Fernando
Associate Technical Lead at Axiata Digital Labs| Speaker | Blogger
A few weeks ago, I attempted to submit my React Native-based iOS application to the App Store. Unfortunately, the submission was rejected, and the primary reason cited was the WebView I had incorporated in my project. The application featured HTML5 games, which functioned flawlessly for others, but this particular submission failed to comply with Apple’s guidelines, leading to the rejection.
Following discussions with Apple Developer Support on this matter, it was conveyed that while React Native WebView is suitable for loading other websites, it is not recommended for running HTML5 games within the WebView. Their suggestion was to integrate WKWebView into my project for a more suitable solution.
Having engaged in discussions with Apple Developer Support, it became evident that using React Native WebView to load HTML5 games wasn’t in line with Apple’s guidelines. To address this, I took the necessary steps to implement a custom native WebView and integrate it into my React Native project. In this guide, I’ll detail the process of implementing the custom WebView, sharing insights on how this approach successfully resolved the submission issue.
This guide assumes you have a basic understanding of React Native development and assumes you’ve set up a new React Native project. Make sure to handle any additional configurations specific to your project’s requirements.
Step 1: Create the Native Module
Access your React Native project in Xcode and proceed to the project directory. Subsequently, generate the specified file within the designated location.
1.1. Create a new file named WebViewModule.h and add the following code:
// WebViewModule.h
#import <React/RCTBridgeModule.h>
@interface WebViewModule : NSObject <RCTBridgeModule>
@end
1.2 Create a new file named WebViewModule.m and add following code.
#import "WebViewModule.h"
#import <WebKit/WebKit.h>
#import <AVFoundation/AVFoundation.h>
@implementation WebViewModule
WKWebView *webView;
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(openWebView:(NSString *)url enableScrolling:(BOOL)enableScrolling enableFullScreen:(BOOL)enableFullScreen) {
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController *rootViewController = UIApplication.sharedApplication.delegate.window.rootViewController;
CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
CGFloat navBarHeight = rootViewController.navigationController.navigationBar.frame.size.height;
CGFloat topBarHeight = statusBarHeight + navBarHeight + rootViewController.view.safeAreaInsets.top;
CGRect webViewFrame;
if (enableFullScreen) {
// Full-screen mode
webViewFrame = rootViewController.view.bounds;
} else {
// Normal mode
webViewFrame = CGRectMake(0, topBarHeight, rootViewController.view.bounds.size.width, rootViewController.view.bounds.size.height - topBarHeight);
}
WKWebViewConfiguration *webConfiguration = [[WKWebViewConfiguration alloc] init];
webConfiguration.allowsInlineMediaPlayback = YES;
// Set up audio handling
if (@available(iOS 14.0, *)) {
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback mode:AVAudioSessionModeMoviePlayback options:AVAudioSessionCategoryOptionMixWithOthers error:nil];
}
webView = [[WKWebView alloc] initWithFrame:webViewFrame configuration:webConfiguration];
// Enable or disable scrolling based on the parameter
webView.scrollView.scrollEnabled = enableScrolling;
// Enable user interaction
webView.userInteractionEnabled = YES;
webView.backgroundColor = [UIColor clearColor];
webView.opaque = NO;
if (@available(iOS 11.0, *)) {
webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
rootViewController.automaticallyAdjustsScrollViewInsets = NO;
}
[rootViewController.view addSubview:webView];
NSURL *webURL = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:webURL];
[webView loadRequest:request];
});
}
RCT_EXPORT_METHOD(closeWebView:(RCTResponseSenderBlock)onClose) {
dispatch_async(dispatch_get_main_queue(), ^{
[webView removeFromSuperview];
webView = nil;
if (onClose) {
onClose(@[]);
}
});
}
RCT_EXPORT_METHOD(reduceWebViewHeightFromTop:(CGFloat)value) {
dispatch_async(dispatch_get_main_queue(), ^{
if (webView) {
CGRect newFrame = webView.frame;
// Calculate the new origin.y to reduce height from the top by the provided value
CGFloat reducedOriginY = newFrame.origin.y + value;
// Update the webView frame with the reduced origin.y
newFrame.origin.y = reducedOriginY;
webView.frame = newFrame;
}
});
}
@end
Step 2: Link Libraries
2.1 Import the necessary frameworks at the top of your AppDelegate.m file
领英推荐
#import "WebViewModule.h"
#import <React/RCTBridge.h>
2.2 Register the native module by adding the following line in the didFinishLaunchingWithOptions method:
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
[bridge moduleForClass:[WebViewModule class]];
Step 3: Implementation in React Native
3.1 Open your React Native project’s and import the WebView module
import { NativeModules } from 'react-native';
const WebViewModule = NativeModules.WebViewModule;
3.2 Use the native module methods as needed in your React Native components:
// Example usage in React Native component
WebViewModule.openWebView('https://reactnative.dev/', true, false);
// To close the WebView
WebViewModule.closeWebView(() => {
console.log('WebView closed');
});
// To reduce WebView height from the top
WebViewModule.reduceWebViewHeightFromTop(50);
3.3 Run Your React Native Project
You have the option to execute the iOS project either through Xcode or by utilizing the following command.
npx react-native run-ios
After implementing these changes, I submitted a new release to the App Store, which went through a successful review and received approval. If you encounter a similar situation, you can adopt the same approach that I took.