Getting Started

SwiftUI Setup

// Create a new SwiftUI project in Xcode: // 1. File → New → Project // 2. Select iOS/macOS tab // 3. Choose App template // 4. Select SwiftUI for Interface // Basic SwiftUI App structure import SwiftUI @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() } } } // Basic View structure struct ContentView: View { var body: some View { Text("Hello, SwiftUI!") .padding() } } // Preview provider struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Note: SwiftUI requires Xcode 11+ and iOS 13+ or macOS 10.15+. The @main attribute identifies the app's entry point.

Swift Basics

// Variables and constants var mutableVariable = "This can change" // Mutable let constantValue = "This cannot change" // Immutable // Type annotations var name: String = "John" var age: Int = 30 var price: Double = 19.99 var isEnabled: Bool = true // Optionals (can be nil) var optionalName: String? = nil var unwrappedName = optionalName ?? "Default" // Nil coalescing // Control flow if age > 18 { print("Adult") } else { print("Minor") } // Switch statement switch age { case 0..18: print("Minor") case 18...65: print("Adult") default: print("Senior") } // Arrays and dictionaries var names: [String] = ["Alice", "Bob", "Charlie"] var person: [String: Any] = [ "name": "John", "age": 30, "isStudent": false ]
Note: Swift is a type-safe language with type inference. Use 'let' for constants and 'var' for variables. Optionals handle the absence of a value.

Views & Layouts

Basic Views

// Text view Text("Hello, World!") .font(.title) .foregroundColor(.blue) .bold() // Image view Image(systemName: "heart.fill") // SF Symbols .font(.largeTitle) .foregroundColor(.red) Image("myImage") // Asset catalog .resizable() .scaledToFit() .frame(width: 100, height: 100) // Button Button(action: { print("Button tapped") }) { Text("Tap me") .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) } // TextField @State private var username = "" TextField("Enter username", text: $username) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding() // Toggle @State private var isOn = false Toggle(isOn: $isOn) { Text("Enable feature") }
Note: SwiftUI views are value types that conform to the View protocol. Modifiers are used to customize their appearance and behavior.

Layout Containers

// VStack - Vertical stack VStack(alignment: .leading, spacing: 10) { Text("First item") Text("Second item") Text("Third item") } .padding() // HStack - Horizontal stack HStack(spacing: 20) { Image(systemName: "star.fill") Text("Rating: 4.5") Spacer() // Pushes content to the left } // ZStack - Overlapping views ZStack { Circle() .fill(Color.blue) .frame(width: 100, height: 100) Text("Center") .foregroundColor(.white) .font(.headline) } // List List { Section(header: Text("Section 1")) { Text("Item 1") Text("Item 2") } Section(header: Text("Section 2")) { ForEach(1...5, id: \.self) { number in Text("Item \(number)") } } } .listStyle(GroupedListStyle()) // ScrollView ScrollView { VStack { ForEach(1...50, id: \.self) { index in Text("Item \(index)") .padding() } } }
Note: Stacks are fundamental layout containers in SwiftUI. VStack arranges vertically, HStack horizontally, and ZStack overlays views.

Modifiers

Layout Modifiers

// Frame modifier Text("Hello") .frame(width: 200, height: 100) .frame(maxWidth: .infinity, maxHeight: .infinity) // Expand to fill available space // Padding Text("Padded text") .padding() // All sides with default padding .padding(10) // All sides with specific padding .padding(.horizontal, 20) // Horizontal only .padding([.top, .bottom], 10) // Top and bottom // Position and offset Text("Offset text") .offset(x: 10, y: 5) // Relative offset Text("Positioned text") .position(x: 100, y: 100) // Absolute position in parent // Alignment guides VStack(alignment: .leading) { Text("First") .alignmentGuide(.leading) { d in d[.leading] - 10 } Text("Second") }
Note: Modifiers return new views rather than modifying existing ones. The order of modifiers matters as each one wraps the previous view.

Appearance Modifiers

