Swift package 的製作,使用和分享

開發 iOS App 時,若能將常用的功能整理成重覆使用的套件,我們就能方便地在不同 App 使用,而且也能提供給需要的人使用。

製作套件有許多方法,將程式包裝成 Swift package 是目前最流行的一種做法。以下我們先介紹 Swift package 的製作,然後再說明 App 如何用 SPM 加入 local 端的 package,最後再將 package 上傳分享到 GitHub,讓其它人也能使用。

Swift package 的內容

Swift Package 裡可加入程式,資源檔,和 binaries 檔,因此它不只提供 App 開發需要的功能,還能提供圖片,音樂等資源。

Apple 官方文件的 Swift package 說明圖

製作 Swift package

  • 建立 package。

方法 1。

點選 Xcode menu 的 File > New > Package。

方法 2。

建立新專案,選擇 Multiplatform 下的 Swift Package

  • 輸入 package 的名稱。

彼得潘想做一個顯示 About 頁面的套件,因此輸入 About。

  • 設定套件資訊的 Package.swift。

Package.swift 是 Swift Package 最重要的檔案,它以 Package 物件設定套件的相關資訊。

Package.swift 預設已幫我們填好基本資訊,不過我們想限制 iOS 至少要 16,因此我們修改 Package.swift,加入參數 platforms 設定 iOS 的最低支援版本。

let package = Package(
name: "About",
platforms: [.iOS(.v16)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "About",
targets: ["About"]),
],
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: "About",
dependencies: []),
.testTarget(
name: "AboutTests",
dependencies: ["About"]),
]
)

Package 的主要參數說明如下。

  1. name。

package 的名稱。

2. platforms。

package 預設支援多個平台,我們可透過參數 platforms 限制它支援的系統版本。 [.iOS(.v16)] 表示在 iOS 要 16 以上才能使用 package。值得注意的,沒寫在 [ ] 裡不代表不支援,因此 macOS App 也能使用 package。

3. products。

package 生成的產品。我們主要目的是產生讓 App 使用的 library,所以傳入 .library(name: "About", targets: ["About"])。library 的名稱是 About,它包含的 target 也叫 About。

4. dependencies。

package 的程式也可以使用其它 package。若有用到其它 package,可在 dependencies 欄位設定網路上 package 的網址或本機 package 的路經。

5. targets。

package 產生的 module。我們待會寫的程式將編譯成 About Target。

  • 新增 package 需要的資源檔。

Swift Package 可包含各式各樣的資源檔(resource),預設會認得的有以下幾種。

  1. Asset catalogs,可包含圖片,顏色,影音檔,文字檔等。

2. 設計畫面的 storyboard 和 xib。

3. Core Data 的 xcdatamodeld 檔。

4. 多國語言的檔案。

為了讓 package 的程式能找到這些資源檔,比較簡單的方法是在下圖的 Sources > Package 名稱下新增 Resources 資料夾,到時程式將能從 Resources 下找到它們。

ps: 我們也可以將資源檔放在 Resources 資料夾以外的地方,不過會麻煩許多,需要額外做一些設定才能存取。

以下我們在 Resources 下加入 Media.xcassets & a-small-miracle.mp3,asset 裡加入帥氣的 peter 圖片。

由於 mp3 檔不屬於預設會認得的資源檔,一種解法是放在 asset。若要放在 Project navigator ,則須在 Package.swift 的 target 透過參數 resources 描述路徑。

.target(
name: "About",
dependencies: [],
resources: [.process("Resources/a-small-miracle.mp3")]),
  • 新增 package 的程式。

Sources > Package 名稱下可加入 Package 的程式,預設 Xcode 會幫我們產生跟 package 同名的 swift 檔。我們不需要它,因此先刪除 About.swift 和 AboutTests.swift 裡的 function testExample,然後新增 AboutView.swift,在裡面撰寫彼得潘的個人資訊頁面。

import SwiftUI
import AVFoundation

