SWIFT

SwiftUI-Widget 使用及避坑指南

2022-02-18  本文已影响0人  鑫龙魂

iOS Widget简单介绍( 只介绍iOS 14 以后Widget相关内容):

Widget 是 iOS 14 重磅推出的新功能,使得用户可以在主屏幕添加小组件,快速浏览 app 提供的重要信息。
用户可以通过 Widget 对主屏幕进行个性化定制,但是 iOS 14 的 Widget 跟其他系统上的小组件有很大的区别。在 Widget 的设计上苹果也保持了一贯的克制,定位于轻量化、仅用作关键信息的展示。比如系统自带 Widget 中的股票、天气、电量、运动信息,他们的共同特征是更新频率高、提供的信息重要,让用户不用打开 app 就可以浏览关心的内容。

相关限制:

苹果基于上面的设计定位,同时也为了节省系统资源保证续航,对 Widget 的做了一些限制:
不支持动画,仅支持静态页面展示。
更新频率由系统通过机器学习来动态分配。
不支持拖拽、滚动等复杂的交互,不支持 Switch 等控件。
用户点击 Widget 一定会跳转到 App。
支持三种不同大小的样式

适应不同的屏幕尺寸

iOS Widget简单介绍( 只介绍iOS 14 以后Widget相关内容):

Widget 是 iOS 14 重磅推出的新功能,使得用户可以在主屏幕添加小组件,快速浏览 app 提供的重要信息。
用户可以通过 Widget 对主屏幕进行个性化定制,但是 iOS 14 的 Widget 跟其他系统上的小组件有很大的区别。在 Widget 的设计上苹果也保持了一贯的克制,定位于轻量化、仅用作关键信息的展示。比如系统自带 Widget 中的股票、天气、电量、运动信息,他们的共同特征是更新频率高、提供的信息重要,让用户不用打开 app 就可以浏览关心的内容。

相关限制:

苹果基于上面的设计定位,同时也为了节省系统资源保证续航,对 Widget 的做了一些限制:
不支持动画,仅支持静态页面展示。
更新频率由系统通过机器学习来动态分配。
不支持拖拽、滚动等复杂的交互,不支持 Switch 等控件。
用户点击 Widget 一定会跳转到 App。
支持三种不同大小的样式

适应不同的屏幕尺寸

屏幕尺寸 - portrait 小部件-systemSmall 中型部件-systemMedium 大部件-systemLarge
414x896 pt (XR/XsMax/11/11ProMax) 169x169pt 360x169pt 360x379pt
375x812 pt (X/Xs/11 Pro) 155x155 pt 329x155 pt 329x345 pt
414x736 pt (6p/6sp/7p) 159x159 pt 348x159 pt 348x357 pt
375x667 pt (6/6s/7/8) 148x148 pt 321x148 pt 321x324 pt
320x568 pt (5/5s/SE) 141x141 pt 292x141 pt 292x311 pt

开发要求:

开发工具 Xcode 12 以上版本
开发语言 Swift和SwiftUI
手机系统要求 14以上

Widget 创建:

1.Widget作为项目的一个组件,创建之前需要先创建一个iOS的项目,项目创建成功后点击:File->New->Target添加Widget Extension Target 点击Next。

2.输入Widget组件名,取消勾选,点击Finish就可以了。Include Configuration Intent:是否支持用户配置。

3.关于预览:
本项目会提示需要升级,新创建项目的没有该问题
(运行widget target 模拟器调试打日志有时不显示)

多个Widget和小、大、中页面数据布局

如何定义多个Widget,并且小、中、大的布局完全不同?

iOS14中Widget是支持通过创建一个扩展项目返回一个或多个小部件的,可以使您的应用提供多种小部件选择。并且在项目中视图通过WidgetFamily的枚举自定义自己想要的组件和布局。

WidgetFamily枚举

public enum WidgetFamily : Int, RawRepresentable, CustomDebugStringConvertible, CustomStringConvertible {

    /// A small widget.
    case systemSmall

    /// A medium-sized widget.
    case systemMedium

    /// A large widget.
    case systemLarge
}

默认模版代码,只能支持展示一类型种的一种样式

@main //widget 主入口,系统从这里加载
struct WidgetTest: Widget {
  //kind的值是widget的唯一标识
    let kind: String = "Widget"
    var body: some WidgetConfiguration {//初始化配置代码
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
           WidgeEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")//编辑页面展示的标题
        .description("This is an example widget.")//编辑页面展示的描述内容
        .supportedFamilies([.systemSmall,.systemMedium,.systemLarge])// 如何实现预览里面small样式展示不同样式
    }
}

可以通过修改原Widget入口文件方法添加更多配置来支持多个Widget,相同类型不同样式。

@main
struct SwiftWidgetsBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        Widget1()
        Widget2()
        Widget3()
        Widget4()
        ...
    }
}

与主应用交互:

