
Fun is one of the most important and underrated ingredients in any successful venture. If you're not having fun, then it's probably time to call it quits and try something else.
- Richard Branson
我們在上一章中建立了一個更引人注目的細節視圖,如果你還沒有完成這個作業,則可以先下載完整專案來使用: http://www.appcoda.com/resources/swift59/swiftui-foodpin-detail-view.zip.
在本章中,我們的重點在於改進導覽列與細節視圖,以使 App UI 更棒且更具彈性。透過這個練習,你將學到下列幾個主題:
讓我們開始吧 !
iOS SDK 有一些 可以使用的內建顏色 例如:我們自訂導覽列標題的顏色時,將顏色設定為「.systemRed」。
navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.systemRed, .font: UIFont(name: "ArialRoundedMTBold", size: 35)!]
如果你想要使用自己的顏色怎麼辦呢?假設你訪問 flatuicolors.com ,並記下這個色碼:
rgb(218, 96, 51)
你如何在我們的程式碼中使用這個顏色呢?UIColor 類別有一個可以接受紅色、綠色與藍色的分量的初始器。要使用上列的色碼來建立 UIColor 的實例,則可以初始化 UIColor 如下:
UIColor(red: 218/255, green: 96/255, blue: 51/255, alpha: 1.0)
這裡我們使用自訂的 RGB 值來實例化一個 UIColor 物件。由於 UIColor 只接受範圍是 0 至1 的 RGB 值,所以我們必須在初始化期間,將每個 RGB 分量除以 255。
在 FoodPinApp.swift 檔案中,你可以更新下列的程式碼來使用自訂的紅色:
navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor(red: 218/255, green: 96/255, blue: 51/255, alpha: 1.0), .font: UIFont(name: "ArialRoundedMTBold", size: 35)!]
navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor(red: 218/255, green: 96/255, blue: 51/255, alpha: 1.0), .font: UIFont(name: "ArialRoundedMTBold", size: 20)!]
在我們深入討論自訂顏色之前,我想要岔開一下話題來說明 Swift 中名為「擴展」(Extension )的強大功能,這是一個你不想錯過的功能。
Swift 中的「擴展」功能,可以讓你為現有類別、結構與列舉新增功能,這意味著什麼呢?你如何使用這個功能來編寫更好的程式碼呢?
我們以這行程式碼為例:
UIColor(red: 218/255, green: 96/255, blue: 51/255, alpha: 1.0)
要符合初始化的要求,我們必須將每個 RGB 分量除以 255,這聽起來有點麻煩?我們可以簡化上列的程式碼如下嗎?
UIColor(red: 218, green: 96, blue: 51)
這裡我們可以應用擴展來擴充 UIColor 的功能。儘管 UIColor 是 iOS SDK 提供的內建類別,但我們可以使用 Swift 擴展來為其加入更多的功能。
我們回到 FoodPin 專案來看看如何建立擴展。為了更好組織我們的專案,首先建立一個用於儲存擴展檔案的群組,在專案導覽器中的「FoodPin」資料夾按右鍵,並選擇「New Group」,然後將群組命名為「Extensions」。
接下來,在「Extensions」上按右鍵,並選擇「New File...」,然後選取「Swift File」模板,將檔案命名為「UIColor+Ext.swift」。建立檔案後,更新程式碼如下:
import UIKit
extension UIColor {
convenience init(red: Int, green: Int, blue: Int) {
let redValue = CGFloat(red) / 255.0
let greenValue = CGFloat(green) / 255.0
let blueValue = CGFloat(blue) / 255.0
self.init(red: redValue, green: greenValue, blue: blueValue, alpha: 1.0)
}
}
要為現有的類別宣告擴展,則以 extension 關鍵字開頭,後面接著你想擴展的類別,而本例是 UIColor 類別。
我們實作另外的便利型初始器(Convenience Initializer ),其接受「red」、「green」與「blue」等三個參數。在初始器的主體中,我們將給定的 RGB 值除以 255 來執行轉換, 最後我們使用轉換後的RGB 分量來呼叫原來的init 方法。
這就是如何利用 Swift 擴展來加入另一個初始器至內建類別的方式,現在新的初始器已經可以使用了,你可以在 FoodPinApp.swift 中修改下列的程式碼:
navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor(red: 218/255, green: 96/255, blue: 51/255, alpha: 1.0), .font: UIFont(name: "ArialRoundedMTBold", size: 35)!]
navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor(red: 218/255, green: 96/255, blue: 51/255, alpha: 1.0), .font: UIFont(name: "ArialRoundedMTBold", size: 20)!]
改為:
navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor(red: 218, green: 96, blue: 51), .font: UIFont(name: "ArialRoundedMTBold", size: 35)!]
navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor(red: 218, green: 96, blue: 51), .font: UIFont(name: "ArialRoundedMTBold", size: 20)!]
現在,程式碼看起來簡潔多了,對吧?
那麼你還能使用原來的初始器嗎?絕對可以,這個新的初始器只是簡化了冗餘的轉換,來讓你輸入較少的程式碼。
隨著深色模式的導入,你的 App 應該要能因應淺色外觀與深色外觀。到目前為止, FoodPin App 在深色模式下運作良好,這項成功的主要原因之一是我們廣泛使用 Apple 所提供的系統顏色,這些系統顏色經過專門設計,可無縫適應淺色模式和深色模式。

