如何从FlutterViewController“推送”一个UIViewController

14

我想在Flutter视图上“推送”一个新的本地视图(Android中的Activity和iOS中的UIViewController),并在完成/结束后,使屏幕返回到我们的Flutter视图。

我可以在Android上做到这一点。但是在iOS方面,我很陌生,当我尝试在ios/Runner/AppDelegate.m中实现时:

  SwiftViewController *swiftViewController = [controller.storyboard instantiateViewControllerWithIdentifier:@"SwiftViewController"];
  [(FlutterViewController *)self.window.rootViewController pushViewController:swiftViewController animated:YES];

它出现错误:

没有声明选择器“pushViewController: animated”的“FlutterViewController”可见的@interface

那么在iOS中应该如何做呢?谢谢。


很好。但是我不得不绕过Swift编程来实现类似的结果。此外,我还必须使用我的Storyboard ID来获取本地控制器。 - Vijay Kumar Kanta
请不要将解决方案公告编辑到问题中。如果已经有答案,请接受其中一个(即单击其旁边的“勾”)。如果没有现成的答案,您也可以创建自己的答案,并接受它。 - Yunnosch
6个回答

6

在 Swift 中,我们可以使用方法通道从 Flutter 操作事件打开本机 iOS 视图控制器。

  • 在 Flutter 中,您需要在操作方法中添加以下代码行以触发本机 iOS 方法:
var platform = const MethodChannel('com.nativeActivity/iosChannel');
final String result = await platform.invokeMethod('StartNativeIOS');
  • iOS中,在AppDelegate的application方法中添加以下行:
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController

let methodChannel = FlutterMethodChannel(name: "com.nativeActivity/iosChannel", binaryMessenger:controller.binaryMessenger)

methodChannel.setMethodCallHandler({
  (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
        
  if call.method == "StartNativeIOS" {
    let mainVC = MainViewController() // Your viewController
    let navigationController = UINavigationController(rootViewController: mainVC)
    self.window.rootViewController = navigationController
    self.window.makeKeyAndVisible()
  } else {
    result(FlutterMethodNotImplemented)
    return
  }
})

这会起作用!干杯

注意:但是无法返回到flutter视图。

以下是正确的方法。 使用协调器模式在iOS应用中使用Flutter导航


4
在Swift中,我们可以使用方法通道从flutter操作事件打开本机iOS viewController。 在flutter中,您需要在您的操作方法中添加以下代码行以触发本机iOS方法。
var platform = const MethodChannel('navigation');
final String result = await platform.invokeMethod('IOS');

在iOS中,在AppDelegate的application方法中添加以下行。
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let methodChannel = FlutterMethodChannel(name: "navigation", binaryMessenger:controller.binaryMessenger)
navigationController = UINavigationController(rootViewController: controller)
navigationController.setNavigationBarHidden(true, animated: false)
self.window!.rootViewController = navigationController
self.window!.makeKeyAndVisible()
methodChannel.setMethodCallHandler({
    (call: FlutterMethodCall, result: @escaping FlutterResult) -> 
    Void in
if call.method == "IOS" {
      let vc = "Your viewController"(nibName: "Your 
      viewController", bundle: nil)
      navigationController.pushViewController(vc, animated: 
     false)
} else {
         result(FlutterMethodNotImplemented)
         return
}
})

本地视图控制器返回按钮操作。

self.navigationController?.popViewController(animated: true)

谢谢。


4

pushViewController只能在您的控制器是navigation stack的一部分时工作,即如果您的FlutterViewController嵌入在一个UINavigationController中,则可以像这样从中push另一个控制器:

    self.navigationController?.pushViewController(swiftViewController, animated: true)

第二种选择是: 如果FlutterViewController没有嵌入在UINavigationController中,你可以像这样简单地从中present另一个控制器:

    self.present(swiftViewController, animated: true, completion: nil)

3
您需要在容器UINavigationController中编程或在故事板中嵌入FlutterViewController,然后您就能推出下一个控制器了。
这里是一个编程嵌入的示例:
@interface AppDelegate()
@property (nonatomic, strong) UINavigationController *navigationController;
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];

  UIViewController *flutterViewController = [[FlutterViewController alloc] init];
  self.navigationController = [[UINavigationController alloc] initWithRootViewController:flutterViewController];
  [self.navigationController setNavigationBarHidden:YES];

  self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  self.window.rootViewController = self.navigationController;
  [self.window makeKeyAndVisible];

  return true;
}

