跳到主要内容

Fabric 原生组件:iOS

现在是时候编写一些 iOS 平台代码,以便能够渲染 web view 了。你需要遵循的步骤如下:

  • 运行 Codegen。
  • 编写 RCTWebView 的代码
  • 在应用程序中注册 RCTWebView

1. 运行 Codegen

你可以手动运行 Codegen,不过更简单的做法是在你将要演示该组件的应用程序中直接执行它。

cd ios
bundle install
bundle exec pod install

重要的是,你会看到来自 Codegen 的日志输出,我们将会在 Xcode 中使用这些输出来构建我们的 WebView 原生组件。

注意

你应该小心不要将生成的代码提交到仓库中。生成的代码是针对每个 React Native 版本特定的。使用 npm peerDependencies 来限制与某个 React Native 版本的兼容性。

3. 编写 RCTWebView

我们需要通过完成以下 5 个步骤 来使用 Xcode 准备你的 iOS 项目:

  1. 打开 CocoaPods 生成的 Xcode Workspace:
cd ios
open Demo.xcworkspace
打开 Xcode Workspace
  1. 右键点击 app 并选择 New Group,将新组命名为 WebView
右键点击 app 并选择 New Group
  1. WebView 组中,创建 NewFile from Template
使用 Cocoa Touch Class 模板创建一个新文件
  1. 使用 Objective-C File 模板,并将其命名为 RCTWebView
创建一个 Objective-C RCTWebView 类
  1. 重复第 4 步,创建一个名为 RCTWebView.h 的头文件。

  2. RCTWebView.m 重命名为 RCTWebView.mm,使其成为一个 Objective-C++ 文件。

Demo/ios
Podfile
...
Demo
├── AppDelegate.swift
...
├── RCTWebView.h
└── RCTWebView.mm

创建好头文件和实现文件后,你就可以开始实现它们了。

下面是 RCTWebView.h 文件的代码,它声明了组件接口。

Demo/RCTWebView/RCTWebView.h
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface RCTWebView : RCTViewComponentView

// 你可以在这里声明想要从视图中访问的原生方法

@end

NS_ASSUME_NONNULL_END

这个类定义了一个 RCTWebView,它继承自 RCTViewComponentView 类。这是所有原生组件的基类,由 React Native 提供。

实现文件(RCTWebView.mm)的代码如下:

Demo/RCTWebView/RCTWebView.mm
#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>
#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]) {
_webView = [WKWebView new];
_webView.navigationDelegate = self;
[self addSubview:_webView];
}
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);

// 在这里处理你的 props
if (oldViewProps.sourceURL != newViewProps.sourceURL) {
NSString *urlString = [NSString stringWithCString:newViewProps.sourceURL.c_str() encoding:NSUTF8StringEncoding];
_sourceURL = [NSURL URLWithString:urlString];
if ([self urlIsValid:newViewProps.sourceURL]) {
[_webView loadRequest:[NSURLRequest requestWithURL:_sourceURL]];
}
}

[super updateProps:props oldProps:oldProps];
}

-(void)layoutSubviews
{
[super layoutSubviews];
_webView.frame = self.bounds;

}

#pragma mark - WKNavigationDelegate

-(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;
}

// 事件发射器便利方法
- (const CustomWebViewEventEmitter &)eventEmitter
{
return static_cast<const CustomWebViewEventEmitter &>(*_eventEmitter);
}

+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>();
}

@end

这段代码使用 Objective-C++ 编写,包含各种细节:

  • @interface 实现了两个协议:
    • RCTCustomWebViewViewProtocol,由 Codegen 生成;
    • WKNavigationDelegate,由 WebKit 框架提供,用于处理 web view 导航事件;
  • init 方法用于实例化 WKWebView,将其添加到子视图中,并设置 navigationDelegate
  • updateProps 方法在组件的 props 发生变化时由 React Native 调用;
  • layoutSubviews 方法描述自定义视图需要如何布局;
  • webView:didFinishNavigation: 方法让你在 WKWebView 完成页面加载时处理相应逻辑;
  • urlIsValid:(std::string)propString 方法检查作为 prop 接收的 URL 是否有效;
  • eventEmitter 方法是一个用于获取强类型 eventEmitter 实例的工具方法
  • componentDescriptorProvider 返回由 Codegen 生成的 ComponentDescriptor

添加 WebKit 框架

备注

只有在我们创建 Web view 时才需要这一步。iOS 上的 Web 组件需要链接到 Apple 提供的 WebKit 框架。如果你的组件不需要访问 web 特定功能,可以跳过这一步。

Web view 需要访问 Apple 通过 Xcode 和设备自带的某个框架提供的一些功能:WebKit。 你可以在原生代码中通过在 RCTWebView.mm 中添加的 #import <WebKit/WebKit.h> 这一行看到它。

要在你的应用中链接 WebKit 框架,请按照以下步骤操作:

  1. 在 Xcode 中,点击你的项目
  2. 选择 app target
  3. 选择 General 选项卡
  4. 向下滚动直到找到 "Frameworks, Libraries, and Embedded Contents" 部分,然后点击 + 按钮
将 webkit 框架添加到你的应用 1
  1. 在搜索栏中筛选 WebKit
  2. 选择 WebKit framework
  3. 点击 Add。
将 webkit 框架添加到你的应用 2