小组件尺寸
注意:
- 超大号尺寸系统要求最低 iOS15及以后,专门在 iPad 上使用的
- 从 iOS 16 开始,支持
accessoryCircular
、accessoryRectangular
和accessoryInline
类型,这几种小部件可以用在手表中,也可以出现在 iOS 的锁定屏幕上。
目前小组件支持设置基础的三个尺寸,即:小 、中 、大 、超大(没有调出来)。
适配不同尺寸小组件
1.supportedFamilies
在 iOS14 上适配不同尺寸的小组件就是在 Widget 上添加 supportedFamilies 属性:
scss
struct MyWidget: Widget {
let kind: String = "MyWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
followWidgetEntryView(entry: entry)
.padding()
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
// 设置支持的小组件类型
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
.adoptableWidgetContentMargin()
}
}
2.available
如需在 iOS14 上支持 超大尺寸 ,可以通过 available 区分不同操作系统版本
scss
struct MyWidget: Widget {
let kind: String = "MyWidget"
var body: some WidgetConfiguration {
if #available(iOSApplicationExtension 15.0, *) {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
// 新增加
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
} else {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
// 新增加
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
}
3.自定义supportedFamilies
同样可以通过自定义 supportedFamilies
函数返回值来达到适配不同尺寸小组件的目的。
swift
struct MyWidget: Widget {
let kind: String = "MyWidget"
private var customSupportedFamilies: [WidgetFamily] {
if #available(iOSApplicationExtension 15.0, *) {
return [.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge]
} else {
return [.systemSmall, .systemMedium, .systemLarge]
}
}
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
// 新增加
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies(customSupportedFamilies)
}
}
适配多种类型的小组件
不同小组件类型需要不同的系统版本支持,如果在 iOS14 上添加 iOS16 锁屏小组件就会报错。
解决方案可根据不同系统版本设置不同的小组件类型支持
swift
struct MyWidget: Widget {
let kind: String = "MyWidget"
private var customSupportedFamilies: [WidgetFamily] {
if #available(iOSApplicationExtension 16.0, *) {
return [.systemSmall, .systemMedium, .systemLarge, .accessoryInline, .accessoryCircular, .accessoryRectangular]
} else {
return [.systemSmall, .systemMedium, .systemLarge]
}
}
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
// 新增加
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies(customSupportedFamilies)
}
}
Environment
在 MyWidgetEntryView 中可以从环境变量(Environment)中获取当前小组件类型,根据类型可以做不同的 UI 适配:
swift
struct MyWidgetEntryView : View {
var entry: Provider.Entry
// 环境变量获取当前的小组件类型
@Environment(\.widgetFamily) var family: WidgetFamily
var familyString: String {
switch family {
case .systemSmall:
return "小组件"
case .systemMedium:
return "中等组件"
case .systemLarge:
return "大号组件"
case .systemExtraLarge:
return "超大号组件"
case .accessoryCircular:
return "圆形组件"
case .accessoryRectangular:
return "方形组件"
case .accessoryInline:
return "内联小组件"
@unknown default:
return "其他类型小组件"
}
}
var body: some View {
VStack(spacing: 10) {
Image(systemName: entry.time.icon)
.imageScale(.large)
.foregroundColor(.red)
HStack {
Text("现在是:")
Text(entry.time.text)
}
.font(.title3)
Text("小组件类型: \(familyString)")
}
.widgetBackground(Color.white)
}
}
完整代码
scss
import WidgetKit
import SwiftUI
extension View {
// 背景
@ViewBuilder
func widgetBackground(_ backgroundView: some View) -> some View {
// 如果是 iOS 17,则使用 containerBackground
if #available(iOS 17.0, *) {
containerBackground(for: .widget) {
backgroundView
}
} else {
background(backgroundView)
}
}
}
enum Time {
case morning, afternoon, night
var text: String {
switch self {
case .morning:
return "上午"
case .afternoon:
return "下午"
case .night:
return "晚上"
}
}
var icon: String {
switch self {
case .morning:
return "sunrise"
case .afternoon:
return "sun.max.fill"
case .night:
return "sunset"
}
}
}
func getDate(hour: Int) -> Date {
let calendar = Calendar.current
var components = calendar.dateComponents([.year, .month, .day], from: Date())
components.hour = hour
components.minute = 0
components.second = 0
return calendar.date(from: components)!
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), time: .morning)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), time: .morning)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let hour = Calendar.current.component(.hour, from: Date())
switch hour {
case 8..<12:
entries.append(SimpleEntry(date: Date(), time: .morning))
entries.append(SimpleEntry(date: getDate(hour: 12), time: .afternoon))
entries.append(SimpleEntry(date: getDate(hour: 18), time: .night))
case 12..<18:
entries.append(SimpleEntry(date: Date(), time: .afternoon))
entries.append(SimpleEntry(date: getDate(hour: 18), time: .night))
default:
entries.append(SimpleEntry(date: Date(), time: .night))
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
// 表示上午、下午、晚上
let time: Time
}
struct MyWidgetSmallEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack(spacing: 10) {
Image(systemName: entry.time.icon)
.imageScale(.large)
.foregroundColor(.red)
HStack {
Text("现在是:")
Text(entry.time.text)
}
.font(.title3)
Text("小号组件类型")
}
.widgetBackground(Color.white)
}
}
struct MyWidgetMiddleEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack(spacing: 10) {
Image(systemName: entry.time.icon)
.imageScale(.large)
.foregroundColor(.red)
HStack {
Text("现在是:")
Text(entry.time.text)
}
.font(.title3)
Text("中号组件类型")
}
.widgetBackground(Color.white)
}
}
struct MyWidgetLargeEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack(spacing: 10) {
Image(systemName: entry.time.icon)
.imageScale(.large)
.foregroundColor(.red)
HStack {
Text("现在是:")
Text(entry.time.text)
}
.font(.title3)
Text("大号组件类型")
}
.widgetBackground(Color.white)
}
}
func AccessoryCircularView() -> some View {
Image(systemName: "brain.head.profile")
}
func AccessoryInlineView() -> some View {
HStack {
Image(systemName: "brain.head.profile")
.padding(.trailing, 5)
Text("内联锁屏小组件")
}
}
func AccessoryRectangularView() -> some View {
VStack {
Image(systemName: "brain.head.profile")
.padding(.bottom, 5)
Text("矩形锁屏小组件")
}
}
struct MyWidgetEntryView : View {
var entry: Provider.Entry
// 环境变量获取当前的小组件类型
@Environment(\.widgetFamily) var family: WidgetFamily
var body: some View {
switch family {
case .systemSmall:
MyWidgetSmallEntryView(entry: entry)
case .systemMedium:
MyWidgetMiddleEntryView(entry: entry)
case .systemLarge:
MyWidgetLargeEntryView(entry: entry)
case .accessoryCircular:
AccessoryCircularView()
case .accessoryRectangular:
AccessoryRectangularView()
case .accessoryInline:
AccessoryInlineView()
default:
Text("其他类型小组件")
}
}
}
struct MyWidget: Widget {
let kind: String = "MyWidget"
private var customSupportedFamilies: [WidgetFamily] {
if #available(iOSApplicationExtension 16.0, *) {
return [.systemSmall, .systemMedium, .systemLarge, .accessoryInline, .accessoryCircular, .accessoryRectangular]
} else {
return [.systemSmall, .systemMedium, .systemLarge]
}
}
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
// 新增加
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies(customSupportedFamilies)
}
}
struct MyWidgetEntryView_Previews: PreviewProvider {
static var previews: some View {
MyWidgetEntryView(entry: SimpleEntry(date: Date(), time: .morning))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
本文同步自微信公众号 "程序员小溪" ,这里只是同步,想看及时消息请移步我的公众号,不定时更新我的学习经验。