- (void)pushExample {
  UIViewController *viewController = [[UIViewController alloc] init];
  [self.navigationController pushViewController:viewController animated:true];
}

@end

在需要时(例如按钮被点击),调用pushExample函数。 您也可以查看此视频中的“故事板”方式。


感谢您的帮助,由于我仍在苦苦学习iOS的黑魔法,我能否请求您提供您描述的示例代码呢?谢谢兄弟。 - Eason PI
1
问题已解决,您可以在此处找到我的示例代码:https://github.com/EasonPai/flutter_pushpop_native_view - Eason PI
非常感谢 @"Eason PI"(还有 @"German Saprykin")!!! \o/ - Breno Medeiros de Oliveira

2
也许你可以看一下Webview插件。类似于您的描述,它们启动一个新的Activity或UIViewController。显然,您不需要启动Webview。

太棒了,你说得对,Webview插件绝对具有我想要的相同流程,并且可以作为一个完整的示例,谢谢! - Eason PI

2

编写自定义平台特定代码

一个带有按钮的小部件。点击按钮应该向 iOS 应用程序推送相关消息以推送视图控制器。

class BodyWidget extends StatelessWidget {

  static const platform = const MethodChannel('samples.flutter.dev/navigation_channel');

  Future<void> _pushOnNative() async {
    try {
      Map parameters = Map();
      parameters["title"] = "Contact Details";
      parameters["name"] = "Bang Operator";
      parameters["so_profile_exists"] = true;
      await platform.invokeMethod('push',[parameters]);
    } on PlatformException catch (e) {
      print("Failed to communicate level: '${e.message}'.");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Center(
        child: FlatButton(
          textColor: Colors.white,
          color: Colors.red,
          onPressed: _pushOnNative,
          child: Text('Push Me'),
        )
    );
  }
}

在iOS应用程序端,我们需要观察通道samples.flutter.dev/navigation_channel的方法'push'。我们还可以从Flutter侧传递参数,在此示例中,我们传递一个作为NSDictionary(Obj-C)接收的Map(Dart)。最初的回答。
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];

  FlutterViewController *controller = (FlutterViewController*)self.window.rootViewController;
  UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];

  self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  self.window.rootViewController = navigationController;
  [navigationController setNavigationBarHidden:YES animated:YES];
  [self.window makeKeyAndVisible];

  [self setupNavigationChannelCallbacksOnMessenger:controller
                           forNavigationController:navigationController];


  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)setupNavigationChannelCallbacksOnMessenger:(id<FlutterBinaryMessenger>)messenger
                           forNavigationController:(UINavigationController *)navigationController {
  FlutterMethodChannel* pushChannel = [FlutterMethodChannel
                                       methodChannelWithName:@"samples.flutter.dev/navigation_channel"
                                       binaryMessenger:messenger];

  [pushChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    if ([call.method isEqualToString:@"push"] && [self getDictionaryFromArguments:call.arguments]) {
      NSDictionary *parameters = [self getDictionaryFromArguments:call.arguments];
      [self pushContactScreenWithInfo:parameters onNaivgationController:navigationController];
      result(@(true));
    } else {
      result(FlutterMethodNotImplemented);
    }
  }];
}

- (NSDictionary *)getDictionaryFromArguments:(id)arguments {
  if (![arguments isKindOfClass:[NSArray class]]) {
    return nil;
  }
  NSArray *argumentsArray = (NSArray *)arguments;
  if (argumentsArray.firstObject == nil || ![argumentsArray.firstObject isKindOfClass:[NSDictionary class]]) {
    return nil;
  }
  return (NSDictionary *)argumentsArray.firstObject;
}

- (void)pushContactScreenWithInfo:(NSDictionary *)info
          onNaivgationController:(UINavigationController *)navigationContorller {
  UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
  ContactViewController *controller = [storyboard instantiateViewControllerWithIdentifier:@"ContactViewController"];
  [navigationContorller pushViewController:controller animated:YES];
  [controller view];
  [controller configureWithInfo:info];
}

我该如何返回到Flutter屏幕?我尝试在本地iOS中使用pop ViewController,然后它回到了Flutter应用程序的启动屏幕。我希望从Flutter屏幕开始,当我点击按钮并推送视图控制器时,它只会弹出并显示相同的Flutter屏幕,其中放置了我的按钮。请问我该如何做到这一点?请帮忙。 - Ashwini Salunkhe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接