在專案開發到一定規模後,你可能會發現某些 feature 其實相對獨立:它們有自己的流程、畫面、資源檔,甚至可以被其他專案重用。這時候,最乾淨、最有彈性的做法,就是把它抽成 Swift Package。
以我最近在做的功能為例,它是一個完整的獨立模組 - 有多個頁面、支援多國語言、使用圖片與動畫資源。為了避免日後整合時出現命名衝突、相依過重或編譯過慢的問題,我選擇把它獨立成一個 Swift Package。在將 feature 抽出到 Package 的過程我也踩到了一些坑,趁著這個機會記錄下來,以免日後忘記。
為什麼要把 Feature 打包成 Swift Package?
建立專屬的 Swift Package 有幾個明顯的好處:
- ✅ 可獨立開發與測試:模組化後不必依賴主專案,可單獨編譯與驗證。
- ⚙️ 降低相依與衝突:減少命名重複、依賴鏈過長等問題。
- 🚀 加快編譯速度:主專案不需每次都重新編譯整個功能。
- 🔄 方便整合與重用:未來可以直接被其他 app 或團隊使用。
問題一:如何支援多國語言?
若要在 Package 內使用多國語言,有兩件事情一定要搞清楚:
- 檔案結構要正確放置。
- 讀取時要明確指定
.module。
正確的資料夾結構
根據 Apple 官方文件,多國語言檔(.lproj)必須直接放在 Resources 資料夾底下,不能再有子目錄。正確的結構如下:
MyPackage/
├─ Sources/
│ └─ MyLibrary/
│ └─ Resources/
│ ├─ en.lproj/
│ │ └─ Localizable.strings
│ ├─ zh-Hant.lproj/
│ │ └─ Localizable.strings
│ └─ Strings.dict
這樣做可以確保 .lproj 檔會被正確地讀取與載入。若放錯層級,Xcode 雖然不會報錯,但翻譯字串就是不會出現。
正確的呼叫方式
在 Package 內呼叫多國語言字串時,別忘了指定 bundle: .module。否則 Swift 會自動去主專案尋找對應字串,導致載不到 Package 內的翻譯。
extension String {
var localized: String {
NSLocalizedString(
self,
tableName: "Localizable",
bundle: .module, // 關鍵:指定為 package 的 bundle
value: self,
comment: ""
)
}
}
使用時只要 "Some.Localized.String.Key".localized 就能正確取回包內的翻譯字串。這樣做讓 package 在任何專案中都能獨立運作,無需依賴主專案的語言設定。
問題二:如何使用圖片與動畫資源?
圖片與多國語言的處理方式類似,同樣要注意資料夾結構與載入方式。
圖片資源結構
官方建議將圖片檔(.xcassets)放在 Resources 目錄底下。若你使用像 Lottie 這類第三方函式庫,也可以把相關 JSON 資源放在同個目錄。
MyPackage/
├─ Sources/
│ └─ MyLibrary/
│ └─ Resources/
│ ├─ Images.xcassets/
│ │ ├─ Avatar.imageset/
│ │ ├─ Error.imageset/
│ │ └─ ...
│ └─ Lottie/
│ ├─ greeting.json
│ └─ ...
呼叫圖片的方式
有兩種常見方式可以載入圖片:
UIImage(resource: .xxx)UIImage(named: "xxx", in: .module, with: nil)
而如果你使用 Lottie 動畫,則要記得加上 bundle:
LottieAnimationView(name: "greeting", bundle: .module)
這樣才能確保動畫資源來自 package,而不是主專案。
問題三:Package.swift 要怎麼設定?
最後一步,就是在 Package.swift 中設定好本地化與資源處理。這一步如果漏掉,前面做的一切可能都不會生效。
let package = Package(
name: "MyLibrary",
defaultLocalization: "en", // 一定要設定預設語言
platforms: [
.iOS(.v16)
],
products: [
.library(
name: "MyLibrary",
targets: ["MyLibrary"]
),
],
dependencies: [
.package(url: "https://github.com/airbnb/lottie-spm.git", from: "4.5.2"),
],
targets: [
.target(
name: "MyLibrary",
dependencies: [
.product(name: "Lottie", package: "lottie-spm"),
],
resources: [
.process("Resources") // 使用 .process 讓 Xcode 處理資源檔
]
),
]
)
這裡的兩個重點是:
defaultLocalization:沒有這行,多國語言會無法自動套用。.process("Resources"):讓 Xcode 知道要把資源包含進 target。
結語:讓 Feature 真正成為可重用的模組
當一個功能變得越來越複雜時,把它獨立成 Swift Package 不只是整潔問題,更是 架構與維護性的升級。
模組化能讓你:
- 更容易進行單元測試;
- 快速重用或移植到新專案;
- 明確定義每個功能的邊界與責任。
只要依照上面的步驟設定語言與資源,就能建立一個乾淨、可移植、可重用的 Feature Package。當你下一次打開 Xcode 時,或許就會開始想:「這個功能,其實也該是一個獨立的 package 吧?」