SwiftUI

2024 年 2 月 4 日

用 SwiftUI 開發 macOS 的控制中心模組 App

已複製到剪貼板


在 macOS 上,使用 SwiftUI 的 MenuBarExtra 可以實現螢幕右上方的選單列功能。

macOS 控制中心模組
macOS 控制中心模組

SwiftUI 的 MenuBarExtra 概覽

MenuBarExtra 是 Mac 螢幕右上方選單列中的功能,它可以是一個圖像或文字,可以用來提供一些 App 的常用功能,即使我們的 macOS app 不在活躍的狀態。

補充

活躍(Active)指的是 App 為開啟的狀態,且使用者正在此 App。而不活躍指的一樣是 App 為開啟的狀態,但使用者正使用其他的 App。

實作 macOS 選單列功能

因為 MenuBarExtra 也是一種 SwiftUI Scene,所以跟 WindowGroup 一樣,會寫在 App 的 body 裡,而實作 MenuBarExtra 的方式有很多種。

用文字顯示 macOS 選單列功能

這是 MenuBarExtra 最簡單的做法:

@main
struct XcodeProjectApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        
        MenuBarExtra("App Menu Bar Extra") {
            StatusMenu()
        }
    }
}

這裡傳入了一個字串與 View,「App Menu Bar Extra」會顯示在選單列上,而點擊了之後則會顯示 StatusMenu()

用文字標題呈現 macOS 控制中心模組
用文字標題呈現 macOS 控制中心模組

用系統圖像顯示 macOS 選單列功能

我們也能使用系統的 SF Symbol 來呈現 MenuBarExtra:

@main
struct XcodeProjectApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        
        MenuBarExtra(
            "text for the accessibility system",
            systemImage: "swift"
        ) {
            StatusMenu()
        }
    }
}

這裡傳入了三個參數,第一個一樣是字串,但多了第二個 systemImage,這就是我們要用的系統圖像,而最後一樣是點擊了之後要顯示的 View。不過這裡比較特別的是,第一個參數的字串並不會顯示出來,因為那是給無障礙旁白播報用的。

用系統圖片呈現 macOS 控制中心模組
用系統圖片呈現 macOS 控制中心模組

用文字+系統圖像呈現 macOS 選單列功能

除了用文字或用圖像來顯示 MenuBarExtra,我們也能同時有圖加文:

@main
struct XcodeProjectApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        
        MenuBarExtra {
            StatusMenu()
        } label: {
            Label("Earbuds", systemImage: "earbuds")
                .labelStyle(.titleAndIcon) // 指定圖文都顯示
        }
    }
}

這個 Init 有比較大的彈性,它吃兩個參數,第一個是點擊後要顯示的 View,而第二個 label 則是會顯示在選單列上的 View,這裡我們就簡單的使用 Label 來實作,不過要注意,在 MenuBarExtra 裡,Label 會預設隱藏文字,所以我們要特別指定將文字與圖像都顯示。

用文字與圖片呈現 macOS 控制中心模組
用文字與圖片呈現 macOS 控制中心模組

MenuBarExtra 內容樣式

點擊 MenuBarExtra 展開的畫面有兩種樣式:menu 與 window。

預設 PullDown 的選單 menu 樣式

預設的 menu 樣式就像個選單那樣,我們可以直接擺上 Text、Button 之類的,系統就會直接幫我們垂直擺放,連 VStack 都不用寫。

@main
struct XcodeProjectApp: App {
    var body: some Scene {
        MenuBarExtra("Earbuds", systemImage: "earbuds") {
            Text("AirPods")
            Button("AirPods 2") {}
            Button("AirPods 3") {}
            Button("AirPods Pro") {}
            Divider()
            Button("AirPods Max") {}
        }
    }
}
macOS 控制中心模組:選單列的內容樣式
macOS 控制中心模組:選單列的內容樣式

以 Popover 呈現的 window 樣式

另一種 menuBarExtraStyle 就是 window,它會以 Popover 的方式展開我們的 menuBarExtra 功能,如果我們想要完全自己實作畫面,而非只是像功能選單那樣的話,就要使用這個 Style。

@main
struct XcodeProjectApp: App {
    var body: some Scene {
        MenuBarExtra("Airpods") {
            VStack(spacing: 12) {
                MenuBarExtraRowView(textName: "Airpods", imagesystemName: "airpods")
                MenuBarExtraRowView(textName: "Earbuds", imagesystemName: "earbuds")
                MenuBarExtraRowView(textName: "Airpods Pro", imagesystemName: "airpodspro")
                MenuBarExtraRowView(textName: "Airpods 3", imagesystemName: "airpods.gen3")
                Divider()
                MenuBarExtraRowView(textName: "Airpods Max", imagesystemName: "airpodsmax")
            }
            .padding(12)
            .frame(width: 180)
        }
        .menuBarExtraStyle(.window) // 指定為 Popover 的樣式
    }
}

struct MenuBarExtraRowView: View {
    
    var textName: String
    var imagesystemName: String
    
    var body: some View {
        HStack {
            Text(textName).bold()
            Spacer()
            Image(systemName: imagesystemName)
        }
    }
}
SwiftUI menuBarExtraStyle window
macOS 控制中心模組:自訂畫面的內容樣式

動態決定是否顯示 macOS 選單列功能

有時候,我們可能會希望 App 的選單列功能不要一直顯示,或是在特定情況下才出現。這時候我們就能使用含有 isInserted 這個參數的 Init 來實作 MenuBarExtra,這個參數是一個 Binding<Bool>,可以用來決定要不要顯示 MenuBarExtra。

@main
struct XcodeProjectApp: App {

    @State private var showMenuBarExtra = true

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        
        MenuBarExtra(
            "text for the accessibility system",
            systemImage: "swift",
            isInserted: $showMenuBarExtra
        ) {
            StatusMenu()
        }
    }
}

讓 App 僅出現在 macOS 選單列功能中

對於某些 App 來說,開發者可能不希望讓它出現在 Dock 中,一方面是所有的內容都透過了 macOS 選單列來提供,也沒有一個主要的 App 畫面,而另一方面也可能是不希望 App 能被使用者結束掉。

當我們把 macOS App 從 Dock 中隱藏掉之後,這會讓我們的 App 感覺起來更像是一個 Mac 的擴充功能!要實現這件事非常簡單,只要到我們的專案檔底下,找到 Info(Information Property List),並在裡面新增一個名為「Application is agent (UIElement)」的 key,然後將此設為 YES,就完成了從 Dock 中隱藏 App。

Application is agent (UIElement)
Application is agent (UIElement)

Application is agent (UIElement) 這個 key 的用意是告訴 Xcode,我們的 App 會在背景執行,且不要出現在 Dock 中。

分享文章

已複製到剪貼板

主題文章

查看 SwiftUI

超級感謝

關於 XcodeProject

XcodeProject 創立於 2023,致力於協助開發者探索 Apple 的創新世界,學習在 iOS、iPadOS、macOS、tvOS、visionOS 與 watchOS 上開發 App,發現眾多技術與框架,讓開發者獲得更多能力。


Contacts

Ricky Chuang

XcodeProject

RickyChuang.xcodeproj@gmail.com

XcodeProject 聯絡

contact.xcodeproj@gmail.com

最新文章