Currently there is no TextView for Flutter, so we have to have an injection native TextView from iOS & Android to Flutter project.
PlaformView & PlaformChannel
https://flutter.dev/docs/development/platform-integration/platform-channels
- This is an API from Flutter to create Widgets from Native Views!
- And the way how Dart & iOS/Android communicate to each order
Implementation
In Flutter project
Create a Widget call TextView in Flutter side
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);
}
}
We also can having more methods same as setHtmlText
to setting up more data for the TextView from native side
Caller from Flutter
TextView(onTextViewCreated: (TextViewController controller) {
controller.setHtmlText(text: "<html><body>Hello World</body></html>");
})
In iOS project
Setting preview
Open the Info.plist, adding key io.flutter.embedded_views_preview
with value is true
to allow Flutter previewing on the UIView.
<key>io.flutter.embedded_views_preview</key>
<true/>
Create the TextView compatible with FlutterPlatformView
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
}
}
}
Create an UITextView Factory
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()
}
}
Setting Flutter engine
This setup for the iOS project without the FlutterAppDelegate
1: Go to the swift file that you wanna present the Flutter app
2: Importing
import Flutter
import FlutterPluginRegistrant
3: Store the Flutter engine, make sure this variable is global variable in your class
let flutterEngine = FlutterEngine(name: "this_is_my_flutter_app")
- name: Just indicated the name for the engine
4: Start & register the engine
flutterEngine.run()
GeneratedPluginRegistrant.register(with: flutterEngine)
More details about the Flutter Engine in iOS (https://flutter.dev/docs/development/add-to-app/ios/add-flutter-screen?tab=engine-swift-tab)
5: Register the FlutterUITextView to the Flutter engine
let registrar = flutterEngine.registrar(forPlugin: "FlutterUITextView")!
let viewFactory = FlutterUITextViewViewFactory(messenger: registrar.messenger())
registrar.register(viewFactory, withId: "FlutterUITextView")
- The param
withId
("FlutterUITextView"
) should be matched with theviewType
that we defined withUiTextView
’sviewType
in Flutter code.
6: Present the Flutter app
let flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
parent?.present(flutterVC, animated: true, completion: nil)