| id | fabric-native-components-ios |
|---|---|
| title | Fabric Native Components: iOS |
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import constants from '@site/core/TabsConstants';
Now it's time to write some iOS platform code to be able to render the web view. The steps you need to follow are:
- Run Codegen.
- Write the code for the
RCTWebView - Register the
RCTWebViewin the application
You can manually run the Codegen, however it's simpler to use the application you're going to demo the component in to do this for you.
cd ios
bundle install
bundle exec pod installImportantly you will see logging output from Codegen, which we're going to use in Xcode to build our WebView native component.
:::warning You should be careful about committing generated code to your repository. Generated code is specific to each version of React Native. Use npm peerDependencies to restrict compatibility with version of React Native. :::
We need to prepare your iOS project using Xcode by completing these 5 steps:
- Open the CocoaPods generated Xcode Workspace:
cd ios
open Demo.xcworkspace- Right click on app and select
New Group, call the new groupWebView.
- In the
WebViewgroup, createNew→File from Template.
- Use the
Objective-C Filetemplate, and name itRCTWebView.
-
Repeat step 4 and create a header file named
RCTWebView.h. -
Rename
RCTWebView.m→RCTWebView.mmmaking it an Objective-C++ file.
Podfile
...
Demo
├── AppDelegate.swift
...
// highlight-start
├── RCTWebView.h
└── RCTWebView.mm
// highlight-end
After creating the header file and the implementation file, you can start implementing them.
This is the code for the header file, which declares the component interface.
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTWebView : RCTViewComponentView
// You would declare native methods you'd want to access from the view here
@end
NS_ASSUME_NONNULL_ENDimport React
import UIKit
@objc(RCTWebView)
class RCTWebView: RCTViewComponentView {
// You would declare native methods you'd want to access from the view here
}This class defines an RCTWebView which extends the RCTViewComponentView class. This is the base class for all the native components and it is provided by React Native.
The code for the implementation file is the following:
#import "RCTWebView.h"
#import <react/renderer/components/AppSpec/ComponentDescriptors.h>
#import <react/renderer/components/AppSpec/EventEmitters.h>
#import <react/renderer/components/AppSpec/Props.h>
#import <react/renderer/components/AppSpec/RCTComponentViewHelpers.h>
// highlight-next-line
#import <WebKit/WebKit.h>
using namespace facebook::react;
@interface RCTWebView () <RCTCustomWebViewViewProtocol, WKNavigationDelegate>
@end
@implementation RCTWebView {
NSURL * _sourceURL;
WKWebView * _webView;
}
-(instancetype)init
{
if(self = [super init]) {
// highlight-start
_webView = [WKWebView new];
_webView.navigationDelegate = self;
[self addSubview:_webView];
// highlight-end
}
return self;
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<CustomWebViewProps const>(_props);
const auto &newViewProps = *std::static_pointer_cast<CustomWebViewProps const>(props);
// Handle your props here
if (oldViewProps.sourceURL != newViewProps.sourceURL) {
NSString *urlString = [NSString stringWithCString:newViewProps.sourceURL.c_str() encoding:NSUTF8StringEncoding];
_sourceURL = [NSURL URLWithString:urlString];
// highlight-start
if ([self urlIsValid:newViewProps.sourceURL]) {
[_webView loadRequest:[NSURLRequest requestWithURL:_sourceURL]];
}
// highlight-end
}
[super updateProps:props oldProps:oldProps];
}
-(void)layoutSubviews
{
[super layoutSubviews];
_webView.frame = self.bounds;
}
#pragma mark - WKNavigationDelegate
// highlight-start
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
CustomWebViewEventEmitter::OnScriptLoaded result = CustomWebViewEventEmitter::OnScriptLoaded{CustomWebViewEventEmitter::OnScriptLoadedResult::Success};
self.eventEmitter.onScriptLoaded(result);
}
- (BOOL)urlIsValid:(std::string)propString
{
if (propString.length() > 0 && !_sourceURL) {
CustomWebViewEventEmitter::OnScriptLoaded result = CustomWebViewEventEmitter::OnScriptLoaded{CustomWebViewEventEmitter::OnScriptLoadedResult::Error};
self.eventEmitter.onScriptLoaded(result);
return NO;
}
return YES;
}
// Event emitter convenience method
- (const CustomWebViewEventEmitter &)eventEmitter
{
return static_cast<const CustomWebViewEventEmitter &>(*_eventEmitter);
}
// highlight-end
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>();
}
@endimport React
import WebKit
@objc(RCTWebView)
class RCTWebView: RCTViewComponentView {
private var sourceURL: URL?
private var webView: WKWebView!
override init(frame: CGRect) {
super.init(frame: frame)
setupWebView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupWebView()
}
private func setupWebView() {
// highlight-start
webView = WKWebView()
webView.navigationDelegate = self
addSubview(webView)
// highlight-end
}
override func updateProps(_ props: Props, oldProps: Props) {
guard let oldViewProps = _props as? CustomWebViewProps,
let newViewProps = props as? CustomWebViewProps else {
super.updateProps(props, oldProps: oldProps)
return
}
// Handle your props here
if oldViewProps.sourceURL != newViewProps.sourceURL {
let urlString = String(cString: newViewProps.sourceURL)
sourceURL = URL(string: urlString)
// highlight-start
if urlIsValid(newViewProps.sourceURL) {
if let url = sourceURL {
webView.load(URLRequest(url: url))
}
}
// highlight-end
}
super.updateProps(props, oldProps: oldProps)
}
override func layoutSubviews() {
super.layoutSubviews()
webView.frame = bounds
}
// highlight-start
private func urlIsValid(_ propString: String) -> Bool {
if !propString.isEmpty && sourceURL == nil {
let result = CustomWebViewEventEmitter.OnScriptLoaded(
result: .error
)
eventEmitter.onScriptLoaded(result)
return false
}
return true
}
private var eventEmitter: CustomWebViewEventEmitter {
return _eventEmitter as! CustomWebViewEventEmitter
}
// highlight-end
@objc static func componentDescriptorProvider() -> ComponentDescriptorProvider {
return concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>()
}
}
// MARK: - WKNavigationDelegate
extension RCTWebView: WKNavigationDelegate {
// highlight-start
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let result = CustomWebViewEventEmitter.OnScriptLoaded(
result: .success
)
eventEmitter.onScriptLoaded(result)
}
// highlight-end
}This code is written in Objective-C++ and contains various details:
- the
@interfaceimplements two protocols:RCTCustomWebViewViewProtocol, generated by Codegen;WKNavigationDelegate, provided by the WebKit framework to handle the web view navigation events;
- the
initmethod that instantiates theWKWebView, adds it to the subviews and that sets thenavigationDelegate; - the
updatePropsmethod that is called by React Native when the component's props change; - the
layoutSubviewsmethod that describes how the custom view needs to be laid out; - the
webView:didFinishNavigation:method that lets you handle what to do when theWKWebViewfinishes loading the page; - the
urlIsValid:(std::string)propStringmethod that checks whether the URL received as prop is valid; - the
eventEmittermethod which is a utility to retrieve a strongly typedeventEmitterinstance - the
componentDescriptorProviderwhich returns theComponentDescriptorgenerated by Codegen;
:::note This step is only required because we are creating a Web view. Web components on iOS needs to be linked against the WebKit framework provided by Apple. If your component doesn't need to access web-specific features, you can skip this step. :::
A web view requires access to some features that Apple provides through one of the frameworks shipped with Xcode and the devices: WebKit.
You can see it in the native code by the #import <WebKit/WebKit.h> line added in the RCTWebView.mm.
To link the WebKit framework in your app, follow these steps:
- In Xcode, Click on your project
- Select the app target
- Select the General tab
- Scroll down until you find the "Frameworks, Libraries, and Embedded Contents" section, and press the
+button
- In the search bar, filter for WebKit
- Select the WebKit framework
- Click on Add.





