iOS18適配新功能:ControlWidgetToggle和ControlWidgetButton

为敢技术發表於2024-08-17

熱烈歡迎,請直接點選!!!

進入博主App Store主頁,下載使用各個作品!!!

注:博主將堅持每月上線一個新app!!

支援原創,部落格園原文連結:https://www.cnblogs.com/strengthen/p/18362397

文末可以有demo下載。

首先檢視WWDC2024的官方影片:

WWDC2024將 App 控制元件擴充套件到系統級別:https://developer.apple.com/cn/videos/play/wwdc2024/10157/

There are two types of controls: buttons and toggles. Buttons perform discrete actions, which can include launching your app, while toggles change a piece of boolean state, like turning something on or off.
【翻譯為】: 
控制元件有兩種型別:按鈕和切換按鈕。按鈕執行離散操作,包括啟動應用,而切換按鈕則更改布林狀態,例如開啟或關閉某些功能。

官方文件:Adding refinements and configuration to controls -https://developer.apple.com/documentation/WidgetKit/Adding-refinements-and-configuration-to-controls

詳細步驟:

1、點選連結:https://developer.apple.com/download/applications/ ,輸入Apple開發者賬號,下載Xcode 16.5 beta安裝包。

2、下載iOS 18.1 beta 2 模擬器執行時:https://developer.apple.com/download/all/,下載:【iOS 18.1 beta 2 Simulator Runtime】,也可以透過安裝Xcode16後,啟動Xcode16時,進行安裝iOS18模擬器執行時。

3、Xcode 16安裝模擬器執行時,請參考另一篇博文: https://www.cnblogs.com/strengthen/p/18348016

4、如果無法開啟Xcode16,請升級你的mac OS系統,升級為mac OS 14.5以上或者升級為mac OS 15 Beta::https://developer.apple.com/download/,下載macOS 15.1 beta 2。

5、新建Widget Extension:選擇Xcode16,螢幕左上角【File】-【New】-【Target】-【Widget Extension】

6、瞭解應用擴充套件的工作原理:https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html#//apple_ref/doc/uid/TP40014214-CH2-SW2

App Extension (應用擴充套件): 這是一個擴充套件應用功能的元件,可以在其他應用的上下文中提供特定的功能,如一個Share Extension可以讓使用者在Safari或Photos等其他應用內分享內容到你的應用。

Containing App (宿主應用): 這是指包含了上述擴充套件的主應用程式。也就是說,應用擴充套件是這個主應用的一部分,以擴充套件形式分發,並且與它同被打包到一個應用包中。

Host App (託管應用或主機應用): 這是指在當前上下文中呼叫或執行該應用擴充套件的應用。例如,如果使用者正在使用Safari並且觸發了一個Share Extension來分享一個網頁,那麼Safari就是這個應用擴充套件的host app。

(6.1)、app extension主要與其host app進行通訊,其通訊方式類似於事務處理:host app發出請求,app extension發出響應。

(6.2)、app extension與其宿主應用之間沒有直接通訊,當app extension執行時,宿主應用甚至不會執行。宿主應用和host app不通訊。

(6.3)、虛線表示app extension與其宿主應用之間可用的有限互動。app extension及其宿主應用,可以透過訪問私有定義的共享容器中的共享資料進行通訊。

(6.4)、在後臺,系統使用程序間通訊來確保host app和app extension能夠協同工作。在程式碼中無需考慮這種底層通訊機制,因為使用的是系統提供的更高階的 API。

7、 建立App Group共享儲存物件,用於app extension和app進行共享資料。

8、WWDC2024程式碼不夠詳細,直接上程式碼。

WidgetControlBundle.swift檔案:

//
//  WidgetControlBundle.swift
//  WidgetControl
//
//  Created by iNFC on 2025/1/1.
//

import WidgetKit
import SwiftUI

//  標記:表示這是應用的入口點。它將這個 WidgetBundle 註冊為應用的一部分。
@main
// Widget Bundle 是一種容器,允許你將多個控制元件組合在一起,以更好地組織和管理這些控制元件,並方便地在應用中啟用或禁用它們。
// WidgetControlBundle這是我們定義的 WidgetBundle 名稱。
struct WidgetControlBundle: WidgetBundle {
    // body屬性表示該 WidgetBundle 中包含的控制元件列表。你可以在這裡新增任意數量的控制元件。
    var body: some Widget {
        // 型別一:Toggles:切換控制元件,切換開關狀態。
        WidgetToggle()
        // 型別二:Buttons:執行離散操作,開啟App。
        WidgetButton()
    }
}

WidgetToggle.swift檔案:

//
//  WidgetToggle.swift
//  WidgetDemo
//
//  Created by iNFC on 2025/1/1.
//

import Foundation
import WidgetKit
import SwiftUI
import AppIntents
import UIKit
import Combine
import Intents

