交互小组件 — iOS 17

作为一名 iOS 开发人员,该平台有一些令人兴奋的特性和功能值得探索。 其中,小部件是我的最爱。 小部件已成为 iOS 和 macOS 体验中不可或缺的一部分,并且随着 SwiftUI 中引入的最新功能,它们现在变得更加强大。 在本文中,我们将探讨如何通过交互性和动画使小组件变得栩栩如生,使它们更具吸引力和视觉吸引力。 我们将深入探讨动画如何与小组件配合使用的细节,并展示新的 Xcode Preview API,它可以实现快速迭代和自定义。 此外,我们将探索如何使用熟悉的控件(如 Button 和 Toggle)向小部件添加交互性,并利用 App Intents 的强大功能。 那么让我们开始吧!

小部件中的交互性 小部件在单独的进程中呈现,它们的视图代码仅在归档期间运行。 为了使小组件具有交互性,我们可以使用 Button 和 Toggle 等控件。 但是,由于 SwiftUI 不会在应用程序的进程空间中执行闭包或改变绑定,因此我们需要一种方法来表示可由小部件扩展执行的操作。 App Intents 为此提供了一个解决方案,允许我们定义可由系统调用的操作。 通过导入 SwiftUI 和 AppIntents,我们可以使用接受 AppIntent 作为参数的 Button 和 Toggle 初始值设定项来执行所需的操作。

现在我们要为现有项目创建小组件。

相应地命名它。 请注意,禁用两个复选框

现在我将使用清单和按钮重写现有代码。

less 复制代码
struct Provider: TimelineProvider {  
    func placeholder(in context: Context) -> SimpleEntry {  
        SimpleEntry( checkList: Array(ModelData.shared.items.prefix(3)))  
    }  
  
    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {  
        let entry = SimpleEntry(checkList: Array(ModelData.shared.items.prefix(3)))  
        completion(entry)  
    }  
  
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {  
        //var entries: [SimpleEntry] = []  

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.  
        let data = Array(ModelData.shared.items.prefix(3))  
        let entries = [SimpleEntry(checkList: data)]  

        let timeline = Timeline(entries: entries, policy: .atEnd)  
        completion(timeline)  
    }  
}

struct SimpleEntry: TimelineEntry {  
    var date: Date = .now  

    var checkList: [ProvisionModel]  
}

struct InteractiveWidgetEntryView : View {  
    var entry: Provider.Entry  

    var body: some View {  
        VStack(alignment: .leading, spacing: 5.0) {  
            Text("My List")  
            if entry.checkList.isEmpty{  
                Text("You've bought all🏆")  
            }else{  
                ForEach(entry.checkList) { item in  
                    HStack(spacing: 5.0){  

                        Image(systemName: item.isAdded ? "checkmark.circle.fill":"circle")  
                        .foregroundColor(.green)  


                        VStack(alignment: .leading, spacing: 5){  
                            Text(item.itemName)  
                            .textScale(.secondary)  
                            .lineLimit(1)  
                            Divider()  
                        }  
                    }  
                }  
            }  
        }  
        .containerBackground(.fill.tertiary, for: .widget)  
    }  
}

struct InteractiveWidget: Widget {  
let kind: String = "InteractiveWidget"  
  
var body: some WidgetConfiguration {  
    StaticConfiguration(kind: kind, provider: Provider()) { entry in  
            InteractiveWidgetEntryView(entry: entry)  
        }  
        .configurationDisplayName("My Widget")  
        .description("This is an example widget.")  
    }  
}

提供的代码在 iOS 或 macOS 应用程序中使用 SwiftUI 定义小部件。 让我们分解代码并解释每个部分:

  1. Provider:该结构体符合TimelineProvider协议,负责向widget提供数据。 它包含三个功能:
  • placeholder(in:):此函数返回一个占位符条目,表示首次添加小部件时的外观。 它使用派生自 ModelData.shared.items 的清单数组创建 SimpleEntry。
  • getSnapshot(in:completion:):此函数生成一个表示小部件当前状态的快照条目。 它使用派生自 ModelData.shared.items 的清单数组创建 SimpleEntry。
  • getTimeline(in:completion:):此函数生成小部件的条目时间线。 它使用派生自 ModelData.shared.items 的清单数组创建 SimpleEntry 实例的数组,并返回包含这些条目的时间线。
  1. SimpleEntry:此结构符合 TimelineEntry 协议,表示小部件时间线中的单个条目。 它包含一个表示条目日期的日期属性和一个 checkList 属性,后者是一个 ProvisionModel 项的数组。
  2. InteractiveWidgetEntryView:此结构定义用于显示小部件条目的视图层次结构。 它采用 Provider.Entry 类型的条目作为输入。 在 body 属性内部,它创建一个具有对齐和间距设置的 VStack。 它显示一个标题,并根据 checkList 数组是否为空,显示一条消息或迭代该数组以显示每个项目的信息。
  3. InteractiveWidget:该结构定义小部件本身。 它符合Widget协议并指定了Widget的种类。 它提供了一个 StaticConfiguration,其中包含一个 Provider 实例作为数据提供者,并提供一个 InteractiveWidgetEntryView 作为每个条目的视图。 它还设置小部件的显示名称和描述。
  4. Preview:此代码块用于在开发过程中预览小部件的外观。 它为 .systemSmall 大小的小部件创建预览,并提供 SimpleEntry 实例作为条目。 总的来说,此代码设置了一个使用 SwiftUI 框架显示清单的小部件。 小部件的数据由 Provider 结构提供,条目的视图由 InteractiveWidgetEntryView 结构定义。 InteractiveWidget 结构配置小部件并提供用于开发目的的预览。

还有按钮动作!

Apple 为此推出了 AppIntents!

