iOS老项目通过Cocoapods集成Flutter(iOS混编

2020-03-03  本文已影响0人  汣远

1、Flutter环境配置 Flutter中文网 跟着里面一步一步来就完事了。

2、iOS工程Enable Bitcode 需要关闭,因为Flutter混合开发目前还不支持Bitcode

3、创建flutter module

FlutterMixDemo/BaseFramework/  (BaseFramework 是我的 iOS 工程项目)
进入在 FlutterMixDemo 目录下,终端执行命令:
flutter create -t module flutter_module 

flutter_module是自己起的名字,记得字母都要小写,不然会报错。

这里也有Flutter官方网站英文文档 → iOS接入Flutter教程

4、添加以下代码到Podfile:(没有Podfile怎么办?终端先cd到BaseFramework项目里,执行pod init)

platform :ios, '9.0'

target 'BaseFramework' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  #需要添加的代码  flutter_module是自己创建的名字
  flutter_application_path = '../flutter_module'
  load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  install_all_flutter_pods(flutter_application_path)

end

执行pod install,如果有报错,根据错误提示一个个解决,具体哪些报错不懂的话就谷歌百度吧。。。

注意:当你在flutter_module/pubspec.yaml中有改变了Flutter插件的依赖关系时(不管有没修改啥,只要按了command+s保存后),一定要在BaseFramework(自己的项目)里再次运行pod install。

5、在iOS应用里使用 FlutterViewController

创建FlutterEngine

AppDelegate.h

@import UIKit;
@import Flutter;

@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
AppDelegate.m

#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // Used to connect plugins.

#import "AppDelegate.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
  self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
  // Runs the default Dart entrypoint with a default Flutter route.
  [self.flutterEngine run];
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

用FlutterEngine显示一个FlutterViewController,简单示例:在入口文件里创建一个button用来点击弹出

ViewController.m

@import Flutter;
#import "AppDelegate.h"
#import "ViewController.h"

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    //创建一个button用来点击弹出FlutterViewController
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self
               action:@selector(showFlutter)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
    button.backgroundColor = UIColor.blueColor;
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [self.view addSubview:button];
}

- (void)showFlutter {
    FlutterEngine *flutterEngine =
        ((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
    FlutterViewController *flutterViewController =
        [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:YES completion:nil];
}
@end

当然也可以使用隐式FlutterEngine创建一个FlutterViewController。这里我用了个延迟0.5秒自动弹出FlutterViewController,FlutterEngine创建也是需要时间,如果FlutterEngine还没创建好就弹出FlutterViewController会失败。如果想一启动app就立马弹出Flutter,就要考虑这个问题了。

- (void)viewDidLoad {
    [super viewDidLoad];

    [self showFlutter2];
}

- (void)showFlutter2 {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        FlutterViewController *flutterViewController =
            [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
        flutterViewController.modalPresentationStyle = UIModalPresentationFullScreen;
        [self presentViewController:flutterViewController animated:NO completion:nil];
    });

}

FlutterAppDelegate可以实现如下功能,比如:

将应用程序回调(如openURL)转发到插件(如local_auth)。
转发状态栏点击(只能在AppDelegate中检测到)让Flutter以实现滚动到顶部。

使用FlutterAppDelegate,建议使用UIApplicationDelegate子类FlutterAppDelegate,但不是必需的。
如果你的AppDelegate不能直接使FlutterAppDelegate成为子类,那AppDelegate需要实现FlutterAppLifeCycleProvider协议,以确保插件收到必要的回调。否则,依赖于这些事件的插件可能会有未定义的行为。代码↓

代码是从Flutter官网直接拷过来的,亲测没任何毛病。

AppDelegate.h

@import Flutter;
@import UIKit;
@import FlutterPluginRegistrant;

@interface AppDelegate : UIResponder <UIApplicationDelegate, FlutterAppLifeCycleProvider> 
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
AppDelegate.m

@interface AppDelegate ()
@property (nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate;
@end

@implementation AppDelegate

- (instancetype)init {
    if (self = [super init]) {
        _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
    }
    return self;
}

- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id>*))launchOptions {
    self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
    [self.flutterEngine runWithEntrypoint:nil];
    [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
    return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}

// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {
    UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
    if ([viewController isKindOfClass:[FlutterViewController class]]) {
        return (FlutterViewController*)viewController;
    }
    return nil;
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
    [super touchesBegan:touches withEvent:event];

    // Pass status bar taps to key window Flutter rootViewController.
    if (self.rootFlutterViewController != nil) {
        [self.rootFlutterViewController handleStatusBarTouches:event];
    }
}

- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
    [_lifeCycleDelegate application:application
didRegisterUserNotificationSettings:notificationSettings];
}

- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    [_lifeCycleDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
    [_lifeCycleDelegate application:application
       didReceiveRemoteNotification:userInfo
             fetchCompletionHandler:completionHandler];
}

- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
    return [_lifeCycleDelegate application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
    return [_lifeCycleDelegate application:application handleOpenURL:url];
}

- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
  sourceApplication:(NSString*)sourceApplication
         annotation:(id)annotation {
    return [_lifeCycleDelegate application:application
                                   openURL:url
                         sourceApplication:sourceApplication
                                annotation:annotation];
}

- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
  completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {
    [_lifeCycleDelegate application:application
       performActionForShortcutItem:shortcutItem
                  completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
  completionHandler:(nonnull void (^)(void))completionHandler {
    [_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifier
                  completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
    [_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}

- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {
    [_lifeCycleDelegate addDelegate:delegate];
}
@end

6、当你需要从弹出的Flutter界面返回iOS界面,需要在Flutter的文件里实现SystemNavigator.pop()这个方法。比如在Flutter里定义一个按钮,实现其方法 ↓,注意:需要引入 services.dart 模块才可以使用

import 'package:flutter/services.dart';
RaisedButton(
  onPressed: (){
    SystemNavigator.pop(animated: true);
  },
  child: Text('返回'),
),

注意:当你的根视图控制器是UINavigationController时,SystemNavigator.pop()调用的是' popViewControllerAnimated: '。如果是通过presentViewController:FlutterViewController,那么SystemNavigator.pop()调用的是' dismissViewControllerAnimated:completion: '。

Flutter里还有个exit(0)方法,是强制退出app,使用这个方法要先引入import 'dart:io'和上面的services.dart;

7、Dart里的入口函数默认是调用 main(),入口文件:``lib/main.dart:

你也可以修改Dart的入口函数并在iOS里调用,例如:↓,就把入口函数修改为myOtherEntrypoint(), 入口文件:lib/other_file.dart

[flutterEngine runWithEntrypoint:@"myOtherEntrypoint" libraryURI:@"other_file.dart"];

参考:

https://flutter.dev/docs/development/add-to-app/ios/project-setup

https://flutter.dev/docs/development/add-to-app/ios/add-flutter-screen

上一篇下一篇

猜你喜欢

热点阅读