// Color modifiers Text("Colored text") .foregroundColor(.blue) // Text color .background(Color.yellow) // Background color .accentColor(.green) // Accent color for interactive elements // Font modifiers Text("Styled text") .font(.title) // System font .fontWeight(.bold) // Font weight .font(.system(size: 24, weight: .heavy, design: .rounded)) // Shape and border Text("Rounded background") .padding() .background(Color.blue) .cornerRadius(10) // Rounded corners Circle() .stroke(Color.red, lineWidth: 2) // Border .fill(Color.blue) // Fill color .frame(width: 100, height: 100) // Shadow and overlay Text("Shadow text") .shadow(color: .gray, radius: 2, x: 2, y: 2) Rectangle() .fill(Color.blue) .frame(width: 100, height: 100) .overlay( Text("Overlay") .foregroundColor(.white) ) .background( Circle() .fill(Color.red) .frame(width: 120, height: 120) )
Note: Overlay adds views on top of the current view, while background adds views behind it. The order of these modifiers affects the final result.

State Management

Property Wrappers

// @State - For local view state struct CounterView: View { @State private var count = 0 var body: some View { VStack { Text("Count: \(count)") Button("Increment") { count += 1 } } } } // @Binding - For sharing state with parent views struct ToggleView: View { @Binding var isOn: Bool var body: some View { Toggle(isOn: $isOn) { Text(isOn ? "On" : "Off") } } } // @ObservedObject - For external reference types class UserSettings: ObservableObject { @Published var username = "Anonymous" } struct SettingsView: View { @ObservedObject var settings: UserSettings var body: some View { TextField("Username", text: $settings.username) } } // @EnvironmentObject - For shared data across views class AppData: ObservableObject { @Published var userIsLoggedIn = false } struct ContentView: View { @EnvironmentObject var appData: AppData var body: some View { if appData.userIsLoggedIn { Text("Welcome!") } else { LoginView() } } } // In the app entry point ContentView() .environmentObject(AppData())
Note: @State is for value types owned by the view. @ObservedObject is for reference types that conform to ObservableObject. @EnvironmentObject is for shared data across the view hierarchy.

More State Management

// @StateObject - For creating & owning observable objects struct TimerView: View { @StateObject private var timer = TimerManager() var body: some View { Text("Time: \(timer.time)") } } class TimerManager: ObservableObject { @Published var time = 0 private var timer: Timer? init() { timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in self?.time += 1 } } } // @Environment - For accessing system-wide settings struct ThemeView: View { @Environment(\.colorScheme) var colorScheme var body: some View { Text(colorScheme == .dark ? "Dark Mode" : "Light Mode") } } // @AppStorage - For UserDefaults integration struct SettingsView: View { @AppStorage("username") var username = "Anonymous" @AppStorage("isDarkMode") var isDarkMode = false var body: some View { Form { TextField("Username", text: $username) Toggle("Dark Mode", isOn: $isDarkMode) } } } // @SceneStorage - For scene state restoration struct ContentView: View { @SceneStorage("selectedTab") var selectedTab = "home" var body: some View { TabView(selection: $selectedTab) { HomeView() .tabItem { Label("Home", systemImage: "house") } .tag("home") SettingsView() .tabItem { Label("Settings", systemImage: "gear") } .tag("settings") } } }
Note: @StateObject is similar to @ObservedObject but ensures the object isn't recreated during view updates. @AppStorage provides easy access to UserDefaults, while @SceneStorage helps with state restoration.

Animation

Implicit Animation

// Implicit animation with .animation() modifier struct AnimatedCircle: View { @State private var isTapped = false var body: some View { Circle() .fill(isTapped ? .red : .blue) .frame( width: isTapped ? 100 : 50, height: isTapped ? 100 : 50 ) .animation(.easeInOut(duration: 0.3), value: isTapped) .onTapGesture { isTapped.toggle() } } } // Spring animation Circle() .scaleEffect(isTapped ? 1.5 : 1.0) .animation( .spring(response: 0.3, dampingFraction: 0.2), value: isTapped ) // Multiple animations with different timing Rectangle() .fill(isTapped ? .green : .yellow) .animation(.easeInOut(duration: 0.5), value: isTapped) .rotationEffect(.degrees(isTapped ? 45 : 0)) .animation(.easeInOut(duration: 1.0), value: isTapped) // Animation with delay Text("Hello") .opacity(isVisible ? 1.0 : 0.0) .animation( .easeInOut(duration: 0.5) .delay(0.2), value: isVisible )
Note: Implicit animations automatically animate all animatable properties that change within the view. The animation is triggered when the specified value changes.

Explicit Animation

