在前一篇教學文章中,我們在 SwiftUI 創建了動畫導航選單 (navigation menu)。那如果我們想要重用其他專案的程式碼呢?當然,我們也可以就這樣從一個專案複製程式碼到另一個專案上;但更好的方法就是利用 Swift Packages 重用程式碼。
Swift Packages 是可重用的組件,開發者可以把組件匯入到自己的專案中。Swift Package Manager 是一個內建的工具,用於創建和管理 Packages,如此一來,我們就可以以 Packages 簡單地分享可重用的程式碼。
在這篇教學文章中,我會帶大家看看如何創建 Swift Packages,並把動畫導航選單的程式碼,轉換為可重用的 SwiftUI 組件。
請注意,我用了 Xcode 13 編寫範例程式碼。不過即使你正在使用較舊版本的 Xcode,應該還是可以跟上文章的步驟。
創建 Swift Packages
要創建 Swift Packages 有兩個方法:利用 Command Line 或 Xcode。
利用 Command Line
要用 Command Line 創建 Swift Package,我們可以打開 Terminal 並輸入以下 Command:
mkdir AnimatedMenuBar
cd AnimatedMenuBar
swift package init
資料夾的名稱就是 Package 的名稱,在這裡,我們使用了 AnimatedMenuBar。按下 return 後,我們會看到以下的訊息。
Creating library package: AnimatedMenuBar
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/AnimatedMenuBar/AnimatedMenuBar.swift
Creating Tests/
Creating Tests/AnimatedMenuBarTests/
Creating Tests/AnimatedMenuBarTests/AnimatedMenuBarTests.swift
這會創建出 Swift Package 的基本架構,包括 Source 和 Test。我們可以編輯 README.md 文件,來提供 Package Description。Package.swift
是一個 manifest 檔案,它會利用 PackageDescription
模塊來定義 Package 的名稱及內容。
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "AnimatedMenuBar",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "AnimatedMenuBar",
targets: ["AnimatedMenuBar"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "AnimatedMenuBar",
dependencies: []),
.testTarget(
name: "AnimatedMenuBarTests",
dependencies: ["AnimatedMenuBar"]),
]
)
請注意,Package 的 manifest 必須以字串 // swift-tools-version:
開頭,然後寫上版本號碼,例如:// swift-tools-version:5.3
。
利用 Xcode
如果想使用 Xcode 來創建 Package,我們可以到 File > New > Project…,然後在 Multiplatform 下選擇 Swift Package。
更新 Source 檔案
在 Sources
檔案下的 AnimatedMenuBar.swift
文件中,只包含了 Xcode 的預設內容:
public struct AnimatedMenuBar {
public private(set) var text = "Hello, World!"
public init() {
}
}
我們需要添加創建動畫導航選單的程式碼。在這個範例中,我們可以重用上一篇文章的程式碼。
import SwiftUI
@available(iOS 14, macOS 11.0, *)
public struct AnimatedMenuBar: View {
@Binding var selectedIndex: Int
@Namespace private var menuItemTransition
var menuItems = [ "Travel", "Nature", "Architecture" ]
public init(selectedIndex: Binding<Int>, menuItems: [String] = [ "Travel", "Nature", "Architecture" ]) {
self._selectedIndex = selectedIndex
self.menuItems = menuItems
}
public var body: some View {
HStack {
Spacer()
ForEach(menuItems.indices) { index in
if index == selectedIndex {
Text(menuItems[index])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Color.purple))
.foregroundColor(.white)
.matchedGeometryEffect(id: "menuItem", in: menuItemTransition)
} else {
Text(menuItems[index])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Color( red: 244, green: 244, blue: 244)))
.onTapGesture {
selectedIndex = index
}
}
Spacer()
}
}
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.animation(.easeInOut, value: selectedIndex)
}
}
因為是一個 Swift Package,我們需要把 AnimatedMenuBar
結構設置為 public
。另外,我們也需要創建一個 public access level 的客製化 init
。
除了上述兩點之外,其他程式碼都幾乎都是一樣的。你可以也發現到另一個不同之處,就是我們用了 @available
屬性 (attribute) 來標註有可用性 (availability) 資訊的結構,這行程式碼表示這個結構只適用於 iOS 14 和 macOS 11.0(或更新的版本)。
修改 Test 程式碼
在預設情況下,Xcode 會產生一個 test 資料夾,讓我們放 automated test。我們可以把 test 程式碼放在 AnimatedMenuBarTests.swift
檔案中。但是在這個範例專案中,我們不會編寫程式碼。你可以如此註解排除 (comment out) 這行程式碼:
// XCTAssertEqual(AnimatedMenuBar().text, "Hello, World!")
添加 Dependency(非必須)
雖然這個 Package 不會依賴其他 Swift Packages,但如果需要的話,你還是可以編輯 dependencies
部分來添加 Dependent Package:
dependencies: [
.package(url: "https://url/to/dependency", from: 1.0.0)
],
添加支援平台
Swift Packages 應該提供多平台支援,但如果某個 Package 只支援特定平台,我們可以使用 Package.swift
中的 platforms
屬性。讓我們看看以下例子:
platforms: [
.iOS(.v14),
.macOS(.v11)
],
以這個 Package 為例,它會支援 iOS 14 和 macOS 11.0(或更新版本)。
把 Package 發佈到 GitHub
做好所有更改之後,我們就應該能夠 build package 以在本機 (local) 使用。如果要與團隊或社群中的開發者共享,我們就可以在 GitHub 上發佈 Package。
到 Xcode 選單選擇 Source Control > New Git Repositories…,以創建一個新的程式庫。
然後,轉到 Source Control Navigator,右擊 Remotes 並選擇 New “AnimatedMenuBar ” Remote…。
如果你已經在 Xcode 設定好了 GitHub 帳戶,就應該可以創建一個遠端程式庫。把程式庫命名為 AnimatedMenuBar,並輸入 Package Description。你可以選擇把 Package 設為 public 或 private,在這個範例中,我會設定為 public。
點擊 Create 按鈕後,Xcode 會在 GitHub 上創建程式庫,並把本機檔案上傳到程式庫。
目前,我們還沒有為 Package 設定版本號碼。要設定版本號碼,我們可以到 Source Control Navigator,右擊 Initial Commit 的條目,並選擇 Tag。
接著,把 Tag 設定為 1.0.0
,並點擊 Create 來確認。
我們剛剛做的改變只限於本機,要為遠端程式庫設定 Tag,我們就需要把改變 push 到 GitHub。到 Xcode 選單,選擇 Source Control > Push,請記得要勾選 Include tags
方格,然後點擊 Push 按鈕。
完成了!我們已經把 Swift Package 發佈到 GitHub 了。你可以在這裡找到我們這個 Swift Package。
使用 Swift Package
要在其他 Xcode 專案中使用 Swift Package,選擇 File > Add Package…,並在搜尋欄輸入 Package URL。
然後,Xcode 就應該會顯示 Package 的描述和版本。點擊 Add Package,就可以下載並添加 Package 到專案中。
下載 Package 後,我們會在 Project Navigator 中的 Package Dependencies 下看到 Package。現在,我們就可以在專案中使用 AnimatedMenuBar
視圖了。
我們只需要 import
AnimatedMenuBar Package,並如此使用 AnimatedMenuBar
視圖就可以了:
import SwiftUI
import AnimatedMenuBar
struct ContentView: View {
@State var tabIndex = 0
var body: some View {
AnimatedMenuBar(selectedIndex: $tabIndex)
}
}
總結
在這篇教學文章中,我們介紹了創建 Swift Package 的步驟,以重用一些常見的 SwiftUI 視圖。這個技巧不只可應用於重用 SwiftUI 視圖,還可以應用於其他常用組件,來與團隊和專案分享。
你認為 Swift Packages 如何呢?有沒有使用 Swift Package Manager 來創建可分享 的組件?歡迎留言與我分享。