除了系統顏色外,Apple 也導入了另一種名為「語義化顏色」(Semantic Colors )的內建顏色。「語義化顏色」是描述顏色含義的顏色,以下提供幾個例子:
UIColor.label - 包含主要內容的文字標籤的顏色。 UIColor.secondaryLabel - 包含次要內容的文字標籤的顏色。 UIColor.systemBackground - 介面主背景的顏色。 同樣的,語義化顏色也被設計為自適應,並對不同的介面樣式回傳不同的顏色值。
依照 Apple 的準則,Apple 鼓勵開發者使用系統顏色與語義化顏色,因為它大大簡化了支援深色模式的過程。建議避免建立具有寫死顏色值的 UIColor 物件,但是如果我們想要使用自己的顏色而不使用內建顏色(例如:用於導覽列標題的顏色),該如何做呢?
通常,我們使用素材目錄來儲存圖片與圖示,素材目錄也提供了管理顏色的功能。要建立顏色集,請開啟 Assets 資料夾,並在任何空白區域按右鍵來訪問內容選單,選擇「Color Set」來產生一個新顏色集,如圖 13.2 所示。將顏色集命名為「NavigationBarTitle」,因為我們打算專門為導覽列標題定義一種新顏色。

你可在顏色集中定義兩種不同的顏色,「Any Appearance」的顏色設定為「#DA6033」, 「Dark Appearance」的顏色則設定為「#D35400」。要設定顏色為 16 進位格式時,在屬性檢閱器中更改輸入方法為「8-bit Hexidecimal」,然後你就可以設定十六進位(Hex)值, 如圖1 3.3 所示。

稍後,當你使用這個顏色集時,系統即會依照使用者的系統設定(例如:淺色 / 深色模式)自動為你挑選顏色。
現在再次開啟 FoodPinApp.swift 檔,我們不使用紅色,而是變更導覽列標題的顏色為我們剛才定義的顏色。修改init() 中的程式碼,並更改 largeTitleTextAttributes 與 titleText Attributes 的值,如下所示:
navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor(named: "NavigationBarTitle") ?? UIColor.systemRed, .font: UIFont(name: "ArialRoundedMTBold", size: 35)!]
navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor(named: "NavigationBarTitle") ?? UIColor.systemRed, .font: UIFont(name: "ArialRoundedMTBold", size: 20)!]
分別在淺色及深色模式下執行 App,你應該可注意到其顏色差異。

何謂「動態型別」?你可能沒聽過動態型別,但你應該有看過設定畫面(「設定→輔助使用→顯示與文字大小→放大文字」),如圖 13.5 所示。

動態型別並不是一個新的功能,從 iOS 7 時就被導入,它讓使用者可以自訂 App 的文字大小來符合他們的偏好,不過只有採用動態型別的 App 才能因應文字大小的變更。
Apple 提供的所有內建的原廠 App 都採用動態型別,至於第三方 App,則是取決於開發者的決定。雖然 Apple 並沒有強制要求開發者要支援動態型別,但是其強烈建議為使用者提供根據自己的偏好調整文字大小的功能。
那麼,該如何採用動態型別呢?使用 SwiftUI 的話,採用動態型別簡直是小事一件。你可能還記得我們設定所有的標籤使用文字樣式,要採用動態型別,你只需要使用文字樣式而不是固定的字型樣式,例如:下列程式碼指示 iOS 使用「headline」文字樣式:
Text("PHONE")
.font(.system(.headline, design: .rounded))
那麼,你該如何使用內建的模擬器來測試動態型別呢?其中一種方式是到「設定(Settings )→輔助使用(Accessibility )→顯示與文字大小(Display & Text Size )→放大文字(Large Text )」中變更模擬器的設定。啟用「更大的輔助使用字體大小」(Larger Accessibility Sizes ),並將滑桿往右拖曳來加大字型。
另一個更方便的方式是在 Xcode 中使用environment 修飾器。在模擬器中執行 App 時, 點選「Environment Overrides」按鈕,並開啟「Text」選項,你可以使用動態型別滑桿來調整字型大小。在模擬器中,App 應該會響應文字大小的變化,如圖 13.6 所示。

以上就是使用單獨的模擬器來測試動態型別的方式。如果你喜歡更方便的方式,則可以將 environment 修飾器加入視圖來測試動態型別。
我們開啟 RestaurantDetailView.swift,並更新#Preview 程式碼區塊如下:
#Preview {
NavigationStack {
RestaurantDetailView(restaurant: Restaurant(name: "Cafe Deadend", type: "Coffee & Tea Shop", location: "G/F, 72 Po Hing Fong, Sheung Wan, Hong Kong", phone: "232-923423", description: "Searching for great breakfast eateries and coffee? This place is for you. We open at 6:30 every morning, and close at 9 PM. We offer espresso and espresso based drink, such as capuccino, cafe latte, piccolo and many more. Come over and enjoy a great meal.", image: "cafedeadend", isFavorite: true))
.environment(\.dynamicTypeSize, .xxxLarge)
}
.tint(.white)
}
在上列的程式碼中,我們加上 environment 修飾器,並設定動態型別大小為「.xxxLarge」, 然後預覽會以加大的字型來渲染UI,如圖 13.7 所示。

在本章中,你學習了如何應用顏色集來調整顏色,以適應淺色外觀與深色外觀。我們還討論了動態型別的用法,它透過讓使用者選擇他們喜愛的字型大小來提供額外的彈性。Apple 大力鼓勵所有的iOS 開發者採用這個技術,因為它為使用者提供選擇。一些視力較差的人可能喜歡較大的文字大小,而其他人可能更喜歡較小的文字大小,只要可行,強烈建議在你的App 中支援動態型別,以適應使用者偏好。
在本章所準備的範例檔中,有最後完整的 Xcode 專案及作業的解答可供你下載參考:http://www.appcoda.com/resources/swift59/swiftui-foodpin-dynamic-type.zip 。
你也可以進一步參考Apple 的: 《人機介面指南》(iOS Human Interface Guidelines ) 。