// 使用 ControlWidget 協議來定義一個基礎控制元件,定義了一個新的結構體 WidgetToggle,它實現了 ControlWidget 協議。
struct WidgetToggle: ControlWidget {
    // body屬性表示該 WidgetToggle 中包含的控制元件列表。
    var body: some ControlWidgetConfiguration {
        // 這是一個靜態配置,用於定義控制元件的結構:外觀和行為。
        StaticControlConfiguration(
            // 指定控制元件的唯一識別符號
            kind: "com.apple.ControlWidgetToggle",
            // 使用定義的 TimerValueProvider 作為值提供者。
            provider: TimerValueProvider()
        ) { isRunning in // :這是一個閉包,當獲取到定時器狀態時執行,其中 isRunning 表示當前的定時器狀態。透過這種方式,你可以使控制元件動態響應定時器狀態的變化,提供更好的使用者體驗。
            // 定義了一個切換控制元件,使用 isOn 屬性表示當前的狀態。 isOn 屬性和操作意圖 action。
            ControlWidgetToggle(
                "EasyShare",
                // 繫結到 TimerManager.shared.isRunning,表示定時器是否在執行。
                isOn: isRunning,
                // 指定了一個 ToggleTimerIntent 操作,當使用者點選控制元件時觸發該操作。
                action: ToggleTimerIntent()
            ){ isOn in // 這是一個閉包,用於根據 isOn 的值動態更新控制元件的圖示。
// 影像控制元件,用於顯示系統圖示。根據 isOn 的值選擇不同的圖示。 // Image(systemName: isOn ? "hourglass" : "hourglass.bottomhalf.filled") // 這是一個帶有文字和圖示的控制元件,用於顯示控制元件的狀態。 Label(isOn ? "Running" : "Stopped", // 根據 isOn 的值選擇顯示文字,執行時顯示“Running”,停止時顯示“Stopped”。 systemImage: isOn // 根據 isOn 的值選擇不同的圖示。 ? "hourglass.bottomhalf.filled" : "hourglass") // 這是一個方法,用於為控制元件新增操作提示,如“Start”或“Stop”。 .controlWidgetActionHint(isOn ? "Start" : "Stop") } // 設定控制元件的主題色為紫色,使其更具視覺吸引力。 .tint(.purple) } // 設定控制元件的顯示名稱為“iNFC”。 .displayName("iNFC") // 新增控制元件的描述。 .description("The most powerful NFC application") } } // 定義了一個新的結構體 TimerValueProvider,它實現了 ControlValueProvider 協議。 struct TimerValueProvider: ControlValueProvider { // 這是一個非同步函式,用於獲取當前的定時器狀態。 func currentValue() async throws -> Bool { // 獲取定時器的執行狀態。 return TimerManager.shared.fetchRunningState() } // 定義了一個預覽值,當無法獲取實際值時使用。 var previewValue: Bool { return false } } // 操作意圖(Intent),用於處理定時器的啟動和停止操作。定義了一個新的結構體,它實現了 SetValueIntent 和 LiveActivityIntent 協議。 struct ToggleTimerIntent: SetValueIntent, LiveActivityIntent { // 本地化字串資源 static var title: LocalizedStringResource = "WidgetToggle" // Parameter:這是一個引數註解,用於定義意圖中的引數。 // title:引數的標題。 @Parameter(title: "Toggle") // 一個布林值,表示定時器的執行狀態。 var value: Bool // 定義了執行意圖時的操作。 func perform() async throws -> some IntentResult { // 切換儲存開關狀態,呼叫 TimerManager 來設定定時器的執行狀態。 TimerManager.shared.setTimerRunning(value) // 儲存資料到Group App容器,傳遞給主應用 if let appGroupDefaults = UserDefaults(suiteName: "group.com.apple.iNFC") { if appGroupDefaults.bool(forKey: "widgetExtensionData") { appGroupDefaults.set(false, forKey: "widgetExtensionData") } else { appGroupDefaults.set(true, forKey: "widgetExtensionData") } } // 返回一個結果,表示意圖執行成功。 return .result() } }

TimerManager.swift檔案:

//
//  TimerManager.swift
//  WidgetDemo
//
//  Created by iNFC on 2025/1/1.
//

import SwiftUI
import Combine

class TimerManager: ObservableObject {
    static let shared = TimerManager()
    @Published var isRunning = false

    private init() {}

    func setTimerRunning(_ value: Bool) {
        isRunning = value
    }

    func fetchRunningState() -> Bool {
        return isRunning
    }
}

WidgetButton.swift檔案:

//
//  WidgetButton.swift
//  WidgetDemo
//
//  Created by iNFC on 2025/1/1.
//

import Foundation
import WidgetKit
import SwiftUI
import AppIntents
import UIKit
import Combine

// 使用 ControlWidget 協議來定義一個基礎控制元件,定義了一個新的結構體 WidgetToggle,它實現了 ControlWidget 協議。
struct WidgetButton: ControlWidget {
    // body屬性表示該 WidgetButton 中包含的控制元件列表。
    var body: some ControlWidgetConfiguration {
        // // 這是一個靜態配置,用於定義控制元件的結構:外觀和行為。
        StaticControlConfiguration(
            kind: "com.apple.ControlWidgetButton"
        ) {
            // 定義了一個開啟容器App的控制元件,
            ControlWidgetButton(action: OpenContainerAction()) {
                Label("WidgetButton", systemImage: "paperplane")
            } actionLabel: { isActive in
                if isActive {
                    Label("WidgetButton", systemImage: "hourglass")
                }
            }
        }
        .displayName("iNFC")
        .description("The most powerful NFC application")
    }
}

struct OpenContainerAction: AppIntent {
    // // 本地化字串資源
    static let title: LocalizedStringResource = "WidgetButton"
    // 定義了執行意圖時的操作。
    func perform() async throws -> some IntentResult & OpensIntent {
        // 儲存資料到Group App容器,傳遞給主應用
        if let appGroupDefaults = UserDefaults(suiteName: "group.com.apple.iNFC") {
            if appGroupDefaults.bool(forKey: "widgetExtensionData") {
                appGroupDefaults.set(false, forKey: "widgetExtensionData")
            } else {
                appGroupDefaults.set(true, forKey: "widgetExtensionData")
            }
        }
        // 重要:開啟容器App的操作
        return .result(opensIntent: OpenURLIntent(URL(string: "iNFC://")!))
    }
}

Github連結:demo下載

相關文章