把 Swift Package 变成能装到 iPhone 的 iOS App
用 Swift Package 或 Swift Playgrounds 写的 App,代码能编译,却没法直接装到 iPhone 上,也打不出 ipa,在 Mac 上也是一样。这篇在 Windows 上把这样的包转成一个 iOS App 工程,装到真机调试,再打包上架。之后你照常改包里的代码,App 工程跟着一起变。
开始前准备好一部 iPhone 和数据线、Apple 开发者账号下的证书,以及装好 KXBuild 和 VS Code 插件的电脑,还没装的按安装页操作。
哪些包能转
能转的是可执行 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 .
转为 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
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
改名称、图标和权限说明
名称与版本号
在 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 文件夹。证书的获取方式见证书与签名。
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
打包 ipa 上架
上架用 Apple Distribution 证书和 App Store 类型的描述文件,放进同一个 keychain 目录,在 password.properties 里加一行密码。然后以下两种做法任选其一。
- VS Code 插件。在 KXBuild 视图的「构建配置」节点切到 Release,命令面板运行
KXBuild: 构建。 - 命令行。在包目录执行下面的命令。
1kxbuild build .\Hello\Hello.xcodeproj -c Release
完成后生成 Hello\build\Hello.ipa。在 App Store Connect 建 App、上传 ipa、填写资料和提交审核的完整流程见在 Windows 上上架 App Store。
常见问题
提示 only provides libraries
包里只有库产品,没有 App。这种包不转换,新建一个 App 工程再把它作为本地包添加进去,见 Swift 技术栈。
提示 has no app to convert
可执行 target 里找不到 App 入口,是命令行程序。确认入口文件里有 @main 标记的 App 结构体,或者 UIApplicationDelegate。
包里有多个 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 ..。
转换输出里有 warnings
可执行 target 在 Package.swift 里写的几样设置不会带到 App 工程,输出里会逐条列出。
- resources。资源会拷进 App,但 App 里没有
Bundle.module,读取资源的代码改用Bundle.main。 - swiftSettings、linkerSettings 等编译设置。在工程设置编辑器的 Build Settings 页里重新设置。
- 构建插件。不会在 App 工程里运行。
链接报 …vpfi 未定义
Debug 构建在链接阶段报 undefined symbol: …vpfi,是开源 Swift 编译器的已知问题。@State private var count = 0 这类带初始值的属性,所在结构体的 init 写在另一个文件里就会出现。App 的代码在包里,属于本地包,默认按文件编译。设置环境变量 KXBUILD_SWIFT_WHOLE_MODULE=all 改为整模块编译,重开 VS Code 后再构建即可。
1setx KXBUILD_SWIFT_WHOLE_MODULE all
能不能重新转换一次
可以加 --force 在原目录重新生成,但整个 App 工程会重建,之前在工程设置里改过的内容和换过的图标都会还原。只是加了依赖的,按上文添加即可,不必重新转换。