关于 ios-development
面向 Apple 平台原生应用开发者的 Swift 开发参考,涵盖 SwiftUI 与 UIKit 实践,并涉及数据持久化、网络、设计、测试和 App Store 准备。
主要功能
- 01SwiftUI 结构与状态管理
- 02UIKit 生命周期与 Auto Layout
- 03使用 Core Data 和 SwiftData 持久化
- 04使用 URLSession 进行网络请求
- 05Apple 设计、无障碍与平台指南
- 06测试和 App Store 准备
适用场景
- 01启动 SwiftUI 应用项目
- 02实现数据持久化和网络请求
- 03准备应用测试及商店素材
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ travisjneuman/ .claude --skill ios-development安装后在 Claude Code 中输入/ios-development确认已加载。
详细介绍
iOS/iPadOS/tvOS 开发
构建原生 Apple 平台应用的综合指南。
涵盖的平台
| 平台 | 最低目标版本 | 当前版本 |
|---|---|---|
| iOS | iOS 15.0+ | iOS 17+ |
| iPadOS | iPadOS 15.0+ | iPadOS 17+ |
| tvOS | tvOS 15.0+ | tvOS 17+ |
| watchOS | watchOS 8.0+ | watchOS 10+ |
SwiftUI(新项目首选)
基本结构
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("Count: \(count)")
.font(.largeTitle)
Button("Increment") {
count += 1
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
状态管理
// Local state
@State private var text = ""
// Binding (child components)
@Binding var isPresented: Bool
// Observable object
@StateObject private var viewModel = MyViewModel()
@ObservedObject var viewModel: MyViewModel
// Environment
@Environment(\.dismiss) private var dismiss
@Environment(\.colorScheme) private var colorScheme
@EnvironmentObject var appState: AppState
// App Storage (UserDefaults)
@AppStorage("username") private var username = ""
现代并发
// Async/await
func fetchData() async throws -> [Item] {
let url = URL(string: "https://api.example.com/items")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Item].self, from: data)
}
// In View
.task {
do {
items = try await fetchData()
} catch {
errorMessage = error.localizedDescription
}
}
// Main actor for UI updates
@MainActor
class ViewModel: ObservableObject {
@Published var items: [Item] = []
func load() async {
items = try? await fetchData()
}
}
导航(iOS 16+)
// NavigationStack with typed destinations
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(items) { item in
NavigationLink(value: item) {
Text(item.name)
}
}
.navigationDestination(for: Item.self) { item in
DetailView(item: item)
}
}
}
}
列表和网格
// Modern List
List {
ForEach(items) { item in
ItemRow(item: item)
}
.onDelete(perform: delete)
.onMove(perform: move)
}
.listStyle(.insetGrouped)
.searchable(text: $searchText)
.refreshable {
await refresh()
}
// LazyVGrid
LazyVGrid(columns: [
GridItem(.adaptive(minimum: 150))
]) {
ForEach(items) { item in
ItemCard(item: item)
}
}
UIKit(旧版/复杂 UI)
视图控制器生命周期
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// About to appear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Fully visible
}
}
自动布局
// Programmatic constraints
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16)
])
数据持久化
Core Data
// Model
@objc(Item)
public class Item: NSManagedObject {
@NSManaged public var id: UUID
@NSManaged public var name: String
@NSManaged public var createdAt: Date
}
// Fetch
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.createdAt, ascending: false)],
animation: .default
)
private var items: FetchedResults<Item>
SwiftData(iOS 17+)
@Model
class Item {
var id: UUID
var name: String
var createdAt: Date
init(name: String) {
self.id = UUID()
self.name = name
self.createdAt = Date()
}
}
// In View
@Query(sort: \Item.createdAt, order: .reverse)
private var items: [Item]
@Environment(\.modelContext) private var modelContext
func addItem() {
let item = Item(name: "New Item")
modelContext.insert(item)
}
Keychain
// Store sensitive data
import Security
func saveToKeychain(key: String, data: Data) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data
]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.saveFailed
}
}
网络
URLSession
actor NetworkService {
func fetch<T: Decodable>(_ type: T.Type, from url: URL) async throws -> T {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw NetworkError.invalidResponse
}
return try JSONDecoder().decode(T.self, from: data)
}
}
Apple 人机界面指南
iOS 设计原则
- 清晰:文字易读,图标精确,装饰简洁
- 谦让:内容至上,UI 不喧宾夺主
- 层次:视觉层次和逼真的动态效果传达层级关系
关键尺寸
| 元素 | 尺寸 |
|---|---|
| 触控目标 | 最小 44x44 pt |
| 导航栏 | 44 pt |
| 标签栏 | 49 pt |
| 状态栏 | 47 pt(刘海屏)/20 pt(旧款设备) |
安全区域
.safeAreaInset(edge: .bottom) {
BottomBar()
}
// Ignore safe area
.ignoresSafeArea(.keyboard)
.ignoresSafeArea(.container, edges: .bottom)
平台特定内容
iPadOS 特性
// Split view
NavigationSplitView {
Sidebar()
} content: {
ContentList()
} detail: {
DetailView()
}
// Keyboard shortcuts
.keyboardShortcut("n", modifiers: .command)
// Hover effects
.hoverEffect(.highlight)
tvOS 特性
// Focus management
@FocusState private var isFocused: Bool
Button("Action") { }
.focused($isFocused)
.focusable()
// Large text for 10-foot UI
.font(.system(size: 38))
App Store 要求
必需素材
- App 图标(1024x1024)
- 各设备尺寸对应的屏幕截图
- 隐私政策 URL
- App 描述(最多 4000 个字符)
隐私
<key>NSCameraUsageDescription</key>
<string>We need camera access to...</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need photo access to...</string>
App 传输安全
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
测试
单元测试
import XCTest
@testable import MyApp
final class MyTests: XCTestCase {
func testExample() async throws {
let sut = MyViewModel()
await sut.load()
XCTAssertEqual(sut.items.count, 10)
}
}
UI 测试
import XCTest
final class MyUITests: XCTestCase {
let app = XCUIApplication()
override func setUp() {
continueAfterFailure = false
app.launch()
}
func testNavigation() {
app.buttons["Start"].tap()
XCTAssertTrue(app.staticTexts["Welcome"].exists)
}
}
项目结构
MyApp/
├── App/
│ └── MyApp.swift
├── Features/
│ ├── Home/
│ │ ├── HomeView.swift
│ │ └── HomeViewModel.swift
│ └── Settings/
│ └── SettingsView.swift
├── Core/
│ ├── Models/
│ ├── Services/
│ └── Extensions/
├── Resources/
│ ├── Assets.xcassets
│ └── Localizable.strings
└── Tests/
最佳实践
应该:
- 新项目使用 SwiftUI
- 支持动态字体以提升无障碍体验
- 处理所有设备方向
- 实施适当的错误处理
- 充分利用 Swift 的类型系统
不应该:
- 未经验证就强制解包可选值
- 通过网络调用阻塞主线程
- 硬编码字符串(使用本地化)
- 忽略内存管理(循环引用)
- 跳过无障碍标签
@Observable 宏(iOS 17+,取代 ObservableObject)
import Observation
// New pattern: @Observable macro (replaces ObservableObject + @Published)
@Observable
class UserViewModel {
var user: User?
var isLoading = false
var errorMessage: String?
func load() async {
isLoading = true
defer { isLoading = false }
do {
user = try await fetchUser()
} catch {
errorMessage = error.localizedDescription
}
}
}
// In View: no @StateObject/@ObservedObject wrappers needed
struct UserView: View {
@State private var viewModel = UserViewModel()
var body: some View {
Group {
if viewModel.isLoading {
ProgressView()
} else if let user = viewModel.user {
Text(user.name)
}
}
.task { await viewModel.load() }
}
}
// @Bindable for two-way bindings
struct EditView: View {
@Bindable var viewModel: EditViewModel
var body: some View {
TextField("Name", text: $viewModel.name)
}
}
从 ObservableObject 迁移
| 旧版(ObservableObject) | 新版(@Observable) |
|---|---|
class: ObservableObject | @Observable class |
@Published var | var(自动) |
@StateObject | @State |
@ObservedObject | 直接引用 |
@EnvironmentObject | @Environment(TypeName.self) |
SwiftUI 6 新视图和修饰符
// Mesh gradient (iOS 18+)
MeshGradient(
width: 3, height: 3,
points: [
.init(0, 0), .init(0.5, 0), .init(1, 0),
.init(0, 0.5), .init(0.5, 0.5), .init(1, 0.5),
.init(0, 1), .init(0.5, 1), .init(1, 1)
],
colors: [
.red, .purple, .indigo,
.orange, .cyan, .blue,
.yellow, .green, .mint
]
)
// Zoom navigation transition
NavigationLink {
DetailView(item: item)
} label: {
ItemCard(item: item)
}
.matchedTransitionSource(id: item.id, in: namespace)
// ScrollView enhancements
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemRow(item: item)
}
}
}
.scrollPosition(id: $scrolledID)
.defaultScrollAnchor(.bottom)
// Custom containers
@Entry macro for EnvironmentValues
@Environment(\.myCustomValue) var value
Swift 6 并发
// Complete data race safety (strict concurrency checking)
// Enable in Xcode: SWIFT_STRICT_CONCURRENCY = complete
// Sendable conformance required for data crossing isolation boundaries
struct UserData: Sendable {
let id: UUID
let name: String
}
// Actor isolation
actor DatabaseManager {
private var cache: [String: Data] = [:]
func get(_ key: String) -> Data? { cache[key] }
func set(_ key: String, value: Data) { cache[key] = value }
}
// Global actors
@MainActor
class ViewModel {
var items: [Item] = []
func load() async {
let data = await fetchFromNetwork() // Runs off main actor
items = data // Back on main actor automatically
}
}
// Typed throws (Swift 6)
enum DataError: Error {
case notFound, corrupted
}
func loadData() throws(DataError) -> Data {
guard let data = cache.get("key") else {
throw .notFound
}
return data
}
最低目标版本:iOS 17+
新项目应以 iOS 17+ 为目标,以使用以下功能:
| 功能 | 最低 iOS 版本 | 优势 |
|---|---|---|
| @Observable | 17 | 更简单的状态管理 |
| SwiftData | 17 | 现代化持久化(取代 Core Data) |
| NavigationStack | 16 | 类型安全的导航 |
| Swift Charts | 16 | 原生图表功能 |
| MeshGradient | 18 | 精美渐变 |
| ControlWidget | 18 | 控制中心小组件 |
visionOS 与空间计算
// visionOS app structure
@main
struct MyVisionApp: App {
var body: some Scene {
// 2D window
WindowGroup {
ContentView()
}
// Immersive space
ImmersiveSpace(id: "immersiveScene") {
ImmersiveView()
}
// 3D volume
WindowGroup(id: "3dContent") {
Model3D(named: "Globe") { model in
model.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
ProgressView()
}
}
.windowStyle(.volumetric)
}
}
// RealityKit integration
import RealityKit
struct ImmersiveView: View {
var body: some View {
RealityView { content in
let sphere = MeshResource.generateSphere(radius: 0.5)
let material = SimpleMaterial(color: .blue, isMetallic: true)
let entity = ModelEntity(mesh: sphere, materials: [material])
content.add(entity)
}
.gesture(TapGesture().targetedToAnyEntity().onEnded { value in
// Handle spatial tap
})
}
}
watchOS 开发
// watchOS app with SwiftUI
@main
struct MyWatchApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
// Complications / Widgets
struct MyWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "myWidget", provider: Provider()) { entry in
MyWidgetView(entry: entry)
}
.configurationDisplayName("My Widget")
.supportedFamilies([
.accessoryCircular,
.accessoryRectangular,
.accessoryInline,
])
}
}
// Watch Connectivity for iPhone <-> Watch communication
import WatchConnectivity
class ConnectivityManager: NSObject, WCSessionDelegate {
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
// Handle message from paired device
}
}
评论
支持 Markdown