macOS 应用开发:使用 SwiftUI 与 AppKit
macOS 应用开发入门:SwiftUI 与 AppKit 双剑合璧
macOS 拥有庞大的用户群体和成熟的开发生态,本教程将带你从零开始构建原生桌面应用。你将掌握两种核心技术:SwiftUI 用于快速搭建现代界面,AppKit 用于访问底层系统能力。无论你是 iOS 开发者转向桌面端,还是完全的新手,都能循序渐进地掌握 macOS 开发的核心。
开发环境准备
开发 macOS 应用必须使用运行 macOS 的电脑,并安装 Xcode。打开 Mac App Store,搜索 Xcode 进行安装。安装完成后,启动 Xcode 并同意许可协议。建议 macOS 版本保持较新,以获得最新的 SwiftUI 和框架支持。
创建第一个 SwiftUI 应用
Xcode 提供了 SwiftUI 项目模板,能让我们在几分钟内看到成果。
- 启动 Xcode,选择 File > New > Project。
- 在模板选择器中,找到 macOS 标签页,选择 App,点击 Next。
- 填写 Product Name(如
MyFirstApp),Team 可以选择 None,Interface 选择 SwiftUI,Language 选择 Swift。点击 Next 并选择保存位置。 - 项目创建后,你会看到一个名为
ContentView.swift的文件,里面包含一个简单的 SwiftUI 视图。
点击 Xcode 工具栏中的运行按钮(▶️),一个窗口将弹出,显示 “Hello, world!”。这就是你的第一个 macOS 应用。
SwiftUI 核心概念
SwiftUI 采用声明式语法,你只需描述界面的状态,框架会自动处理渲染和更新。
视图与布局
每个界面元素都是一个视图,使用 VStack、HStack、ZStack 进行布局。
VStack(alignment: .leading, spacing: 16) {
Text("欢迎使用我的应用")
.font(.largeTitle)
.bold()
HStack {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
Text("收藏")
}
Button("点击我") {
print("按钮被点击")
}
.buttonStyle(.borderedProminent)
}
.padding()
.frame(width: 400)
VStack:垂直排列子视图。HStack:水平排列。.padding():添加内边距。.frame():固定尺寸。
数据绑定与状态管理
使用 @State、@Binding、@ObservedObject 等属性包装器驱动界面更新。
示例:绑定文本输入
struct ContentView: View {
@State private var name: String = ""
var body: some View {
VStack {
TextField("输入你的名字", text: $name)
.textFieldStyle(.roundedBorder)
Text("你好,\(name)")
.font(.title2)
}
.padding()
}
}
当用户输入文字时,name 的值随之改变,下方的问候语自动刷新。
列表与导航
使用 List 展示数据,搭配 NavigationView 实现多层级导航。
struct Item: Identifiable {
let id = UUID()
let title: String
}
struct ListView: View {
let items = [Item(title: "笔记"), Item(title: "提醒"), Item(title: "照片")]
var body: some View {
NavigationView {
List(items) { item in
NavigationLink(destination: Text(item.title)) {
Text(item.title)
}
}
.listStyle(.sidebar)
Text("未选择项目")
}
.frame(minWidth: 600, minHeight: 400)
}
}
macOS 上的 NavigationView 默认呈现为三栏布局,非常适合构建生产力工具。
SwiftUI 与 AppKit 混合开发
SwiftUI 虽然强大,但仍无法覆盖所有系统 API。当需要访问 AppKit 特有的功能(如 NSToolbar、NSOpenPanel、NSMenu)时,就需要使用桥接技术。
使用 NSViewRepresentable
通过 NSViewRepresentable 协议,你可以将 AppKit 视图包装为 SwiftUI 视图。
示例:包装一个 AppKit 的 NSTextField(支持富文本)
import SwiftUI
import AppKit
struct AppKitTextField: NSViewRepresentable {
@Binding var text: String
func makeNSView(context: Context) -> NSTextField {
let textField = NSTextField()
textField.delegate = context.coordinator
return textField
}
func updateNSView(_ nsView: NSTextField, context: Context) {
nsView.stringValue = text
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, NSTextFieldDelegate {
var parent: AppKitTextField
init(_ parent: AppKitTextField) {
self.parent = parent
}
func controlTextDidChange(_ obj: Notification) {
if let textField = obj.object as? NSTextField {
parent.text = textField.stringValue
}
}
}
}
// 在 SwiftUI 中使用
struct ContentView: View {
@State private var richText = ""
var body: some View {
AppKitTextField(text: $richText)
.frame(width: 300, height: 100)
}
}
访问 AppKit 的 Window 对象
在 SwiftUI 生命周期中,可以通过 NSApplicationDelegate 或环境值来操作窗口。
方法一:使用 AppDelegate
新建一个 AppDelegate.swift:
import AppKit
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
if let window = NSApplication.shared.windows.first {
window.title = "自定义标题"
window.setContentSize(NSSize(width: 800, height: 600))
}
}
}
并在 MyApp.swift 中注入:
import SwiftUI
@main
struct MyApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
方法二:在视图内部获取窗口
struct ContentView: View {
var body: some View {
Button("更改窗口标题") {
if let window = NSApplication.shared.keyWindow {
window.title = "新标题"
}
}
}
}
使用 NSOpenPanel 选择文件
NSOpenPanel 是一个常用的 AppKit 组件,在 SwiftUI 中可以直接调用。
struct FilePickerView: View {
@State private var selectedFilePath: String = ""
var body: some View {
VStack {
Button("选择文件") {
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = false
if panel.runModal() == .OK {
selectedFilePath = panel.url?.path ?? "未选择"
}
}
Text(selectedFilePath)
}
}
}
构建完整的工具应用:菜单栏与偏好设置
macOS 应用通常需要一个菜单栏和偏好设置窗口。SwiftUI 提供了 MenuBarExtra(macOS 13+)和 Settings 场景。
菜单栏应用
@main
struct MenuBarApp: App {
var body: some Scene {
MenuBarExtra("MyUtility", systemImage: "hammer") {
VStack {
Text("快速操作")
Button("执行任务") {
// 执行操作
}
Divider()
Button("退出") {
NSApplication.shared.terminate(nil)
}
}
.padding()
}
}
}
偏好设置窗口
新建一个 SettingsView.swift,并在 App 结构体中添加 Settings 场景:
import SwiftUI
struct SettingsView: View {
@AppStorage("showDockIcon") private var showDockIcon = true
var body: some View {
TabView {
Form {
Toggle("在程序坞中显示图标", isOn: $showDockIcon)
}
.tabItem { Label("通用", systemImage: "gear") }
.padding()
}
.frame(width: 400, height: 200)
}
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
Settings {
SettingsView()
}
}
}
按下快捷键 ⌘, 即可打开偏好设置。
应用发布与分发
开发完成后,需要打包并提交至 Mac App Store 或自行分发。
- 配置签名:在 Xcode 项目设置中,选择正确的 Team,Xcode 会自动管理签名。
- 归档应用:选择菜单 Product > Archive。
- 验证并上传:在 Organizer 窗口中选择刚生成的 Archive,点击 Distribute App。可以选择 App Store Connect 或 Developer ID 分发。
- App Store Connect:登录 App Store Connect,创建新 App,填写描述、截图等信息,然后提交审核。
对于非商店分发,使用 Developer ID 签名后,可导出 .app 或 .dmg 文件提供给用户。注意要经过 Apple 的公证流程,避免 Gatekeeper 警告。
进阶学习路径
- 深入研究 Combine 框架,处理复杂的异步数据流。
- 学习 Core Data 或 SwiftData 进行本地数据持久化。
- 掌握 XCTest 编写单元测试和 UI 测试。
- 探索 AppKit 的高级特性,如
NSTableView、NSCollectionView、拖放、打印等。 - 阅读 Apple 官方文档:SwiftUI、AppKit。
本文由 免费在线教程 平台提供,旨在降低初学者的学习门槛。持续练习,你就能开发出专业级的 macOS 应用。