根据官方文档的描述,点击Widget窗口唤起APP进行交互指定跳转支持两种方式:

widgetURL:点击区域是Widget的所有区域,代码如下。
if family == .systemSmall {  // 小
  VStack(alignment: .center, spacing: 20, content: {
      Text("\(entry.quotes.date) at \(entry.quotes.place) ")
          .font(.system(size: 9))
          .foregroundColor(.gray)
  })
  .widgetURL(URL(string: "https://www.baidu.com/small"))

}

Link:通过Link修饰,允许让界面上不同元素产生点击响应。

if family == .systemMedium { // 中
  VStack(alignment: .center, spacing: 8, content: {
      Link(destination: URL(string: "https://www.baidu.com/medium/1")!) {
          Text(entry.quotes.content[0])
              .font(.system(size: 17))
              .foregroundColor(.black)
              .frame(maxWidth:.infinity, alignment: .leading)
      }
     
      Text("\(entry.quotes.date) at \(entry.quotes.place) ")
          .font(.system(size: 12))
          .foregroundColor(.gray)
          .frame(maxWidth:.infinity, alignment: .trailing)
          .frame(height: 20, alignment: .bottom)
  })
 
}

注!:systemSmall(小组件)只支持widgetURL,而systemMedium(中组件)和 systemLarge(大组件)则都支持。Link:更希望的是不同元素的点击响应。

在主项目的SceneDelegate代理方法中接收回调

- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
    /// 根据不同的URL回调做出响应
    NSLog(@"%@",URLContexts);
}

或 AppDelegate 中的

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*,id> *)options{
//处理
    return YES;
}

数据更新时机

这里关于刷新策略,根据官方文档来看,Timeline的刷新策略是会延迟的,并不一定根据你设定的时间来。同时官方规定每个配置的窗口小部件每天都接收有限数量的刷新,
(官方文档:https://developer.apple.com/documentation/widgetkit/timelineprovider

导致无法预测何时更新Widget。即使设置了某个时间再次获取时间轴本身进行更新,也无法保证iOS会同时更新视图。从而造成一定的Widget页面更新延迟。

苹果因为提供了一个单独的方法,调用来重新加载所有窗口小部件
/// 控件的所有已配置小部件重新加载时间线
/// 包含应用程序。

WidgetCenter.shared.reloadAllTimelines()

刷新次数限制

Refreshing Widgets Efficiently
Each configured widget receives a limited number of refreshes every day. Several factors affect how many refreshes a widget receives, such as whether the containing app is running in the foreground or background, how frequently the widget is shown onscreen, and what types of activities the containing app engages in.

每个配置的小部件每天都会收到有限的刷新次数。有几个因素会影响小部件接收的刷新次数,例如包含的应用程序是在前台还是后台运行,小部件在屏幕上显示的频率,以及包含的应用程序参与的活动类型。

在Xcode中调试小部件时,WidgetKit不会施加此限制。要验证小部件的行为是否正确,请在Xcode的调试器之外测试应用程序和小部件的行为。

当你的应用程序位于前台、有活动媒体会话或使用标准位置服务时,刷新不计入小部件的每日限制。有关媒体会话和定位服务的更多信息,请参阅doc://com.apple.documentation/documentation/avfoundation/avaudiosession使用标准定位服务。

数据共享:

主要是使用App Group来实现。
如登录态同步等

Swift 与OC 相互调用:

Widget 中的Swift 调用主项目的OC 调用
使用桥接方法,且引入的文件必须 在Target Membership 关联对应的 Widget Target。

注意:

duplicate symbol '_main' in:
    /Users/XXX/Library/Developer/Xcode/DerivedData/XXX-hghyirqieliknyckkefqaeomgvms/Build/Intermediates.noindex/XXX.build/Debug-iphoneos/XXX.build/Objects-normal/arm64/main.o
    /Users/XXX/Library/Developer/Xcode/DerivedData/XXX-hghyirqieliknyckkefqaeomgvms/Build/Intermediates.noindex/XXX.build/Debug-iphoneos/XXX.build/Objects-normal/arm64/XXXWidget.o
ld: 1 duplicate symbol for architecture arm64
clang_bk: error: linker command failed with exit code 1 (use -v to see invocation)
[$] waitpid = 64263 
[$] run clang_bk fail,not exist 
nagain clang exit 
Command /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang failed with exit code 255
var familNames:[String] = []
        familNames = UIFont.familyNames
        for fami in familNames {
            print("---familNames",fami)
            let namesArr = UIFont.fontNames(forFamilyName: fami)
            for name in namesArr {
                print("---famil fontname",name)
            }
        }
  print(familNames)

参考:
如何用iOS14 Widget小组件自定义玩法

iOS14 Widget初体验

如何进行 iOS Widget 开发?
iOS小组件Widget踩坑

上一篇下一篇

猜你喜欢

热点阅读