Swift Package 转 iOS App:Windows 上把 Swift Package 装到 iPhone 并打包 ipa
最后更新
Swift Package 转 iOS App 只要一步:在 Windows 上用 VS Code 插件或 kxbuild project spm2app,把可执行 target 是 App 的包生成一个 iOS App 工程,然后装到 iPhone 断点调试、打包 ipa 上架。包本身能编译,却装不进手机、也打不出 ipa,在 Mac 上也一样,所以要先转换。转换只做一次,App 工程直接编译包里的源码,之后照常改包里的代码,App 跟着一起变。
开始前准备好一部 iPhone 和数据线、Apple 开发者账号下的证书,以及装好 KXBuild 和 VS Code 插件的电脑,还没装的按安装页操作。
哪些 Swift Package 能转成 iOS App?
能转的是可执行 target 本身就是 App 的包,也就是可执行 target 里有 @main struct …: App 这样的 SwiftUI 入口,或者 UIKit 的 UIApplicationDelegate。其他几种情况这样处理。
- 只提供库的包。它是给 App 用的,不转换。在 App 工程里添加 Swift Package 依赖即可。
- 命令行程序。可执行 target 里没有 App 入口,iOS 上跑不了命令行程序,也不转换。
- Swift Playgrounds 的 App 项目。目前不能直接转换,先整理成普通的包,做法见后文。
下面用一个最小的包演示,目录是 D:\projects\Hello。Hello 是 App,HelloCore 是 App 用到的一个库 target。
1Hello\
2├─ Package.swift
3└─ Sources\
4 ├─ Hello\
5 │ └─ HelloApp.swift
6 └─ HelloCore\
7 └─ Greeting.swift
1// swift-tools-version:5.9
2import PackageDescription
3
4let package = Package(
5 name: "Hello",
6 platforms: [.iOS(.v16)],
7 products: [
8 .executable(name: "Hello", targets: ["Hello"])
9 ],
10 targets: [
11 .executableTarget(name: "Hello", dependencies: ["HelloCore"]),
12 .target(name: "HelloCore")
13 ]
14)
1import SwiftUI
2import HelloCore
3
4@main
5struct HelloApp: App {
6 var body: some Scene {
7 WindowGroup {
8 Text(Greeting.text)
9 }
10 }
11}
在 VS Code 中「文件 → 打开文件夹」打开 Package.swift 所在目录。打开 .swift 文件就有补全和错误提示,SwiftUI、UIKit 的 API 按 iOS 补全。转换前可以先在包目录编译检查一次。
1kxbuild build .
怎么把 Swift Package 转为 App 工程?
以下两种做法任选其一。
- VS Code 插件。命令面板运行
KXBuild: 将 Swift Package 转为 App 工程,或者点 KXBuild 视图里标着「点击生成 App 工程」的那一行。按提示填 App 名和 Bundle ID,App 名留空就用可执行 target 的名字。完成后新工程自动设为启动项目。 - 命令行。在 Package.swift 所在目录执行下面的命令。之后在 KXBuild 视图中点一下 Hello 工程节点,把它设为启动项目。
1kxbuild project spm2app . -n Hello -b com.yourname.hello
Bundle ID 换成你自己的反向域名,后面生成描述文件、在 App Store Connect 建 App 都用它。转换完成后包目录下多了一个和 App 同名的子目录,里面就是 App 工程。
1Hello\
2├─ Package.swift
3├─ Sources\
4│ ├─ Hello\
5│ └─ HelloCore\
6└─ Hello\
7 ├─ .gitignore
8 ├─ Hello.xcodeproj
9 └─ Hello\
10 └─ Assets.xcassets
App 工程里没有复制源码,它直接编译 Sources\Hello 目录,包里的其他 target 按本地包引用。所以改包里的代码就是改 App,这个子目录要和包一起提交到版本库。子目录里自带的 .gitignore 已经排除了构建产物和证书目录。
示例里 App 用到了 HelloCore,而包里没有暴露它的库产品,App 工程引用不到。这时转换会在 Package.swift 的 products 里补一行,插件会弹出提示,命令行输出的 manifestChanges 里也会列出来。转换完成后检查并提交 Package.swift 的这处改动。
1 products: [
2 .executable(name: "Hello", targets: ["Hello"]),
3 .library(
4 name: "HelloCore",
5 targets: [ "HelloCore" ]
6 ),
7 ],
手边还没有证书时,可以先不签名编译一次,确认工程没问题。
1kxbuild build .\Hello\Hello.xcodeproj -D CODE_SIGNING_ALLOWED=NO
包里有多个 App
命令行用 --executable 指定要转的那个,每个 App 各转一次,工程分别建在各自的子目录里。
1kxbuild project spm2app . -n HelloPro -b com.yourname.hellopro --executable HelloPro
提示 choose another place with -o
可执行 target 的源码目录就是包根目录(Package.swift 里写了 path: "."),工程建在包里会被当成 App 源码一起编译。用 -o 把工程放到包目录外,例如 -o ..。
Swift Playgrounds 的 App 项目怎么转?
Swift Playgrounds 建的 App 项目是一个 .swiftpm 目录,Package.swift 里有 import AppleProductTypes,用 .iOSApplication 描述 App。这种包目前不能直接转换,运行转换会提示暂不支持。做法是另建一个普通的包,把源码复制过去再转换,原来的 .swiftpm 不动,在 iPad 上照常打开。
下面以 D:\projects\PGApp.swiftpm 为例。先把其中的 .swift 文件连同子文件夹复制到新包的 Sources\PGApp 目录,Package.swift 除外。
1robocopy D:\projects\PGApp.swiftpm D:\projects\PGApp\Sources\PGApp *.swift /S /XF Package.swift
再在 D:\projects\PGApp 下新建 Package.swift,写入下面的内容,最低 iOS 版本照原项目的 platforms 填。
1// swift-tools-version: 5.9
2import PackageDescription
3
4let package = Package(
5 name: "PGApp",
6 platforms: [.iOS(.v16)],
7 targets: [
8 .executableTarget(name: "PGApp")
9 ]
10)
然后按上一节转换。原项目 .iOSApplication 里写的 Bundle ID 用 -b 带上,版本号、显示名称、图标在下一节的编辑器里改。原项目有 Assets.xcassets 的,把里面的图片集和颜色集复制到新 App 工程的 Assets.xcassets 中。
1cd D:\projects\PGApp
2kxbuild project spm2app . -b com.yourname.pgapp
怎么改 App 名称、图标和权限说明?
名称与版本号
在 KXBuild 视图中右键 Hello target 选「KXBuild: 编辑目标设置」,General 页可以改 Display Name、Bundle Identifier、版本号和最低 iOS 版本,Ctrl+S 保存。各字段见工程设置编辑器。

