ios 小组件和数据共享

创建主工程就不必讲了

1 创建小组件
创建子工程 [new Target ] 选择 [ Widger Extension]

小组件入口是WidgetBundle文件,可以进行多个小组件的调试

TestWidget2文件是主要操作,小组件使用swiftUI布局,使用 AppIntent进行事件处理,TestDataUserDefalut 用的userdefault 和主程序数据同步,下面详细讲解

dart 复制代码
//
//  TestWidget2.swift
//  TestSwift6Demo
//
//  Created by ITHPNB04296 on 11/3/25.
//

import WidgetKit
import SwiftUI
import AppIntents


struct TestWidget2:Widget{
    let kind:String = "TestWidget2"
    var body: some WidgetConfiguration{
        StaticConfiguration(kind: kind, provider: MyWidget2Provider()) {entry in
            TestWidget2View(entry: entry ,ganmeStatus: entry.state)
                .containerBackground(for: ContainerBackgroundPlacement.widget) {
                    //背景色
                    Color.yellow
                }
        }
        .configurationDisplayName("TestWidget2")
        .description("show Testwidget2")
        .supportedFamilies([.systemMedium])
    }
    
   
}


struct TestWidget2View:View {
    var entry: MyWidget2Provider.Entry
    @Environment(\.widgetFamily) var family:WidgetFamily
    var ganmeStatus:Test2State
//    var selectedCharacter:CharacterDetail
    var body: some View {
        VStack{
            Text("\(ganmeStatus.date.description)")
            Button("\(ganmeStatus.state)") {
                print("点击按钮1 直接就跳转到主APP了")
                
            }
            
           
            if entry.running{
                createRunningView()
            }else{
                createStaticView()
            }
            
         
        }
        

    }
    
    func createRunningView()->some View{
           //点击一次更新了一次
        return VStack{
            Button("点击按钮2", intent: Test2Intent())
            //该Text 无法设置停止时间
//            Text(entry.date, style: Text.DateStyle.timer)
//                .font(Font.system(size: 15.0))
//                .fontWeight(.bold)
//                .multilineTextAlignment(.center)
//                .contentTransition(.numericText(countsDown: entry.state.duration > 0))
            
            //新Text样式,可以设置停止时间
            Text(timerInterval: entry.daterange,pauseTime: entry.pauseTime,countsDown: entry.countsDown,showsHours: entry.showsHours)
        }
    }
    
    
    func createStaticView()->some View{
        return VStack{
            Button("点击按钮2", intent: Test2Intent())
            Text(entry.date, style: Text.DateStyle.date)
                .font(Font.system(size: 15.0))
                .fontWeight(.bold)
                .multilineTextAlignment(.center)
            //过渡动画 countsDown 参数用来控制是否向下滚动
                .contentTransition(.numericText(countsDown: entry.state.duration > 0))
        }
        
    }
}

//添加按钮点击事件行为
struct Test2Intent:AppIntent{
  
    
    static var title: LocalizedStringResource = "XXX"
    
  
    //点击事件函数是异步的可以做一些耗时操作,比如网络请求等
    func perform() async throws -> some IntentResult {
        //也可以通过App group 和主程序共享数据
        
      
        //点击事件更新
        let isrunning = TestDataUserDefalut().get()
        TestDataUserDefalut().set(!isrunning)
        
        return .result()
    }
}

struct Test2State{
    var state:String
    var date:Date{
        return Date()
    }
    var duration:Int = 10
    
    
}

struct MyWidget2Entry:TimelineEntry{
    var date: Date
    
    //默认区间是0
    var daterange:ClosedRange<Date> = Date()...Date()
    //默认没有开启
    var running:Bool {
        //添加状态控制
        return TestDataUserDefalut().get()
    }
    /// The relevance of a widget's content to the user.
    var relevance: TimelineEntryRelevance?
    
    //添加适配新的TimerText
    
    //停止时间
    var pauseTime: Date = Date()
    //倒计时
    var countsDown = false
    
    var showsHours = true
    
    
    //携带其他额外数据
    var state:Test2State
}


struct MyWidget2Provider: TimelineProvider{
    //没有数据时候占位,仅初始化时候调用一次,数据没加载上来时候,添加占位数据
    func placeholder(in context: Context) -> MyWidget2Entry {
        return MyWidget2Entry(date: Date(), state: Test2State(state: "init"))
    }
    //生成预览照,给系统组件库使用
    func getSnapshot(in context: Context, completion: @escaping @Sendable (MyWidget2Entry) -> Void) {
        let date = Date()
        let entry:MyWidget2Entry
        print("getSnapshot")
        entry = MyWidget2Entry(date: date, state: Test2State(state:"perview"))
        completion(entry)
    }
    
    //实时更新时间线,每次点击按钮时候调用两次 ? 调用两次没理解
    func getTimeline(in context: Context, completion: @escaping (Timeline<MyWidget2Entry>) -> Void) {
        let date = Date()
        
        let endtime:Date = Calendar.current.date(byAdding: .minute, value: 10, to: date)!
        
        let entry = MyWidget2Entry(date: date, daterange: date...endtime,countsDown: true, state: Test2State(state:"start"))
        
        //下一次更新时候
        let nextUpdateDate = Calendar.current.date(byAdding: .second, value: 1, to: date)!
        //时间线,\
        /*
         atend -当时间线中的所有条目都已显示完毕后,才会重新加载时间线。固定时间点加载,比如天气
         never - 不会自动刷新小组件内容,只有主app主动去刷新数据
         after(Date) - 指定一个未来的日期和时间
         
         */
        let timeline = Timeline(entries: [entry], policy: TimelineReloadPolicy.after(nextUpdateDate))
        print("时间线方法1更新了 ")
        completion(timeline)
    }
    

}