public struct AboutView: View {

@State private var player = AVPlayer()

public init() {
}

public var body: some View {
VStack {
Text("彼得潘")
.font(.title)
Image("peter", bundle: .module)
.resizable()
.scaledToFit()
Text("""
擺渡人靈感來自梁朝偉的電影擺渡人,
電影裡梁朝偉專門幫人解決愛情問題。
由於之前時常有朋友向彼得潘求助工作或學校遇到的 iOS App 開發問題,
彼得潘決定向偶像偉仔看齊,化身 iOS App 金牌擺渡人 !
""")
}
.padding()
.onAppear {
let url = Bundle.module.url(forResource: "a-small-miracle", withExtension: "mp3")!
let item = AVPlayerItem(url: url)
player.replaceCurrentItem(with: item)
player.play()
}
.onDisappear {
player.pause()
}
}
}

struct AboutView_Previews: PreviewProvider {
static var previews: some View {
AboutView()
}
}

以上程式較特別的地方有兩點。

  1. public 權限。

想讓 App 或別的 package 使用的功能必須宣告成 public,因此 AboutView,init & body 被設為 public。

2. 資源檔的讀取。

讀取 Bundle 裡檔案的 URL 要透過 Bundle.module.url,產生 asset 的圖片要加上參數 bundle,傳入 .module。

  • AboutView 的 preview。

Xcode 的 preview 成功顯示 AboutView 的畫面,而且還能聽到播放的音樂。

現在我們已經完成了 Swift package,之後在 Xcode 開發 App 時,都可以加入剛剛製作的 About Package。

使用 local 端的的 Swift package

  • 打開 App 專案,點選 menu 的 File > Add Packages。

記得要關掉 About Package 的 Xcode 視窗,否則待會使用 Package 會出問題。

  • 點選 Add Local。
  • 選擇 Package 的資料夾 About,然後點選 Add Package。

package 成功加入的畫面如下。

此時還無法使用 package,因為我們還沒加入它產生的 library。

  • 加入 package 產生的 library。

點選 Frameworks, Libraries, and Embedded Content 下的 +。

選擇 About library。

About library 成功出現在 Frameworks, Libraries, and Embedded Content。

  • 在 ContentView 使用 About package 的 AboutView。

現在我們可以 import About 和使用它裡面 Public 權限的程式,因此我們可以成功產生 AboutView。

import SwiftUI
import About

struct ContentView: View {
var body: some View {
TabView {
Text("製作中")
.tabItem {
Label("Swift", systemImage: "swift")
}
AboutView()
.tabItem {
Label("About", systemImage: "info.circle")
}
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
  • ContentView 的 preview。

Preview 成功顯示來自 About package 的 AboutView,顯示的圖片和播放的音樂也來自 About package。

  • 修改 package 的程式。

若有需要也可以直接在 App 的 project 修改 package 的程式,它將修改 package 資料夾裡的程式。

  • 從 App 存取 Swift package 的資源檔。

App 的 Swift 程式無法直接存取 package 的資源檔,除非先在 package 的程式宣告 public 的常數。

比方以下 asset 圖片和檔案 URL 的例子。

  • 宣告 computed property 儲存 Image 或 UIImage。
extension Image {
public static var peterImage: Image {
Image("peter", bundle: .module)
}

}
extension UIImage {
public static var peterImage: UIImage? {
UIImage(named: "peter", in: .module, with: nil)
}

}
  • 宣告 musicURL 儲存音樂檔的位置。
extension URL {
public static let musicURL = Bundle.module.url(forResource: "a-small-miracle", withExtension: "mp3")!
}

將 Swift package 上傳到 GitHub,讓其它人使用

我們也可以分享 Swift package,讓其它人的 App 也顯示帥氣 Peter 的 AboutView。

分享的方法很簡單,只要將 package 上傳到 GitHub,之後其它人即可用 SPM 輸入 package 的 GitHub 連結安裝。

以下彼得潘從 Xcode 點選 New About Remote 將 About pakcage 上傳到 GitHub。

產生的 GitHub 連結如下。

https://github.com/AppPeterPan/About

之後其它人開發 App 時只要用 SPM 加入以上網址即可使用 About package。

將專案程式模組化的 local Swift packag

有時我們只想將 App 專案裡的程式模組化,不需要讓不同的 App 專案共享,而且也不想分享給其它人使用呢 ?

此情況可製作存在 App 專案下的 local Swift package。

參考連結

  • Creating a standalone Swift package with Xcode
  • Bundling resources with a Swift package
  • Publishing a Swift package with Xcode

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com