24.调用原生相册

MethodChannel

Flutter 与原生交互的通道是 MethodChannel。下面我们通过调用 iOS 原生相册选择来体验一下。

示例代码

定义一个Channel

MethodChannel _channel = const MethodChannel('mine_header/channel');

给原生发送指令

_channel.invokeMapMethod('sel_avatar');

原生处理指令

import UIKit
import Flutter

/// 这里将指令用 枚举包装,便于使用
enum MineChannelMethod: String {
    case AvatarPick = "sel_avatar"
}

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        
        if let flutterVC = window?.rootViewController as? FlutterViewController, 
        let messenger = flutterVC as? FlutterBinaryMessenger 
        {
            // 实例化 channel
            let mineChannel = FlutterMethodChannel.init(name: "mine_header/channel", binaryMessenger: messenger)
            mineChannel.setMethodCallHandler { call, result in
                guard 
                    call.method.isEmpty == false, 
                    let method = MineChannelMethod(rawValue: call.method) 
                else {
                    return
                }
                
                switch method {
                case .AvatarPick:
                    let picker = UIImagePickerController()
                    picker.modalPresentationStyle = .overFullScreen
                    picker.delegate = self
                    flutterVC.present(picker, animated: true, completion: nil)
                }
                
            }
        }
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

extension AppDelegate: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        picker.dismiss(animated: true, completion: nil)
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
}

检验效果

数据传递

通过检查选择完成后的回调我们发现 info 中包含的数据:

[__C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaType): public.image, __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerImageURL): file:///private/var/mobile/Containers/Data/Application/8A9A613F-32F9-4E45-8CB3-BB882CC9F590/tmp/3BC0596E-8B97-4E64-B260-269668F9CA73.png, __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerOriginalImage): <UIImage:0x281538f30 anonymous {1125, 2436} renderingMode=automatic>, __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerReferenceURL): assets-library://asset/asset.PNG?id=65B7A1B5-597F-4B97-B8E3-9CA019A9A526&ext=PNG]

我们需要将图片路径回传给 Flutter 。

if let imgPath = info[UIImagePickerController.InfoKey.imageURL] as? URL {
    print(imgPath.absoluteString)
}

file:///private/var/mobile/Containers/Data/Application/EE26C1FB-3551-4454-8BAA-B62CADD2B311/tmp/C2302E64-C587-4975-9A24-BD43B34FA0F1.png

if let imgPath = (info[UIImagePickerController.InfoKey.imageURL] as? URL)?.absoluteString, imgPath.count > 8 {
    let path = String(imgPath.suffix(from: String.Index.init(utf16Offset: 7, in: imgPath)))
    print(path)
}

截取有效字段: /private/var/mobile/Containers/Data/Application/C7341168-0BCB-4712-8092-CB2661798888/tmp/828FF919-E315-4401-B76D-C774F5F2EF25.png

数据回传

if let imgPath = (info[UIImagePickerController.InfoKey.imageURL] as? URL)?.absoluteString, imgPath.count > 8 {
    let path = String(imgPath.suffix(from: String.Index.init(utf16Offset: 7, in: imgPath)))
    channel?.invokeMethod("rec_imagePath", arguments: path)
}

数据接收

final String _selAvatar = 'sel_avatar';
final String _reciveImagePath = 'rec_imagePath';
final MethodChannel _channel = const MethodChannel('mine_header/channel');

@override
void initState() {
    super.initState();
    _channel.setMethodCallHandler( (call) async {
      if (call.method == _reciveImagePath) {
        print('${call.arguments}');
        setState(() {
          _avatarPath = call.arguments.toString();
        });
      }
    });
}

得到: flutter: /private/var/mobile/Containers/Data/Application/336E79D6-B33A-4ABC-A380-4501E1550E1D/tmp/D50D8A94-3625-4CA7-8D2B-2C6AD0CCCC67.png

数据展示

class MeHeaderView extends StatelessWidget {
  Function()? avatarTap;
  File? avatarFile;

  MeHeaderView({this.avatarTap, this.avatarFile});

  @override
  Widget build(BuildContext context) {
    return Container(
            ...
                decoration: BoxDecoration(
                    image: DecorationImage(
                    fit: BoxFit.fitWidth,
                    image: (avatarFile == null
                        ? const AssetImage('images/header_avatar.GIF')
                        : FileImage(avatarFile!)) as ImageProvider,
                    ),
                ),
            ),
            ...
  }
}

Last updated