//更新共享数据
struct TestDataUserDefalut{
    func get()->Bool{
        let sharedDefaults = UserDefaults(suiteName: "group.testwidget1.demo")
        let isrunning = sharedDefaults?.value(forKey: "sharedRunning") as? Bool ?? false
        return isrunning
       
    }
    
    func set(_ state:Bool){
        let sharedDefaults = UserDefaults(suiteName: "group.testwidget1.demo")
        sharedDefaults?.set(state, forKey: "sharedRunning")
        sharedDefaults?.synchronize()
        
    }
}

小组件和主工程进行数据同步,也就是进程间通信
***配置 App Groups

  1. 打开 Xcode,选择你的主 App 目标(Target)。
  2. 在 Signing & Capabilities 中,点击 + Capability。
  3. 搜索 App Groups 并添加。
  4. 创建一个新的 App Group(如 group.com.yourcompany.shared)。
  5. 在 Widget Extension / App Clips 目标中重复上述步骤,选择相同的 App Group。***

我的demo使用plist文件也就是userdefault 同步数据当然也可以使用数据库同步数据,我看同事使用SwiftData(ios 17之后才能使用)同步数据 swiftData官方文档 ,我选择的appgroup 名字是 "group.testwidget1.demo",这个不是包名,TestWidget2 里面的 TestDataUserDefalut 就是小程序同步数据代码,下面看一下主程序App同步代码,添加包括刷新通知等,讲解比较详细

dart 复制代码
class ViewController: UIViewController {
    lazy var stateLab:UILabel = {
        let size = UIScreen.main.bounds.size
        let labwidth = 100.0
        let labheight = 50.0
        let lab  = UILabel(frame: CGRectMake((size.width - labwidth) / 2.0, (size.height - labheight) / 2.0, labwidth, labheight))
        view.addSubview(lab)
        lab.backgroundColor = UIColor.green
        lab.font = UIFont.systemFont(ofSize: 28)
        lab.textColor = UIColor.red
        lab.textAlignment = .center
        return lab
    }()
  
    func updateState(_ state:Bool){
        DispatchQueue.main.async {
            self.stateLab.text = state ? "打开" : "关闭"
        }
        
    }
    func addTest(){
        /*
         配置 App Groups
             1.    打开 Xcode,选择你的主 App 目标(Target)。
             2.    在 Signing & Capabilities 中,点击 + Capability。
             3.    搜索 App Groups 并添加。
             4.    创建一个新的 App Group(如 group.com.yourcompany.shared)。
             5.    在 Widget Extension / App Clips 目标中重复上述步骤,选择相同的 App Group。
         */
        
        let appGroup = "group.testwidget1.demo"
        let sharedDefaults = UserDefaults(suiteName: appGroup)
        let isrunning = sharedDefaults?.value(forKey: "sharedRunning") as? Bool ?? false
        updateState(isrunning)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        
        let sharedDefaults = UserDefaults(suiteName: "group.testwidget1.demo")
        let isrunning = sharedDefaults?.value(forKey: "sharedRunning") as? Bool ?? false
        sharedDefaults?.set(!isrunning, forKey: "sharedRunning")
        print("\(!isrunning)")
        updateState(!isrunning)
        //通知小组件刷新
        notifiUpdateWidget()
        
    }
    
    
    func notifiUpdateWidget(){
        //通知小组件刷新,刷新所有小组件
        WidgetCenter.shared.reloadAllTimelines()
        
        //刷新指定的小组件,小组件那边注册的kind
//        WidgetCenter.shared.reloadTimelines(ofKind: "TestWidget1")
        
        
        
        //获取配置去刷新小组件
//        WidgetCenter.shared.getCurrentConfigurations { result in
//            guard case .success(let success) = result else {
//                return
//            }
//            
            if let widget = success.first(
                where:{ widget in
                    let intent = widget.configuration as ConfigurationAppIntent
                    return intent?.character == ""
                }
            ){
                WidgetCenter.shared.reloadTimelines(ofKind: widget.kind)
            }
//        }
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        addTest()
        }
       }

苹果官方小组件文档

苹果官方swiftUI文档

相关推荐
ZRD11123 小时前
Swift flatMap 和 compactMap
ios·swift
Doris Liu.7 小时前
苹果“被盗设备保护”的取证意义
科技·程序人生·安全·ios·智能手机·系统安全·安全架构
ii_best8 小时前
【iOS15/16脚本】多巴胺越狱roothide模式iOS按键精灵安装方式
ios
木木黄木木1 天前
Theos环境搭建与XM文件开发指南,以及iOS弹窗源码分享
ios·c#
木木黄木木1 天前
iOS插件,Theos环境搭建与XM文件开发指南(完善版本)
ios
帅次1 天前
Flutter:StatelessWidget vs StatefulWidget 深度解析
android·flutter·ios·小程序·swift·webview·android-studio
货拉拉技术1 天前
货拉拉基于“声明式”的埋点方案实践
ios·程序员
帅次1 天前
Flutter Widget 体系结构解析
android·flutter·ios·小程序·xcode·web app·dalvik
Mr.NickJJ2 天前
Swift系列02-Swift 数据类型系统与内存模型
开发语言·ios·swift