iOS 14 widget(時(shí)鐘、日歷)

github demo 附在結(jié)尾

本文主要介紹iOS14 widget開發(fā),自定義時(shí)鐘、日歷,app中切換widget背景圖片、顏色。
創(chuàng)建一個(gè)項(xiàng)目,增加widget extension


widget添加.png

系統(tǒng)默認(rèn)代碼(可以略過~~)

//  配置時(shí)間線
struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), configuration: ConfigurationIntent())
    }

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), configuration: configuration)
        completion(entry)
    }

    func getTimeline(for configuration: ConfigurationIntent, 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 currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}
// 數(shù)據(jù)源
struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationIntent
}
// 主視圖
struct CYWidgetExtensionEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        Text(entry.date, style: .time)
    }
}
// 入口
@main
struct CYWidgetExtension: Widget {
    let kind: String = "CYWidgetExtension"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            CYWidgetExtensionEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}
// Xcode 右側(cè)快捷預(yù)覽
struct CYWidgetExtension_Previews: PreviewProvider {
    static var previews: some View {
        CYWidgetExtensionEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

(成功略過,以下是主要實(shí)現(xiàn)代碼~~)

// 自定義時(shí)鐘
iOS14 widget 主要是基于swiftUI 布局,所以需要一些swiftUI基礎(chǔ)。
創(chuàng)建自定義時(shí)鐘表盤,指針

private struct CYWidgetCalendarView: View {
    var entry: SimpleEntry
    
    var body: some View {
        ZStack {
            Image(uiImage: entry.data.image)
                .resizable()
                .scaledToFill()
                .edgesIgnoringSafeArea(.all)

            ForEach(1..<13, id: \.self){ i in
                let sinX = sin(CGFloat(i)*30.0/180.0*CGFloat.pi)
                let sinY = -cos(CGFloat(i)*30.0/180.0*CGFloat.pi)
                let color = Color(UIColor(hexStr: entry.data.colorStr))
                Text("\(i)")
                    .foregroundColor(color)
                    .font(.system(size: RatioLen(14)))
                    .frame(width: RatioLen(16), height: RatioLen(16), alignment: .center)
                    .offset(x: 60*sinX, y: 60*sinY)
            }
            
            // sec
            /*
            Rectangle()
                .fill(Color.red)
                .frame(width: 2, height: RatioLen(40), alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
                .offset(y: -10)
                .rotationEffect(.init(degrees: Double(entry.clockTime().sec*6)))*/

            // min
            Rectangle()
                .fill(Color.blue)
                .frame(width: 2, height: RatioLen(30), alignment: .center)
                .offset(y: -15)
                .rotationEffect(.init(degrees: Double(entry.clockTime().min*6)))
            
            let hour = Double(entry.clockTime().min)*0.5 + Double(entry.clockTime().hour)*30.0
            // hour
            Rectangle()
                .fill(Color.primary)
                .frame(width: 2, height: RatioLen(20), alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
                .offset(y: -10)
                .rotationEffect(.init(degrees: Double(hour)))
            // link 只支持 systemMedium systemLarge
            /*
            Link(destination: URL(string: "https://www.baidu.com/")!, label: {
                    /*@START_MENU_TOKEN@*/Text("Link")/*@END_MENU_TOKEN@*/
                })*/

        } // widgetURL 支持全尺寸 推薦
        .widgetURL(URL(string: "https://www.baidu.com/clock"))
    }
}

關(guān)于在app中切換widget背景圖片 和表盤刻度顏色,需要在xcode targets增加app group, target和widget extension 都需要添加且需要一致,app group id 是bundle id 前加group. 就好了。

控制器中代碼實(shí)現(xiàn),存儲(chǔ)圖片到group沙盒路徑中,保存配置顏色值到group UserDefault

// group id
public let groupBundleKey: String = "group.www.cyan.com.CYWidgetNew"

// widget group 指定路徑
let widgetMainPath = "/Library/cyan/widget/"

// widget 時(shí)鐘顏色
let widgetClockColor = "widgetClockColor"
// widget 日歷顏色
let widgetCalendarColor = "widgetCalendarColor"

class ViewController: UIViewController {

