flutter原生插件开发--IOS端

简介

做Flutter开发肯定会需要自定义插件,下面我把自己开发的插件的Demo分享出来,希望可以对新手小白有点帮助。

准备工作

需要一台Mac设备,安装Android Stodio、Xcode,及搭建Flutter、CocoPods环境

切入主题

IOS 插件 -- OC篇

首先打开Android Stodio,创建Flutter Project项目 选择Flutter Plugin

一路Next,这里选中OCk开发

 

 这里需要打开你的项目目录.以我的目录结构为例

新创建的iOS代码是不包含 pod文件夹的,需要确保插件代码通过example工程被构建过一次(这很重要),可以在终端执行以下命令进行构建安装需要的依赖包

//首先执行 (这里为以上目录路径)

cd flutter_plugin_demo/example/ios 

//然后执行(这里默认已经安装好Xcode和CocoPods)

pod install 

Dart区代码

这是项目中初始代码

现在我们对他进行一下简化修改

/// NOTE:通过字符串test_plugin_demo约定MethodChannel
/// NOTE:这里主要负责和原生进行交互,调用原生的方法和监听原生方法,
class TestPluginDemo {
  MethodChannel _channel;
  TestPluginDemo.init(int id){
    _channel = new MethodChannel('test_plugin_demo');
  }
}

 下面我们来创建一个Dart类显示加载原生的视图

///我是使用的 StatefulWidget 使用StatelessWidget 也是一样
class TestView extends StatefulWidget {
  ///根据自己的需求创建初始化参数
  final TestViewCreatedCallback onCreated; ///是上面创建的回调
  final String titleStr;

  TestView({
    Key key,
    this.onCreated,
    this.titleStr,
  });

  @override
  _TestViewState createState() => _TestViewState();
}

class _TestViewState extends State<TestView> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: _loadNativeView(),
    );
  }
  ///加载原生视图
  Widget _loadNativeView(){
    ///根据不同的平台显示相应的视图
    if(Platform.isAndroid){ ///加载安卓原生视图
      return AndroidView(
        viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
        onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
        creationParams: <String, dynamic>{ ///给原生传递初始化参数 就是上面定义的初始化参数
          'titleStr':widget.titleStr,
        },
        /// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
        /// 如果存在 creationParams,则该值不能为null
        creationParamsCodec: const StandardMessageCodec(),
      );
    }else if(Platform.isIOS){///加载iOS原生视图
      return UiKitView(
        viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
        onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
        creationParams: <String, dynamic>{ ///给原生传递初始化参数 就是上面定义的初始化参数
          'titleStr':widget.titleStr,
        },
        /// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
        /// 如果存在 creationParams,则该值不能为null
        creationParamsCodec: const StandardMessageCodec(),
      );
    }else{
      return Text('这个平台老子不支持');
    }
  }
  ///我也是刚学Flutter 所以我理解的:这个基本上是固定写法
  Future<void> onPlatformViewCreated(id) async {
    if (widget.onCreated == null) {
      return;
    }
    widget.onCreated(new TestFlutterPluginDemo.init(id));
  }
}

然后在main.dart里面加载新增的页面

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  ///定义一个测试类的属性 用来调用原生方法 和原生交互
  var testPluginDemo;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    ///初始化 测试视图的类
    TestView testView = new TestView(
      onCreated: onTestViewCreated,
    );

    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(
            title: const Text('flutter原生调用'),
          ),
          body: Column(
            children: <Widget>[
              Container(
                height: 400,
                width: 400,
                child: testView,///使用原生视图
              )
            ],
          )
      ),
    );
  }
  void onTestViewCreated(TestPluginDemo){
    this.testPluginDemo = TestPluginDemo;
  }
}

到这里Dart相关代码就写完了。接下来到原生部分OC

打开Flutter项目根目录下的ios文件夹,双击Runner.xcworkspace打开原生项目,打开原生代码查看图1是否存在,没有就去如图2目录把Podfile.lock删除,如果不存在Podfile.lock

去终端执行下列操作

//首先执行 (这里为以上目录路径)

cd flutter_plugin_demo/example/ios 

//然后执行(这里默认已经安装好Xcode和CocoPods)

pod install 

 这里发现有两个文件 GeneratedPluginRegistrant.h 和 GeneratedPluginRegistrant.m,

在pod /Development Pods 里面找到Classes文件夹里的这两个文件 开发, 新建类也要在这个目录里面,

在原生端需要做的事情

原生端只需要做以下两件事:

  1. 创建视图工厂对象,并在工厂方法中返回需要的原生视图对象。
  2. 在AppDelegate对象的App启动方法中,使用viewType属性值注册视图工厂对象。

