π± Mastering SwiftUI
Build apps with a declarative UI framework. Code is UI, and UI is code.
β Difficulty: ββ
β±οΈ Est. Time: 2-3h
π App Frameworks
β¨ SwiftUI?
SwiftUI is Apple's modern framework for building UI with declarative syntax. It replaces UIKit and is available from iOS 13+.
π― Basic View Structure
ContentView.swift
import SwiftUI struct ContentView: View { var body: some View { VStack(spacing: 16) { Text("Hello, SwiftUI!") .font(.largeTitle) .fontWeight(.bold) Image(systemName: "star.fill") .font(.system(size: 48)) .foregroundStyle(.yellow) Button("ννκΈ°") { print("Button tapped!") } .buttonStyle(.borderedProminent) } .padding() } }
π State & Binding
SwiftUI manages state with @State and connects data with $ bindings.
StateExample.swift
struct CounterView: View { @State private var count = 0 var body: some View { VStack(spacing: 20) { Text("μΉ΄μ΄νΈ: \(count)") .font(.title) HStack(spacing: 16) { Button("-") { count -= 1 } .buttonStyle(.bordered) Button("+") { count += 1 } .buttonStyle(.borderedProminent) } } .padding() } }
π TextField & Binding
FormExample.swift
struct LoginView: View { @State private var username = "" @State private var password = "" var body: some View { VStack(spacing: 16) { TextField("μμ΄λ", text: $username) .textFieldStyle(.roundedBorder) .textInputAutocapitalization(.never) SecureField("Password", text: $password) .textFieldStyle(.roundedBorder) Button("λ‘κ·ΈμΈ") { login(username: username, password: password) } .buttonStyle(.borderedProminent) .disabled(username.isEmpty || password.isEmpty) } .padding() } func login(username: String, password: String) { // λ‘κ·ΈμΈ μ²λ¦¬ } }
π List & ForEach
ListView.swift
struct Todo: Identifiable { let id = UUID() var title: String var isCompleted: Bool = false } struct TodoListView: View { @State private var todos = [ Todo(title: "SwiftUI 곡λΆ"), Todo(title: "μ± λ§λ€κΈ°"), Todo(title: "μΆμνκΈ°") ] var body: some View { List { ForEach($todos) { $todo in HStack { Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle") .foregroundStyle(todo.isCompleted ? .green : .gray) Text(todo.title) .strikethrough(todo.isCompleted) } .contentShape(Rectangle()) .onTapGesture { todo.isCompleted.toggle() } } .onDelete(perform: deleteTodos) } } func deleteTodos(at offsets: IndexSet) { todos.remove(atOffsets: offsets) } }
π§ Navigation
NavigationExample.swift
struct ContentView: View { var body: some View { NavigationStack { List(1..<11, id: \.self) { number in NavigationLink("μμ΄ν \(number)") { DetailView(number: number) } } .navigationTitle("λͺ©λ‘") } } } struct DetailView: View { let number: Int var body: some View { VStack { Text("μμ΄ν #\(number)") .font(.largeTitle) } .navigationTitle("μμΈ") .navigationBarTitleDisplayMode(.inline) } }
π¨ Modifier & Custom Style
CustomModifier.swift
// Custom Modifier struct CardModifier: ViewModifier { func body(content: Content) -> some View { content .padding() .background(Color.white) .cornerRadius(12) .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) } } extension View { func cardStyle() -> some View { modifier(CardModifier()) } } // Usage Example struct ContentView: View { var body: some View { VStack { Text("μΉ΄λ 1") .cardStyle() Text("μΉ΄λ 2") .cardStyle() } .padding() } }
β±οΈ Animation
AnimationExample.swift
struct AnimationView: View { @State private var isExpanded = false @State private var rotation = 0.0 var body: some View { VStack(spacing: 32) { // ν¬κΈ° μ λλ©μ΄μ RoundedRectangle(cornerRadius: 12) .fill(Color.blue) .frame( width: isExpanded ? 200 : 100, height: isExpanded ? 200 : 100 ) .animation(.spring(response: 0.6), value: isExpanded) // νμ μ λλ©μ΄μ Image(systemName: "star.fill") .font(.system(size: 48)) .foregroundStyle(.yellow) .rotationEffect(.degrees(rotation)) .animation(.linear(duration: 1).repeatForever(autoreverses: false), value: rotation) Button("ν κΈ") { isExpanded.toggle() rotation += 360 } .buttonStyle(.borderedProminent) } } }
π State Management: @Observable (iOS 17+) vs ObservableObject
π‘ @Observable is recommended for iOS 17+!
ObservableObject is a legacy approach. For new projects, use @Observable macro.
For more details, see the Observation Tutorial.
ViewModel.swift (iOS 17+ Recommended)
import Observation @Observable class TodoViewModel { var todos: [Todo] = [] // @Published λΆνμ! var isLoading = false func addTodo(title: String) { let newTodo = Todo(title: title) todos.append(newTodo) } } struct ContentView: View { var viewModel = TodoViewModel() // @StateObject λΆνμ! var body: some View { List(viewModel.todos) { todo in Text(todo.title) } } }
π Legacy: ObservableObject (iOS 13-16)
ViewModel.swift (Legacy)
class TodoViewModel: ObservableObject { @Published var todos: [Todo] = [] @Published var isLoading = false } struct ContentView: View { @StateObject private var viewModel = TodoViewModel() // ... }
π‘ HIG Checklist
β
Use SF Symbols for icons
β
Sufficient spacing with .padding()
β
Define button styles with .buttonStyle()
β
Express state with .disabled()
β
Automatic Dark Mode support