    lazy var button1: UIButton = {
        let button = UIButton(type: .custom)
        button.backgroundColor = UIColor.purple
        button.setTitle("刷新 1", for: .normal)
        button.addTarget(self, action: #selector(save(btn:)), for:.touchUpInside)
        return button
    }()
    
    lazy var button2: UIButton = {
        let button = UIButton(type: .custom)
        button.backgroundColor = UIColor.purple
        button.setTitle("刷新 2", for: .normal)
        button.addTarget(self, action: #selector(save(btn:)), for:.touchUpInside)
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        creatUI()
    }
    
    func creatUI() {
        button1.frame = CGRect(x: 30, y: 70, width: 100, height: 40)
        self.view.addSubview(button1)
    
        button2.frame = CGRect(x: 150, y: 70, width: 100, height: 40)
        self.view.addSubview(button2)
    }
    
    @objc func save(btn: UIButton) {
        let fileManager = FileManager.default
        let url = fileManager.containerURL(forSecurityApplicationGroupIdentifier: groupBundleKey)

        let mainPath = (url?.path ?? "") + widgetMainPath
        var isFolder: ObjCBool = false
        let isExists = fileManager.fileExists(atPath: mainPath, isDirectory: &isFolder)
        if isExists == false || isFolder.boolValue == false {
            try? fileManager.createDirectory(atPath: mainPath, withIntermediateDirectories: true, attributes: nil)
        }
        // temp1  temp2
        var name = ""
        var color = "000000"
        switch btn {
        case button1:
            name = "temp1"
            color = "000000"
        default:
            name = "temp2"
            color = "ffffff"
        }
        
        let data = UIImage(named: name)?.pngData()
        try? data?.write(to: URL(fileURLWithPath: mainPath + "widget1.jpg"), options: .atomic)

        print("mainPath : \(mainPath)")
        
        let userdefaults = UserDefaults.init(suiteName: groupBundleKey)
        userdefaults?.setValue(color, forKey: widgetClockColor)
        userdefaults?.setValue(color, forKey: widgetCalendarColor)
        userdefaults?.synchronize()
        
        // 立即刷新所有組件
        if #available(iOS 14.0, *) {
            DispatchQueue.main.async {
                WidgetCenter.shared.reloadAllTimelines()
                print("刷新成功")
            }
       
        } else {
            // Fallback on earlier versions
        }
    }

}

widget data 中從group沙盒中獲取圖片,從group userdefault中獲取 配置顏色

// group id
public let groupBundleKey: String = "group.www.cyan.com.CYWidgetNew"

// 圖片路徑 存儲(chǔ)名
let clockWidgetPath = "/Library/cyan/widget/widget1.jpg"

// widget 時(shí)鐘顏色
let widgetClockColor = "widgetClockColor"
// widget 日歷顏色
let widgetCalendarColor = "widgetCalendarColor"

let widgetTargetWidth: CGFloat = 329
let iPhoneHeight = UIScreen.main.bounds.size.height

enum CYWidgetType {
    case clock
    case calendar
    
    var path: String {
        clockWidgetPath
    }

}

struct CYWidgetData {
    let title: String
    let imageName : String
    let image   : UIImage
    let colorStr: String
}

struct CYWidgetDataLoader {
    
    static func getWidgetData(_ type: CYWidgetType) -> CYWidgetData {
        let userdefaults = UserDefaults.init(suiteName: groupBundleKey)
        var colorStr = ""
        switch type {
        case .calendar: colorStr = userdefaults?.string(forKey: widgetCalendarColor) ?? "ffffff"
        case .clock: colorStr = userdefaults?.string(forKey: widgetClockColor) ?? "ffffff"
        }
        
        let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupBundleKey)
        let imagePath = (url?.path ?? "") + type.path
        print("imagePath:\(imagePath)")
        if let image = UIImage(contentsOfFile: imagePath) {
            return CYWidgetData(title: "title:cyan", imageName: "", image: image, colorStr: colorStr)
        }
        return CYWidgetData(title: "title:cyan", imageName: "", image: UIImage(named: "widgetBackground")!, colorStr: colorStr)
    }
    
}

demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容