Windows 上写第一个 SwiftUI App,KXBuild 空工程起步,跑到 iPhone 真机

这篇从一个空工程开始,在 Windows 上用 SwiftUI 写一个待办清单 App,装到自己的 iPhone 上运行和断点调试,最后打包发给朋友或同事。适合会一点编程、第一次写 iOS 的人,也适合手边只有 Windows 电脑的 iOS 开发者,全程大约一小时。

开始前准备好三样东西。一个 Apple 开发者账号,用来生成证书和描述文件。一部 iPhone 和能传数据的线。一台装好 KXBuild 和 VS Code 插件的电脑,还没装的按安装页操作。

新建工程 {#create-project}

以下两种做法任选其一。

  • VS Code 插件 。按 Ctrl+Shift+P 打开命令面板,运行 KXBuild: 新建项目,模板选「iOS 应用(SwiftUI)」,项目名填 MyTodo,Bundle ID 改成你自己的反向域名,例如 com.yourname.mytodo,再选一个存放目录。创建完成后选「在新窗口打开」。
  • 命令行 。在 PowerShell 中执行下面的命令,再用 VS Code「文件 → 打开文件夹」打开 D:\projects\MyTodo。
powershell 复制代码
kxbuild project create -t app_swiftui -n MyTodo -b com.yourname.mytodo -o D:\projects

打开后左侧 KXBuild 视图列出 MyTodo 工程和同名的 app target。工程里只有三样东西,MyTodoApp.swift 是 App 入口,ContentView.swift 是首页界面,Assets.xcassets 放图标和颜色。下面的代码全部写在 ContentView.swift 里,省去往工程里添加新文件的步骤。

Bundle ID 是 App 在 Apple 那边的唯一标识,后面生成描述文件、在 App Store Connect 建 App 都要用同一个,现在定好,之后不要再改。

第一步,列表界面 {#step-list}

先做一个只能看的列表,数据写死在代码里。把 ContentView.swift 的内容整个换成下面这段。

swift 复制代码
import SwiftUI

struct TodoItem: Identifiable, Codable {
    var id = UUID()
    var title: String
    var done = false
}

struct ContentView: View {
    @State private var items = [
        TodoItem(title: "买牛奶"),
        TodoItem(title: "给 App 换个图标"),
        TodoItem(title: "把 ipa 发给同事", done: true)
    ]

    var body: some View {
        NavigationView {
            List(items) { item in
                HStack {
                    Image(systemName: item.done ? "checkmark.circle.fill" : "circle")
                        .foregroundColor(item.done ? .green : .secondary)
                    Text(item.title)
                        .strikethrough(item.done)
                }
            }
            .navigationTitle("待办")
        }
    }
}

TodoItem 是一条待办的数据,Identifiable 让 List 能区分每一行,Codable 留给第三步存文件用。@State 标记的数组一变,界面就会自动刷新。

写的过程中你会发现,输入 Text(item.title). 之后会弹出 strikethrough、font、foregroundColor 这些修饰符,Image(systemName:) 的参数、List 的各个初始化方法也都能补全,自己写的类型按 F12 可以跳到定义。写错的地方会画红线,问题面板里有完整的错误信息。第一次打开 Swift 文件时代码提示要启动几秒,补全不出来时在命令面板运行 KXBuild: 重启代码提示,更多情况见代码提示与索引。

装到 iPhone 上运行 {#run-on-device}

第一次在真机上运行要做两件事,放好证书,连好手机。以后每次只需要按 F5。

放好证书 {#signing}

真机运行需要 Apple Development 证书(.p12)和包含这部手机 UDID 的 Development 描述文件(.mobileprovision)。没有的话可以用 Appuploader 登录开发者账号生成,也可以从 Mac 的钥匙串导出,几种做法见证书与签名。手机的 UDID 在连上电脑后运行下面的命令查看,第一列就是。

powershell 复制代码
kxdevice device list

在工程根目录新建 keychain 文件夹,把两个文件放进去,再新建一个 password.properties,写一行「文件名=密码」。

text 复制代码
development.p12=123456

模板自带的 .gitignore 已经排除了 keychain 目录,证书不会被提交到仓库。

连接手机 {#connect-device}

用数据线把 iPhone 接到电脑,在 KXBuild 视图的「设备」节点点一下手机,选为当前设备。第一次连接依次完成这几件事,详见设备准备。

  • 设备节点提示「点击配置」时点一下,安装 Apple 设备驱动,过程中会请求一次管理员权限。
  • 手机弹出「信任此电脑」时解锁并点信任。
  • 打开手机「设置 → 隐私与安全性 → 开发者模式」,手机会重启一次。这个开关在手机连过电脑之后才出现。
  • 第一次调试某个 iOS 版本会自动下载几个 GB 的符号库,大约十多分钟,只有这一次。

按 F5 {#press-f5}

按 F5,KXBuild 依次构建、签名、安装、启动 App 并附加调试器,手机上出现写死的三条待办。构建日志在「输出 → KXBuild」,编译错误在问题面板,点一下跳到对应的代码行。

启动失败时通知里会写明卡在哪一步。最常见的是描述文件里没有这部手机的 UDID,把 UDID 加进描述文件、重新下载后替换 keychain 里的旧文件即可。其他提示的处理见运行与调试。

第二步,添加、删除和勾选 {#step-edit}

现在让列表能改。顶部加一个输入框,点一行切换完成状态,左滑删除,右上角的编辑按钮可以批量删。把 ContentView 换成下面的版本,TodoItem 保持不变。

swift 复制代码
struct ContentView: View {
    @State private var items: [TodoItem] = []
    @State private var newTitle = ""

    var body: some View {
        NavigationView {
            List {
                Section {
                    HStack {
                        TextField("新的待办", text: $newTitle)
                            .onSubmit(add)
                        Button("添加", action: add)
                            .disabled(newTitle.trimmingCharacters(in: .whitespaces).isEmpty)
                    }
                }
                Section {
                    ForEach($items) { $item in
                        Button {
                            item.done.toggle()
                        } label: {
                            HStack {
                                Image(systemName: item.done ? "checkmark.circle.fill" : "circle")
                                    .foregroundColor(item.done ? .green : .secondary)
                                Text(item.title)
                                    .strikethrough(item.done)
                                    .foregroundColor(.primary)
                            }
                        }
                    }
                    .onDelete { offsets in
                        items.remove(atOffsets: offsets)
                    }
                }
            }
            .navigationTitle("待办")
            .toolbar { EditButton() }
        }
    }

    private func add() {
        let title = newTitle.trimmingCharacters(in: .whitespaces)
        guard !title.isEmpty else { return }
        items.append(TodoItem(title: title))
        newTitle = ""
    }
}

ForEach($items) { $item in ... } 拿到的是每一行的绑定,item.done.toggle() 直接改的就是数组里那一项。onDelete 同时支持左滑删除和编辑模式下的删除。

改完代码要先停止调试(Shift+F5)再按 F5,调试工具栏上的「重启」不会重新编译。

打断点看变量 {#breakpoint}

在 add() 里 items.append(...) 这一行的行号左边点一下,出现红点。回到手机上输入一条待办,点「添加」,App 停住,VS Code 切到调试视图。

左侧「变量」里能看到 title 的值,「调用堆栈」里能看到这次调用是怎么进来的。按 F10 单步执行一行,F5 继续运行。下面的调试控制台可以直接输入 lldb 命令,例如输入 po title 打印当前的标题。代码里的 print 输出显示在「输出 → KXBuild Console」。

第三步,把数据存到文件里 {#step-persist}

现在关掉 App 再打开,待办就没了。用 JSON 文件把列表存到 App 的 Documents 目录,每次改动自动保存,启动时读回来。

把数据和读写逻辑放进一个 TodoStore,界面只管显示。在 ContentView.swift 里 TodoItem 的下面加上这个类。

swift 复制代码
final class TodoStore: ObservableObject {
    @Published var items: [TodoItem] = [] {
        didSet { save() }
    }

    private let fileURL = FileManager.default
        .urls(for: .documentDirectory, in: .userDomainMask)[0]
        .appendingPathComponent("todos.json")

    init() {
        if let data = try? Data(contentsOf: fileURL),
           let saved = try? JSONDecoder().decode([TodoItem].self, from: data) {
            items = saved
        }
    }

    private func save() {
        do {
            let data = try JSONEncoder().encode(items)
            try data.write(to: fileURL, options: .atomic)
        } catch {
            print("保存失败 \(error)")
        }
    }
}

然后改 ContentView 里的四处,把 @State 数组换成这个 store。

swift 复制代码
    @StateObject private var store = TodoStore()   // 替换 @State private var items
    // ...
    ForEach($store.items) { $item in               // 原来是 ForEach($items)
    // ...
    store.items.remove(atOffsets: offsets)         // onDelete 里
    // ...
    store.items.append(TodoItem(title: title))     // add() 里

@Published 的数组被修改时会触发 didSet,勾选、删除、添加都会写一次文件。在 init 里给 items 赋值不会触发 didSet,启动读文件时不会反过来又写一遍。

重新按 F5,加几条待办,在手机上把 App 从后台划掉再打开,数据还在。想确认文件确实写了,在 save() 的 let data = ... 这行打个断点,勾选一条待办,停住后在调试控制台输入 po fileURL,能看到文件在 App 沙盒里的完整路径。

数据只有几个开关、一两个字符串时,用 @AppStorage("key") 存进 UserDefaults 更省事。待办这种列表数据存成文件更合适,以后想换成 Core Data 或 SwiftData 也好迁移。

改名字和图标 {#name-and-icon}

手机桌面上 App 的名字默认是 MyTodo。在 KXBuild 视图中右键 target 选「KXBuild: 编辑目标设置」,在 General 页把 Display Name 改成「待办」,Ctrl+S 保存。同一页还能改版本号和最低 iOS 版本,各字段的含义见工程设置编辑器。

图标在 KXBuild 视图的资源目录节点里打开 Assets.xcassets,给 AppIcon 放一张 1024×1024 的图,其他尺寸自动生成,见资源目录编辑器。改完再按一次 F5 就能在手机上看到。

打包发给别人 {#share-ipa}

调试用的 Debug 包只能装在描述文件里列出的手机上。要发给别人装,按人数和对方是否在身边选一种方式。

用 TestFlight 发给测试员 {#testflight}

对方不在身边、人数较多时用 TestFlight。对方在 iPhone 上装 TestFlight App,收到邀请后直接安装,不需要登记 UDID。

在 keychain 目录放入 Apple Distribution 证书和 App Store 类型的描述文件,同样在 password.properties 里加一行密码。然后以下两种做法任选其一。

  • VS Code 插件 。在 KXBuild 视图的「构建配置」节点切到 Release(也可以在命令面板运行 KXBuild: 选择构建配置),再运行 KXBuild: 构建。
  • 命令行。在工程目录执行下面的命令。
powershell 复制代码
kxbuild build .\MyTodo.xcodeproj -c Release

完成后工程目录下生成 build\MyTodo.ipa。在 App Store Connect 建好这个 App,用 Appuploader 上传 ipa,处理完成后在 TestFlight 页添加测试员,详见发布与上架。

用 Ad Hoc 直接装到身边同事的手机 {#ad-hoc}

几台固定的测试机可以用 Ad Hoc。在开发者网站登记同事手机的 UDID,生成包含这些设备的 Ad Hoc 描述文件,用 Distribution 证书按上面的步骤构建 Release。如果 keychain 里同时放着 App Store 和 Ad Hoc 两个描述文件,构建时用 -D 指定要用的那个。

powershell 复制代码
kxbuild build .\MyTodo.xcodeproj -c Release -D CODE_SIGN_STYLE=Manual -D CODE_SIGN_IDENTITY="Apple Distribution" -D PROVISIONING_PROFILE_SPECIFIER="MyTodo AdHoc"

把同事的手机接到你的电脑上,执行下面的命令安装。

powershell 复制代码
kxdevice install .\build\MyTodo.ipa

更多教程可以访问KXBuild官网:https://www.kxbuild.net/zh

相关推荐
2501_915918411 小时前
iOS 上架注意事项有哪些?Appuploader 的四个阶段自查清单
android·ios·小程序·https·uni-app·iphone·webview
Piink2 小时前
Mac使用Vscode教程
ide·vscode·macos
jingcairensheng2 小时前
vscode 配置Python虚拟环境基于扩展和原生miniconda的区别
ide·vscode·python
MounRiver_Studio2 小时前
原生集成AI 能力的 RISC-V IDE:第三代 MounRiver Studio 正式发布
ide·ai·agent·risc-v
长成一棵参天树20 小时前
React15 vs React16 核心区别
git·vscode
小师兄吃牛肉20 小时前
Android Studio 2025 专业介绍:新特性、性能与开发体验全面解析
android·ide·android studio
黑化旺仔21 小时前
【OC】Runtime:类元数据、加载初始化与消息派发
开发语言·macos·ios·objective-c
songgeb1 天前
iOS App支持设置RTL语言的实践
ios·swift·ui kit
软泡芙1 天前
【iOS】swift启动屏显示黑屏
ios