打开Flutter项目根目录下的ios文件夹,双击Runner.xcworkspace打开原生项目。创建一个新的视图工厂类TestPluginViewFactory.h,其需要继承自NSObject类,并遵从FlutterPlatformViewFactory协议,代码如下:

TestPluginViewFactory.h 

#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN

@interface TestPluginViewFactory : NSObject<FlutterPlatformViewFactory>
/// 重写一个构造方法 来接收 Flutter 相关蚕食
/// @param messenger Flutter类 包含回调方法等信息
-(instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
@end

NS_ASSUME_NONNULL_END

 TestPluginViewFactory.m

#import "TestPluginViewFactory.h"
#import "TestPluginView.h"
@interface TestPluginViewFactory ()

@property(nonatomic)NSObject<FlutterBinaryMessenger>* messenger;

@end

@implementation TestPluginViewFactory

- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
    self = [super init];
    if (self) {
        self.messenger = messenger;
    }
    return self;
}

#pragma mark -- 实现FlutterPlatformViewFactory 的代理方法
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
    return [FlutterStandardMessageCodec sharedInstance];
}

/// FlutterPlatformViewFactory 代理方法 返回过去一个类来布局 原生视图
/// @param frame frame
/// @param viewId view的id
/// @param args 初始化的参数
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args{
    
    TestPluginView *testPluginView = [[TestPluginView alloc] initWithFrame:frame viewId:viewId args:args messager:self.messenger];
    return testPluginView;
    
}

@end

FlutterPlatformViewFactory协议中的工厂方法createWithFrame:viewIdentifier:arguments:是一个必须实现的方法,该方法生成一个遵从FlutterPlatformView协议的对象并返回。FlutterPlatformView协议用于将原生视图嵌入到Flutter的widget树中,代码如下:

TestPluginView.h

#import <Foundation/Foundation.h>
#include <Flutter/Flutter.h>


@interface TestPluginView : NSObject<FlutterPlatformView>
- (id)initWithFrame:(CGRect)frame
             viewId:(int64_t)viewId
               args:(id)args
           messager:(NSObject<FlutterBinaryMessenger>*)messenger;
@end

TestPluginView.m

#import "TestPluginView.h"

@interface TestPluginView ()
/** channel*/
@property (nonatomic, strong) FlutterMethodChannel  *channel;
@property (nonatomic, strong) UIButton *button;
@end

@implementation TestPluginView
{
    CGRect _frame;
    int64_t _viewId;
    id _args;
   
}

- (id)initWithFrame:(CGRect)frame
  viewId:(int64_t)viewId
    args:(id)args
messager:(NSObject<FlutterBinaryMessenger>*)messenger
{
    if (self = [super init])
    {
        _frame = frame;
        _viewId = viewId;
        _args = args;
        
        ///建立通信通道 用来 监听Flutter 的调用和 调用Fluttter 方法 这里的名称要和Flutter 端保持一致
        _channel = [FlutterMethodChannel methodChannelWithName:@"test_plugin_demo" binaryMessenger:messenger];
        
        __weak __typeof__(self) weakSelf = self;

        [_channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
            [weakSelf onMethodCall:call result:result];
        }];
       
    }
    return self;
}

