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文档

相关推荐
HarderCoder4 小时前
iOS 知识积累第一弹:从 struct 到 APP 生命周期的全景复盘
ios
叽哥15 小时前
Flutter Riverpod上手指南
android·flutter·ios
用户092 天前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan2 天前
iOS26适配指南之UIColor
ios·swift
权咚2 天前
阿权的开发经验小集
git·ios·xcode
用户092 天前
TipKit与CloudKit同步完全指南
ios·swift
法的空间3 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918413 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
00后程序员张3 天前
iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
android·安全·ios·小程序·uni-app·iphone·webview
Magnetic_h3 天前
【iOS】设计模式复习
笔记·学习·ios·设计模式·objective-c·cocoa