具体的WidgetExtension的开发流程这里就不细说了,可以参考文末的链接。
在Flutter项目开发IOSWidget的过程中,主要的问题有:
App和Widget的数据共享点击Widget跳转App的指定界面在App界面编辑并更新Widget数据 App和Widget数据共享数据共享使用的是UserDefaults,前提是需要为WidgetExtension和Runner添加相同的AppGroup。添加AppGroup的方法为:
Runner -> Target -> Runner -> Signing&Capabilities -> AppGroups -> +
这里如果没有AppGroups可以XCode点击右上角的+号来添加AppGroups。
WidgetExtension添加方法同上,其中AppGroup要和Runner的相同。
UserDefaults的使用
这里以实际的例子为大家展示UserDefaults的使用。为了方便演示,在App启动时保存相关数据,以供小组件进行读取。
// 以下代码在AppDelegate.swift中的Application方法中
// suitName: 为上面添加的AppGroup
let userDefaults = UserDefaults.init(suiteName: "group.com.cc.ToDo")
userDefaults!.setValue("defaultID", forKey: "id")
userDefaults!.setValue("defauleName", forKey: "name")
在小组件中读取
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
print("start getTimeline")
let userDefaults = UserDefaults(suiteName: "group.com.cc.ToDo")
let id = userDefaults?.string(forKey: "id")
let name = userDefaults?.string(forKey: "name")
print("timeline: \(id!) \(name!)")
// ... 这里省略了后续的completion
}
点击Widget跳转App的指定界面
在小组件中处理点击跳转主要有两种方法:
widgetURL:作用于整个小组件,且一个小组件只能有一个Link:作用于Link包裹的组件的大小,在小尺寸[systemSmall]组件中无法使用Link可以根据实际情况选择合适的组件。
URL Schemes
URL Schemes主要负责处理跳转逻辑,通过配置URL Schemes,在App中捕获对应的url和参数来实现跳转指定页。
注册URL Schemes主要包含以下几步:
Runner -> Info -> URL Types -> 添加+ -> 编辑URL Schemes
完成之后可以再widgetURL中添加url(以上述配置的URL Schemes开头),代码如下:
var body: some View{
VStack{
Text("ToDoList")
Text(entry.userid)
Text(entry.author)
}
// URL以配置的URL Schemes开头,可以拼接参数
.widgetURL(URL(string: "dynamictheme://user?userid=\(entry.userid)&author=\(entry.author)"))
}
// 在Link中配置的URL同此
Flutter端则使用uni_links库来进行链接捕获和跳转,具体实现如下:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:uni_links/uni_links.dart';
import 'pages/UserPage.dart';
GlobalKey navigatorKey = GlobalKey();
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
StreamSubscription _sub;
@override
void initState() {
super.initState();
initPlatformStateForStringUniLinks();
}
Future initPlatformStateForStringUniLinks() async {
String initialLink;
// App未打开的状态在这个地方捕获scheme
try {
initialLink = await getInitialLink();
print('跳转地址: $initialLink');
if (initialLink != null) {
print('跳转地址不为null --$initialLink');
// 跳转到指定页面
schemeJump(context, initialLink);
}
} on PlatformException {
initialLink = 'Failed to get initial link.';
} on FormatException {
initialLink = 'Failed to parse the initial link as Uri.';
}
// App打开的状态监听scheme
_sub = getLinksStream().listen((String link) {
if (!mounted || link == null) return;
print('link--$link');
// 跳转到指定页面
schemeJump(context, link);
}, onError: (Object err) {
if (!mounted) return;
});
}
void schemeJump(BuildContext context, String schemeUrl) {
final Uri _jumpUri = Uri.parse(schemeUrl.replaceFirst(
'dynamictheme://',
'http://path/',
));
switch (_jumpUri.path) {
case '/user':
print("接收到的参数为:");
String userid = _jumpUri.queryParameters["userid"];
print(userid);
String author = _jumpUri.queryParameters["author"];
print(author);
Navigator.of(navigatorKey.currentContext).push(CupertinoPageRoute(
builder: (context) => UserPage(
userid: userid,
author: author,
)));
break;
default:
break;
}
}
@override
void dispose() {
super.dispose();
_sub.cancel();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'Flutter 与 IOS',
theme:
ThemeData(primarySwatch: Colors.blue, platform: TargetPlatform.iOS),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("HomePage")
),
body: Center(
child: Text("Home page"),
),
);
}
}
在App界面编辑并更新Widget数据
在App编辑数据并更新widget功能中,通过MenthodChannel来实现。当编辑完数据,需要更新时,通过MethodChannel来调用原生方法,在原生方法中更新UserDefaults的数据,并返回结果给Flutter端。
数据更新完成并不会刷新Widget,因为Widget中使用的是前一Timeline的快照,在下一个Timeline之前并不会刷新数据,因此需要主动调用相关方法来更新数据。
在原生端想要主动来更新小组件的Timeline,主要有两种方法:
WidgetCenter.shared.reloadAllTimelines(): 更新App下所有组件的TimelinesWidgetCenter.shared.reloadTimelines(ofKind: kind): 更新指定kind类型组件的Timelines具体的实现如下可参考以下代码
Flutter端代码如下:
MethodChannel channel = MethodChannel("com.cc.ToDo.widgets");
var res = await channel.invokeMethod("updateWidgetData", {
"userid":idController.text,
"author":nameController.text
});
print(res);
print(res.runtimeType);
Swift端代码如下:
import UIKit
import Flutter
import WidgetKit
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller:FlutterViewController = window?.rootViewController as! FlutterViewController
let userDefaults = UserDefaults.init(suiteName: "group.com.cc.ToDo")
userDefaults!.setValue("defaultID", forKey: "userid")
userDefaults!.setValue("defauleName", forKey: "author")
// 初始化MethodChannel,设置监听
WidgetMenthod.init(messger: controller.binaryMessenger)
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
// 处理Flutter调用
class WidgetMenthod{
init(messger:FlutterBinaryMessenger){
let channel = FlutterMethodChannel(name: "com.cc.ToDo.widgets", binaryMessenger: messger)
channel.setMethodCallHandler{(call:FlutterMethodCall, result: @escaping FlutterResult) in
// 通过call.method来判断要调用的方法
if(call.method == "updateWidgetData"){
// 通过call.arguments来获取参数
if let dict = call.arguments as? Dictionary<String,Any>{
let userid = dict["userid"] as? String
let author = dict["author"] as? String
print("\(userid) ==== \(author)")
let userDefaults = UserDefaults.init(suiteName: "group.com.cc.ToDo")
userDefaults!.setValue(userid, forKey: "userid")
userDefaults!.setValue(author, forKey: "author")
if #available(iOS 14.0, *) {
print("reload timelines")
WidgetCenter.shared.reloadTimelines(ofKind: "todo_list")
print("reload complete!")
result(["code":1,"msg":"success"])
} else {
result(["code":0,"msg":"系统版本过低"])
}
}else{
result(["code":0,"msg":"参数异常"])
}
}
}
}
}
至此,在Flutter项目中开发IOS桌面组件就全部完成了。
完整案例源码点此下载
参考文章网易云音乐 iOS 14 小组件实战手册
[【Flutter 混合开发】与原生通信-MethodChannel](
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)