- (UIView *)view{
    
    UIView *nativeView = [[UIView alloc] initWithFrame:_frame];
    nativeView.backgroundColor = [UIColor purpleColor];

    self.button = [UIButton buttonWithType:UIButtonTypeSystem];
    [self.button setTitle:@"我是按钮" forState:UIControlStateNormal];
    [self.button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [self.button setBackgroundColor:[UIColor redColor]];
    self.button.frame = CGRectMake(100, 100, 200, 100);
    [nativeView addSubview:self.button];
     [self.button addTarget:self action:@selector(flutterMethod) forControlEvents:UIControlEventTouchUpInside];

    return nativeView;
     
}
#pragma mark -- Flutter 交互监听
-(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{
    
    
}
//按钮点击事件
- (void)flutterMethod{
   
}

@end

TestPluginView协议中有一个必须实现的方法view,该方法用于真正生成原生视图树并返回根视图,这里创建了一个UIButton及对应点击处理。

工厂类和视图类都创建完了,现在可以到 刚开始的模板里创建的 TestFlutterPluginDemoPlugin 这个类里去做关联了 是/Development Pods 里面找到Classes文件夹里的这两个文件

​​​​​​​实现原理
App启动后,调用registerWithRegistrar:方法注册插件,要求插件名字唯一。然后调用registerViewFactory:withId:方法注册视图工厂,传入对应的工厂对象和Flutter端对应的viewType值即可。

TestPluginDemoPlugin.m

#import "TestPluginDemoPlugin.h"
#import "TestPluginViewFactory.h"
@implementation TestPluginDemoPlugin

+(void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  
    TestPluginViewFactory *testViewFactory = [[TestPluginViewFactory alloc] initWithMessenger:registrar.messenger];
//这里填写的id 一定要和dart里面的viewType 这个参数传的id一致
    [registrar registerViewFactory:testViewFactory withId:@"testView"];
    
}

@end

修改项目配置

这里运行Flutter端,发现App无法显示原生视图内容,同时控制台报错:

Trying to embed a platform view but the PrerollContext does not support embedding.

因为在Flutter中嵌入原生视图的开销很大,默认情况下是不开放此功能的,因此需要手动修改info.plist文件。在Xcode中右键单击info.plist -> Open As -> Source Code,这样会以源代码方式打开:

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

现在让我们运行一下,效果如下

视图显示基本已经大功告成

Flutter 方法互相调用和传值

Dart代码区,在之前创建的 TestPluginDemo 这个类里面添加 和原生交互代码

/// NOTE:定义一个controller创建成功回调
typedef void TestViewCreatedCallback(TestPluginDemo controller);

/// NOTE:通过字符串test_plugin_demo约定MethodChannel
/// NOTE:这里主要负责和原生进行交互,调用原生的方法和监听原生方法,
class TestPluginDemo {
  MethodChannel _channel;
  TestPluginDemo.init(int id){
    _channel = new MethodChannel('test_plugin_demo');
    _channel.setMethodCallHandler(platformCallHandler);///设置原生参数监听
  }
  ///Flutter 调用原生
  ///这里我传了一个 字符串 当然也可以传Map
  Future<void> changeNativeTitle(String str) async{
    return _channel.invokeListMethod('changeNativeTitle',str);
  }

  ///实现监听原生方法回调
  Future<dynamic> platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "clickAciton":
        print('收到原生回调 ---- $call.arguments');
        return ;
        break;
    }
  }
}

最后给原生视图添加按钮的点击回调

#import "TestPluginView.h"

@interface TestPluginView ()
/** channel*/
@property (nonatomic, strong) FlutterMethodChannel  *channel;
@property (nonatomic, strong) UIButton *button;
@end

@implementation TestPluginView
{
    CGRect _frame;
    int64_t _viewId;
    id _args;
   
}

- (id)initWithFrame:(CGRect)frame
  viewId:(int64_t)viewId
    args:(id)args
messager:(NSObject<FlutterBinaryMessenger>*)messenger
{
    if (self = [super init])
    {
        _frame = frame;
        _viewId = viewId;
        _args = args;
        
        ///建立通信通道 用来 监听Flutter 的调用和 调用Fluttter 方法 这里的名称要和Flutter 端保持一致
        _channel = [FlutterMethodChannel methodChannelWithName:@"test_plugin_demo" binaryMessenger:messenger];
        
        __weak __typeof__(self) weakSelf = self;

        [_channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
            [weakSelf onMethodCall:call result:result];
        }];
       
    }
    return self;
}

- (UIView *)view{
    
    UIView *nativeView = [[UIView alloc] initWithFrame:_frame];
    nativeView.backgroundColor = [UIColor purpleColor];

    self.button = [UIButton buttonWithType:UIButtonTypeSystem];
    [self.button setTitle:@"我是按钮" forState:UIControlStateNormal];
    [self.button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [self.button setBackgroundColor:[UIColor redColor]];
    self.button.frame = CGRectMake(100, 100, 200, 100);
    [nativeView addSubview:self.button];
     [self.button addTarget:self action:@selector(flutterMethod) forControlEvents:UIControlEventTouchUpInside];

    return nativeView;
     
}
#pragma mark -- Flutter 交互监听
-(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{
    //监听Fluter
    if ([[call method] isEqualToString:@"changeNativeTitle"]) {
        [self.button setTitle:call.arguments forState:UIControlStateNormal];
    }
    
}
//调用Flutter
- (void)flutterMethod{
    [self.channel invokeMethod:@"clickAciton" arguments:@"传递参数"];
}

@end

关于flutter IOS插件以上的demo已经实现了。

内容转载自https://www.jianshu.com/p/43a45500e2f6

GitHub:https://github.com/xuan6zm/flutter_ios_plugin

安卓案例https://blog.csdn.net/DearLC/article/details/100974303

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值