// Explicit animation with withAnimation struct AnimatedButton: View { @State private var isPressed = false var body: some View { Button(action: { withAnimation(.easeInOut(duration: 0.3)) { isPressed.toggle() } }) { Text("Press Me") .padding() .background(isPressed ? .red : .blue) .foregroundColor(.white) .cornerRadius(10) .scaleEffect(isPressed ? 0.9 : 1.0) } } } // Complex animation sequence Button("Animate") { withAnimation(.easeInOut(duration: 0.3)) { phase1.toggle() } withAnimation(.easeInOut(duration: 0.6).delay(0.3)) { phase2.toggle() } } // Custom animation withAnimation( .timingCurve(0.68, -0.6, 0.32, 1.6, duration: 0.8) ) { customValue.toggle() } // Transaction for more control var transaction = Transaction() transaction.animation = .easeInOut(duration: 0.5) transaction.disablesAnimations = false withTransaction(transaction) { animatedValue = newValue }
Note: Explicit animations using withAnimation give you more control over when and how animations occur. You can animate specific state changes and chain multiple animations together.

Advanced Topics

View Composition

// Extract subviews for better organization struct ProfileView: View { var body: some View { VStack { ProfileHeader() ProfileDetails() ProfileActions() } } } struct ProfileHeader: View { var body: some View { HStack { Image("avatar") .resizable() .frame(width: 60, height: 60) .clipShape(Circle()) VStack(alignment: .leading) { Text("John Doe") .font(.title2) Text("iOS Developer") .foregroundColor(.secondary) } Spacer() } .padding() } } // ViewBuilder for complex conditional views struct ConditionalView: View { var condition: Bool var body: some View { VStack { if condition { Text("Condition is true") Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) } else { Text("Condition is false") Image(systemName: "xmark.circle.fill") .foregroundColor(.red) } } } } // Custom view modifier struct CardModifier: ViewModifier { func body(content: Content) -> some View { content .padding() .background(Color.white) .cornerRadius(10) .shadow(color: .black.opacity(0.1), radius: 5, x: 0, y: 2) } } // Extension for easier use extension View { func cardStyle() -> some View { modifier(CardModifier()) } } // Usage Text("Card Content") .cardStyle()
Note: Breaking complex views into smaller subviews improves readability and maintainability. Custom view modifiers help apply consistent styling across your app.

Performance & Advanced

// LazyVStack and LazyHStack for efficient scrolling ScrollView { LazyVStack { ForEach(1...1000, id: \.self) { index in RowView(index: index) .onAppear { // Load more data when approaching end if index > items.count - 5 { loadMoreData() } } } } } // EquatableView for preventing unnecessary redraws struct UserView: View, Equatable { let user: User var body: some View { Text(user.name) } static func == (lhs: UserView, rhs: UserView) -> Bool { lhs.user.id == rhs.user.id } } // Using .equatable() UserView(user: currentUser) .equatable() // GeometryReader for responsive layouts struct ResponsiveView: View { var body: some View { GeometryReader { geometry in if geometry.size.width > 600 { HStack { Sidebar() MainContent() } } else { VStack { MainContent() Sidebar() } } } } } // PreferenceKey for communicating size information struct SizePreferenceKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() } } struct SizeReader: View { var body: some View { GeometryReader { geometry in Color.clear .preference(key: SizePreferenceKey.self, value: geometry.size) } } }
Note: Use Lazy stacks for better performance with large datasets. EquatableView prevents unnecessary view updates. GeometryReader helps create responsive layouts that adapt to different screen sizes.

Quick Reference

Property Wrappers Reference

Wrapper Purpose When to Use
@State Local view state Simple value types owned by the view
@Binding Two-way connection Sharing state with parent views
@ObservedObject External reference type Complex data owned elsewhere
@StateObject Owned observable object Creating and owning observable objects
@EnvironmentObject Shared data Data used across many views
@Environment System settings Accessing system values
@AppStorage UserDefaults Simple persistence
@SceneStorage Scene state restoration Preserving scene state

Layout Reference

Container Purpose Direction
VStack Vertical arrangement Top to bottom
HStack Horizontal arrangement Leading to trailing
ZStack Overlapping views Back to front
LazyVStack Efficient vertical scrolling Top to bottom
LazyHStack Efficient horizontal scrolling Leading to trailing
List Scrollable list of items Vertical (typically)
ScrollView Custom scrollable content Any direction
Form Grouped input controls Vertical (typically)

Ready to build amazing apps with SwiftUI?

Practice these concepts and explore the official SwiftUI documentation for more advanced techniques.