我已经创建了视图模型和应用程序意图。

swift 复制代码
struct ProvisionModel: Identifiable{  
    var id: String = UUID().uuidString  
    var itemName: String  
    var isAdded: Bool = false  
  
}  
  
class ModelData{  
    static let shared = ModelData()  

    var items: [ProvisionModel] = [.init(  
    itemName: "Orange"  
    ), .init(  
    itemName: "Cheese"  
    ), .init(  
    itemName: "Bread"  
    ), .init(  
    itemName: "Rice"  
    ), .init(  
    itemName: "Sugar"  
    ), .init(  
    itemName: "Oil"  
    ), .init(  
    itemName: "Chocolate"  
    ), .init(  
    itemName: "Corn"  
    )]  
}

提供的代码包括两个数据结构的定义:ProvisionModel 和 ModelData。 以下是每项的解释:

ProvisionModel:该结构表示清单中的一个供应项。 它符合可识别协议,该协议要求它具有唯一的标识符。 它具有以下属性:

id:一个字符串属性,保存使用 UUID 生成的唯一标识符。 每个 ProvisionModel 实例都会有一个不同的 id。

itemName:表示供应项目名称的字符串属性。

isAdded:一个布尔属性,指示该项目是否已添加到清单中。 它使用默认值 false 进行初始化。

ModelData:此类充当数据存储和单例,提供对供应项的共享访问。 它具有以下组件: 共享:ModelData 类型的静态属性,表示类的共享实例。 它遵循单例模式,允许跨应用程序访问同一实例。

items:一个数组属性,包含表示供应项的 ProvisionModel 实例。 该数组使用一组预定义的项目进行初始化,每个项目都使用特定的 itemName 进行初始化。 ModelData.shared 实例提供对此数组的访问。 总的来说,此代码为清单应用程序设置了数据模型。 ProvisionModel 结构定义每个供应项的属性,包括其唯一标识符以及是否已添加到清单中。 ModelData 类提供对供应项列表的共享访问,并遵循单例模式以确保访问和修改数据的一致性。

现在是 appIntent 的时候了!

swift 复制代码
struct MyActionIntent: AppIntent{  
  
    static var title: LocalizedStringResource = "Toggle Task State"  
    @Parameter(title: "Task ID")  
    var id: String  
    init(){  

    }  

    init(id: String){  
    self.id = id  
}  
  
func perform() async throws -> some IntentResult {  
    if let index = ModelData.shared.items.firstIndex(where: { $0.id == id }) {  
        ModelData.shared.items[index].isAdded.toggle()  

        let itemToRemove = ModelData.shared.items[index]  
        ModelData.shared.items.remove(at: index)  

        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {  
        ModelData.shared.items.removeAll(where: { $0.id == itemToRemove.id })  
        }  

        print("Updated")  
        }  

        return .result()  
    }  
}

提供的代码定义了一个名为 MyActionIntent 的结构,该结构符合 AppIntent 协议。 此结构表示在清单应用程序中切换任务状态的意图。 以下是对其组成部分的解释:

title(静态属性):该属性表示操作意图的标题。 它的类型为 LocalizedStringResource,它是用于本地化目的的本地化字符串资源。

id(属性装饰器):该属性用@Parameter装饰,表示需要切换的任务的ID。

init():这是结构的默认初始化程序。 它不执行任何特定的初始化。

init(id: String):此初始化程序允许您使用特定任务 ID 创建 MyActionIntent 实例。

Perform()(方法):AppIntent 协议需要此方法,并执行与 Intent 相关的操作。 以下是其实施细目: 它检查 ModelData.shared.items 数组中是否存在与意图中提供的 ID 匹配的任务。

如果找到匹配项,它将使用toggle() 方法切换任务的isAdded 属性。 这会改变任务的状态。 然后,它创建一个局部变量 itemToRemove 来存储切换的任务。 使用remove(at:)方法和找到任务的索引从ModelData.shared.items数组中删除任务。 延迟 2 秒后,使用removeAll(where:) 和检查匹配 ID 的闭包从 ModelData.shared.items 数组中删除 itemToRemove。

最后,"Updated"被打印到控制台。 return .result():该语句返回一个IntentResult实例,表示intent的完成,没有任何具体的结果值。 总的来说,此代码定义了一个意图,用于执行切换清单中任务状态的操作。 它访问 ModelData 的共享实例,以根据提供的 ID 查找和修改任务。

现在是时候用 AppIntents 替换图像了

css 复制代码
Button(intent: MyActionIntent(id: item.id)) {  
    Image(systemName: item.isAdded ? "checkmark.circle.fill":"circle")  
    .foregroundColor(.green)  
    }  
.buttonStyle(.plain)
相关推荐
__zhangheng6 小时前
Info.plist contained no UIScene configuration dictionary (looking for configura
macos·ios·objective-c·cocoa·swift
iOS民工11 小时前
iOS SSZipArchive 解压后 中文文件名乱码问题
ios
皮蛋很白15 小时前
IOS safari 播放 mp4 遇到的坎儿
前端·ios·safari·video.js
江上清风山间明月1 天前
Flutter DragTarget拖拽控件详解
android·flutter·ios·拖拽·dragtarget
Kaelinda2 天前
iOS开发代码块-OC版
ios·xcode·oc
ii_best2 天前
ios按键精灵自动化的脚本教程:自动点赞功能的实现
运维·ios·自动化
app开发工程师V帅3 天前
iOS 苹果开发者账号: 查看和添加设备UUID 及设备数量
ios
CodeCreator18183 天前
iOS AccentColor 和 Color Set
ios
iOS民工3 天前
iOS keychain
ios
m0_748238923 天前
webgis入门实战案例——智慧校园
开发语言·ios·swift