图标
图标在 App 工程的 Hello\Hello\Assets.xcassets 里。在 KXBuild 视图中点工程节点下的资源目录打开它,把 AppIcon 的占位图换成一张 1024×1024 的 PNG,见资源目录编辑器。
权限说明
转换出的工程没有 Info.plist 文件,构建时自动生成。用到相机、相册、定位这类权限时,把说明文字写成 INFOPLIST_KEY_ 开头的构建设置,构建后会出现在 App 的 Info.plist 里。以相机为例,以下两种做法任选其一。
- VS Code 插件。在工程设置编辑器切到 Build Settings 页,在值单元格上右键选「添加用户自定义设置」,名称填
INFOPLIST_KEY_NSCameraUsageDescription,值填给用户看的说明,Ctrl+S 保存。 - 命令行。在包目录执行下面的命令。
1kxbuild project add-setting -k INFOPLIST_KEY_NSCameraUsageDescription -v "用于扫描二维码" -t Hello -P .\Hello\Hello.xcodeproj
怎么装到 iPhone 上调试?
真机运行需要 Apple Development 证书(.p12)和包含这部手机 UDID 的 Development 描述文件。在 App 工程目录 Hello\Hello 下新建 keychain 文件夹,把两个文件放进去,再新建 password.properties,写一行「文件名=密码」。想让所有工程共用的,放到 KXBuild 安装目录下的 keychain 文件夹。证书的获取方式见 Windows 给 iOS App 签名。
1development.p12=123456
用数据线接上 iPhone,在 KXBuild 视图的「设备」节点选中它。第一次连接要安装设备驱动、在手机上点信任并打开开发者模式,见设备准备。
确认启动项目是 Hello,在 Sources\Hello 的代码里打断点,按 F5。KXBuild 构建、签名、安装、启动 App 并停在断点上,HelloCore 这类库 target 里的代码同样能断点。改完代码先 Shift+F5 停止再按 F5。变量查看、调试控制台和崩溃定位见 在 Windows 上真机调试 iOS App。
转换后怎么改代码和加依赖?
转换只做一次,之后照常在包里开发。按改动的类型处理。
- 改代码、在
Sources\Hello里新建或删除 .swift 文件。直接构建或按 F5,App 工程跟着这个目录走,不用做别的。 - 改包里库 target 的代码。同样直接构建。
- 给 App 的可执行 target 新增依赖。Package.swift 里改完之后,App 工程也要加上同一个依赖,否则构建报
no such module。
新增的依赖是包里的另一个 target 时,先在 Package.swift 的 products 里给它加一个库产品,例如 .library(name: "HelloExtra", targets: ["HelloExtra"])。然后以下两种做法任选其一。
- VS Code 插件。命令面板运行
KXBuild: 添加 Swift Package 依赖…,选择包目录D:\projects\Hello作为本地包,产品选 HelloExtra。 - 命令行。在包目录执行下面的命令。
1kxbuild project add-package -P .\Hello\Hello.xcodeproj --path . --product HelloExtra
新增的依赖是远程包时,App 工程加同一个包和产品,版本规则和 Package.swift 里写的保持一致。以下两种做法任选其一。
- VS Code 插件。命令面板运行
KXBuild: 添加 Swift Package 依赖…,输入包的 Git 地址,选版本规则和产品。 - 命令行。在包目录执行下面的命令,以 Alamofire 为例。
1kxbuild project add-package -P .\Hello\Hello.xcodeproj --url https://github.com/Alamofire/Alamofire.git --from 5.9.0 --product Alamofire
怎么打包 Swift Package ipa 上架?
上架用 Apple Distribution 证书和 App Store 类型的描述文件,放进同一个 keychain 目录,在 password.properties 里加一行密码。然后以下两种做法任选其一。
- VS Code 插件。在命令面板或工程的右键菜单运行
KXBuild: 生成 App Store ipa。 - 命令行。在包目录执行下面的命令。
1kxbuild build .\Hello\Hello.xcodeproj -c Release --export app-store
完成后生成 Hello\build\Hello.ipa,Swift Package 转 iOS App 到上架的准备就齐了。在 App Store Connect 建 App、上传 ipa、填写资料和提交审核的完整流程见在 Windows 上上架 App Store。
常见问题
提示 only provides libraries 怎么办?
这种包不用转换,把它作为本地包加到 App 工程里即可。它只有库产品、没有 App,先新建一个 App 工程再添加,见 Swift 技术栈。
提示 has no app to convert 是什么原因?
原因是可执行 target 里找不到 App 入口,它是命令行程序。确认入口文件里有 @main 标记的 App 结构体,或者 UIApplicationDelegate。
转换输出里的 warnings 要处理吗?
要处理,可执行 target 在 Package.swift 里写的几样设置不会带到 App 工程,输出里会逐条列出。
- resources。资源会拷进 App,但 App 里没有
Bundle.module,读取资源的代码改用Bundle.main。 - swiftSettings、linkerSettings 等编译设置。在工程设置编辑器的 Build Settings 页里重新设置。
- 构建插件。不会在 App 工程里运行。
链接报 …vpfi 未定义怎么解决?
设置环境变量 KXBUILD_SWIFT_WHOLE_MODULE=all 改为整模块编译,重开 VS Code 后再构建即可。Debug 构建在链接阶段报 undefined symbol: …vpfi,是开源 Swift 编译器的已知问题:@State private var count = 0 这类带初始值的属性,所在结构体的 init 写在另一个文件里就会出现。App 的代码在包里,属于本地包,默认按文件编译,所以会碰到。
1setx KXBUILD_SWIFT_WHOLE_MODULE all
能不能重新转换一次?
可以加 --force 在原目录重新生成,但整个 App 工程会重建,之前在工程设置里改过的内容和换过的图标都会还原。只是加了依赖的,按上文添加即可,不必重新转换。