Flutter学习之嵌入原生iOSView

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情

  • 本文主要介绍Flutter学习之原生客户端交互,主要是在flutter使用iOS中的view。

1. 创建iOS工程View在Flutter中展示

我们想要在flutter使用iOS中的view,应该怎么交互呢。我们使用Xcode打开我们的Runner.xcworkspace

image.png 我们在 iOS工程中创建一个测试view

image.png

import UIKit

import Flutter

class TestView:NSObject, FlutterPlatformView{

    

    lazy var nameLabel: UILabel = {

        let label = UILabel()

        label.font = .systemFont(ofSize: 15)

        label.textColor = .white

//        label.backgroundColor = .red

        label.textAlignment = .center

        return label

    }()

    

    init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger) {

        super.init()

        nameLabel.text = "我是 iOS Test View"

      }

    

    required init?(coder: NSCoder) {

        fatalError("init(coder:) has not been implemented")

    }

    

    func view() -> UIView {

        

        return nameLabel

    }

    

}

同时创建TestViewFactory

import UIKit

import Flutter

class TestViewFactory:NSObject, FlutterPlatformViewFactory {

    

    var messenger: FlutterBinaryMessenger

    init(messenger:FlutterBinaryMessenger) {

          self.messenger = messenger

          super.init()

      }

    

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

           return TestView(frame,viewID: viewId,args: args,messenger: messenger)

       }

       

       func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {

           return FlutterStandardMessageCodec.sharedInstance()

       }

      

}

实现FlutterPlatformViewFactory的协议方法,返回我们的TestView对象,通过实现协议方法进行交互。

import UIKit

import Flutter

class TestViewFactory:NSObject, FlutterPlatformViewFactory {

    

    var messenger: FlutterBinaryMessenger

    init(messenger:FlutterBinaryMessenger) {

          self.messenger = messenger

          super.init()

      }

    

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

           return TestView(frame,viewID: viewId,args: args,messenger: messenger)

       }

       

       func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {

           return FlutterStandardMessageCodec.sharedInstance()

       }

      

}

我们在delegate中进行注册,进行通信。

import UIKit

import Flutter


@UIApplicationMain

@objc class AppDelegate: FlutterAppDelegate {

  override func application(

    _ application: UIApplication,

    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?

  ) -> Bool {

    GeneratedPluginRegistrant.register(with: self)

    

    let registrar:FlutterPluginRegistrar = self.registrar(forPlugin: "plugins.flutter.io/custom_platform_view_plugin")!

    let factory = TestViewFactory(messenger: registrar.messenger())

    registrar.register(factory, withId: "plugins.flutter.io/custom_platform_view")

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)

  }

}

我们在flutter项目中创建一个展示的页面


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

class IosViewPage extends StatelessWidget {
  const IosViewPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    String? title = Get.arguments['title'];

    return Scaffold(

      appBar: AppBar(title: Text(title ?? ''),),
      body: Center(
        child:iosView(),
      ),
    );
  }
  Widget iosView() {
     if(defaultTargetPlatform == TargetPlatform.iOS){
      return  const UiKitView(
        viewType: 'plugins.flutter.io/custom_platform_view',
        creationParams: {'text': 'Flutter传给IOSTextView的参数'},
        creationParamsCodec: StandardMessageCodec(),
      );
    }else {
       return Container();
     }
  }
}

显示效果如下:

image.png

2. Flutter向ios传值

我们在flutter页面中传一些参数

return  const UiKitView(
  viewType: 'plugins.flutter.io/custom_platform_view',
  creationParams: {'text': 'Flutter传给IOSLabel的参数'},
  creationParamsCodec: StandardMessageCodec(),
);

这里viewType就是我们注册的时候使用的标识符creationParams为创建iOS view时带的参数creationParamsCodec:将 creationParams 编码后再发送给平台侧,它应该与传递给构造函数的编解码器匹配
我们在iOS项目中判断是否传递了参数

init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger) {

        super.init()

        if args is NSDictionary {

            let dict = args as! NSDictionary

            nameLabel.text  = dict.value(forKey: "text") as? String

            

        }else{

            nameLabel.text = "这是一个 iOS view"

        }

        

      }

运行结果

image.png

我们在运行中通过按钮点击改变iOS View的内容

static const platform = MethodChannel('com.flutter.test.TestView');

我们在flutter页面定义一个方法通道

 RaisedButton(
child: const Text('传递参数给原生View'),
onPressed: () {
  platform.invokeMethod('userInfo', {'name': 'Jack', 'city': "New York"});
},

点击的时候传参给iOS页面

let method = FlutterMethodChannel(name: "com.flutter.test.TestView", binaryMessenger: messenger)

        

        method.setMethodCallHandler {  (call, reslut) in

            if (call.method == "userInfo") {

                let dict: Dictionary? = call.arguments as? Dictionary<String, Any>


                self.nameLabel.text = "my Name is:\(dict?["name"] ?? "")\n from:\(dict?["city"] ?? "")"

                

            }

        }

在页面中设置回调处理,判断方法获取参数,进行展示 image.png

设置回调的时候还有一个result没有使用,这个是用于我们原生页面向flutter传参使用的。


        let method = FlutterMethodChannel(name: "com.flutter.test.TestView", binaryMessenger: messenger)

        

        method.setMethodCallHandler {  (call, reslut) in

            if (call.method == "userInfo") {

                let dict: Dictionary? = call.arguments as? Dictionary<String, Any>


                self.nameLabel.text = "my Name is:\(dict?["name"] ?? "")\n from:\(dict?["city"] ?? "")"

                

            }else if (call.method == "callBack") {

                

                reslut(["title": self.nameLabel.text])

                

            }

        }

这里我们使用reslut进行回传,在flutter中页面进行调用

RaisedButton(
  child:  Text(callbackData),
  onPressed: () async {
  var result =  await IosViewPage.platform.invokeMethod('callBack');
  setState((){
    callbackData = '${result['title']}';
  });

  },
),

我们进行回调显示我们iOS中view的label文字到按钮上

image.png

3. 多个原生View通信冲突问题

当我们的页面有多个原生view,他们的通信怎么区分

image.png

当我们点击的时候发现只有最好一个view发生改变,我们如何改变。可以从2个方面,一个让这个方法变成唯一的。将一个唯一 id 通过初始化参数传递给原生 View,原生 View使用这个id 构建不同名称的 MethodChannel。或者是通过viewID,原生 View 生成时,系统会为其生成唯一id:viewId,使用 viewId 构建不同名称的 MethodChannel

let method = FlutterMethodChannel(name: "com.flutter.test.TestView\(viewID)", binaryMessenger: messenger)

我们在原生iOS的view中FlutterMethodChannel的name拼接viewID

var iosViews = [];

return   UiKitView(
  viewType: 'plugins.flutter.io/custom_platform_view',
  creationParams: const {'text': 'Flutter传给IOSLabel的参数'},//初始值
  creationParamsCodec: const StandardMessageCodec(),
  onPlatformViewCreated: (viewID) => iosViews.add(MethodChannel('com.flutter.test.TestView$viewID')),
);

使用的时候取出对应的方法即可

image.png

最终结果

image.png

4. 小结

Flutter中嵌入原生iOS视图,主要通过flutter中的UiKitView进行获取原生工程APPDelegate中注册的FlutterPlatformView类型从而获取加载到flutter页面。flutter和原生页面交互通过FlutterMethodChannel,根据一些key, method方法等进行交互回调函数的相互回调实现通信

猜你喜欢

转载自juejin.im/post/7112373752628772878