🌐 KO

πŸ“± 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

πŸ“¦ Learning Resources

πŸ’»
GitHub Project
🍎
Apple HIG Docs
πŸ“–
Apple Official Tutorial

πŸ“Ž Apple Official Resources

πŸ“˜ Documentation πŸ’» Sample Code 🎬 WWDC Sessions