用于 Flutter 的 iOS UITextView

1,025 阅读2分钟

目前 Flutter 没有 TextView,所以我们必须有一个从 iOS 和 Android 到 Flutter 项目的注入原生 TextView。

平台视图和平台通道

flutter.dev/docs/develo…

  • 这是 Flutter 的一个 API,用于从原生视图创建小部件!
  • 以及DartiOS / Android与每个订单的通信方式

执行

在 Flutter 项目中

在 Flutter 端创建一个 Widget 调用 TextView

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

typedef void TextViewCreatedCallback(TextViewController controller);

class TextView extends StatefulWidget {
  const TextView({
    Key key,
    this.onTextViewCreated,
  }) : super(key: key);

  final TextViewCreatedCallback onTextViewCreated;

  @override
  State<StatefulWidget> createState() => _TextViewState();
}

class _TextViewState extends State<TextView> {
  @override
  Widget build(BuildContext context) {
    if (defaultTargetPlatform == TargetPlatform.android) {
      // The native TextView from AndroidView
    }
    if (defaultTargetPlatform == TargetPlatform.iOS) {
      	// viewType `FlutterUITextView` should be matched when register in iOS side
    	return UiKitView(
        viewType: 'FlutterUITextView',
        onPlatformViewCreated: _onPlatformViewCreated,
      );
    }
    return Text('$defaultTargetPlatform is not yet supported by the text_view plugin');
  }

  void _onPlatformViewCreated(int id) {
    if (widget.onTextViewCreated == null) {
      return;
    }
    widget.onTextViewCreated(new TextViewController._(id));
  }
}

class TextViewController {

  // Open a channel with name is `com.my_app.channel.textview_$id`
  // Make sure the iOS/Android observe on same the name of this channel
  TextViewController._(int id) : _channel = new MethodChannel('com.my_app.channel.textview_$id');

  final MethodChannel _channel;

  // Tell the iOS/Android to set the html text to the UITextView (iOS), TextView (Kotlin) 
  Future<void> setHtmlText({String text}) async {
    assert(text != null);
    Map<String, dynamic> arguments = {
      'text': text,
    };
    return _channel.invokeMethod('setHtmlText', arguments);
  }
}

我们也可以有更多的方法setHtmlText来从本地端为 TextView 设置更多的数据

来自 Flutter 的调用者

TextView(onTextViewCreated: (TextViewController controller) {
	controller.setHtmlText(text: "<html><body>Hello World</body></html>");
})

在 iOS 项目中

设置预览

打开Info.plist,添加 keyio.flutter.embedded_views_preview和 value 是true为了让 Flutter 在 UIView 上预览。

<key>io.flutter.embedded_views_preview</key>
<true/>

创建与 FlutterPlatformView 兼容的 TextView

public class FlutterUITextView: NSObject, FlutterPlatformView {
    let frame: CGRect
    let viewId: Int64
    let arguments: Any?
    let textView: UITextView
    var channel: FlutterMethodChannel?

    public init(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?,
         messenger: FlutterBinaryMessenger) {
        self.frame = frame
        self.viewId = viewId
        self.arguments = args
        self.textView = UITextView()
        self.channel = nil
        super.init()

        // This channel name has to be matched with the the name that we defined in `TextViewController` above
        self.channel = FlutterMethodChannel(name: "com.my_app.channel.textview_\(viewId)", binaryMessenger: messenger)
        channel?.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
            switch call.method {
            case "setHtmlText":
                let args = call.arguments as? [String: Any]
                self?.set(htmlText: (args?["text"] as? String) ?? "")
            default:
                break
            }
        })
    }

    public func view() -> UIView {
        return textView
    }

    private func set(htmlText: String) {
        DispatchQueue.main.async {
            //Rendering HTML in in next cycle cuz html from text is expensive task.
            let format = #"<span style="font-size:%.2fpx;font-family:'-apple-system';font-weight:400;color:#40485A;">%@</span>"#
            let html = String(format: format, 14.0, htmlText)
            let data = html.data(using: .utf8)!
            let attributedText = try! NSAttributedString(data: data,
                                          options: [.documentType: NSAttributedString.DocumentType.html,
                                                    .characterEncoding: String.Encoding.utf8.rawValue],
                                          documentAttributes: nil)
            self.textView.attributedText = attributedText
        }
    }
}

创建一个 UITextView 工厂

public class FlutterUITextViewViewFactory: NSObject, FlutterPlatformViewFactory {
    let messenger: FlutterBinaryMessenger

    public init(messenger: FlutterBinaryMessenger) {
        self.messenger = messenger
    }

    public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
        return FlutterUITextView(withFrame: frame, viewIdentifier: viewId,
                                 arguments: args, messenger: messenger)
    }

    public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
        return FlutterStandardMessageCodec.sharedInstance()
    }
}

设置Flutter引擎

这种设置对于iOS的项目没有FlutterAppDelegate

1: 转到你想要展示 Flutter 应用的 swift 文件

2:进口

import Flutter
import FlutterPluginRegistrant

3:存储Flutter引擎,确保这个变量是你的类中的全局变量

let flutterEngine = FlutterEngine(name: "this_is_my_flutter_app")

  • 名称:只显示引擎的名称

4:启动并注册引擎

flutterEngine.run()
GeneratedPluginRegistrant.register(with: flutterEngine)

有关 iOS 中 Flutter 引擎的更多详细信息 ( flutter.dev/docs/develo… )

5:将 FlutterUITextView 注册到 Flutter 引擎

let registrar = flutterEngine.registrar(forPlugin: "FlutterUITextView")!
let viewFactory = FlutterUITextViewViewFactory(messenger: registrar.messenger())
registrar.register(viewFactory, withId: "FlutterUITextView")

  • param withId"FlutterUITextView") 应该与viewType我们在 Flutter 代码中定义的UiTextView's匹配viewType

6:展示 Flutter 应用

let flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
parent?.present(flutterVC, animated: true, completion: nil)

image.png

image.png