Declarative UI Philosophy

SwiftUI represents a paradigm shift from imperative to declarative UI development. Instead of describing how to build the interface, we describe what the interface should look like for any given state.


// Traditional UIKit approach (imperative)
class ProfileViewController: UIViewController {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var profileImageView: UIImageView!
    @IBOutlet weak var followButton: UIButton!
    
    func updateUI(with user: User) {
        nameLabel.text = user.name
        profileImageView.image = user.profileImage
        
        if user.isFollowing {
            followButton.setTitle("Unfollow", for: .normal)
            followButton.backgroundColor = .systemRed
        } else {
            followButton.setTitle("Follow", for: .normal)
            followButton.backgroundColor = .systemBlue
        }
    }
}

// SwiftUI approach (declarative)
struct ProfileView: View {
    let user: User
    @State private var isFollowing: Bool
    
    var body: some View {
        VStack {
            AsyncImage(url: user.profileImageURL)
                .frame(width: 100, height: 100)
                .clipShape(Circle())
            
            Text(user.name)
                .font(.title)
            
            Button(isFollowing ? "Unfollow" : "Follow") {
                isFollowing.toggle()
            }
            .foregroundColor(.white)
            .padding()
            .background(isFollowing ? .red : .blue)
            .cornerRadius(8)
        }
    }
}





State Management Patterns

// @State for local component state
struct CounterView: View {
    @State private var count = 0
    
    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("+1") { count += 1 }
        }
    }
}

// @StateObject for view model lifecycle management
class UserViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false
    
    func loadUsers() async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            users = try await UserService.fetchUsers()
        } catch {
            print("Error loading users: \(error)")
        }
    }
}

struct UserListView: View {
    @StateObject private var viewModel = UserViewModel()
    
    var body: some View {
        NavigationView {
            Group {
                if viewModel.isLoading {
                    ProgressView("Loading...")
                } else {
                    List(viewModel.users) { user in
                        UserRowView(user: user)
                    }
                }
            }
            .navigationTitle("Users")
            .task {
                await viewModel.loadUsers()
            }
        }
    }
}

// @ObservedObject for shared objects
struct UserRowView: View {
    @ObservedObject var user: User
    
    var body: some View {
        HStack {
            AsyncImage(url: user.avatarURL)
                .frame(width: 40, height: 40)
                .clipShape(Circle())
            
            VStack(alignment: .leading) {
                Text(user.name)
                    .font(.headline)
                Text(user.email)
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
            
            Spacer()
            
            if user.isOnline {
                Circle()
                    .fill(.green)
                    .frame(width: 8, height: 8)
            }
        }
    }
}





Async/Await Integration

// Modern async data loading
struct AsyncDataView: View {
    let loadData: () async throws -> Void
    let content: () -> Content
    
    @State private var isLoading = false
    @State private var error: Error?
    
    var body: some View {
        Group {
            if isLoading {
                ProgressView()
            } else if let error = error {
                ErrorView(error: error) {
                    await reload()
                }
            } else {
                content()
            }
        }
        .task {
            await reload()
        }
    }
    
    private func reload() async {
        isLoading = true
        error = nil
        
        do {
            try await loadData()
        } catch {
            self.error = error
        }
        
        isLoading = false
    }
}

// Usage
struct PostsView: View {
    @State private var posts: [Post] = []
    
    var body: some View {
        AsyncDataView {
            posts = try await PostService.fetchPosts()
        } content: {
            List(posts) { post in
                PostRowView(post: post)
            }
        }
        .navigationTitle("Posts")
    }
}

// Custom AsyncImage with better error handling
struct RemoteImage: View {
    let url: URL?
    let placeholder: Image
    
    @State private var phase: AsyncImagePhase = .empty
    
    var body: some View {
        Group {
            switch phase {
            case .empty:
                placeholder
                    .foregroundColor(.gray)
            case .success(let image):
                image
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            case .failure:
                Image(systemName: "photo")
                    .foregroundColor(.red)
            @unknown default:
                placeholder
            }
        }
        .task {
            await loadImage()
        }
    }
    
    private func loadImage() async {
        guard let url = url else {
            phase = .failure(URLError(.badURL))
            return
        }
        
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            if let uiImage = UIImage(data: data) {
                phase = .success(Image(uiImage: uiImage))
            } else {
                phase = .failure(URLError(.cannotDecodeContentData))
            }
        } catch {
            phase = .failure(error)
        }
    }
}





Custom View Modifiers

// Reusable card style modifier
struct CardModifier: ViewModifier {
    let backgroundColor: Color
    let cornerRadius: CGFloat
    let shadowRadius: CGFloat
    
    func body(content: Content) -> some View {
        content
            .padding()
            .background(backgroundColor)
            .cornerRadius(cornerRadius)
            .shadow(radius: shadowRadius)
    }
}

extension View {
    func cardStyle(
        backgroundColor: Color = .white,
        cornerRadius: CGFloat = 8,
        shadowRadius: CGFloat = 2
    ) -> some View {
        modifier(CardModifier(
            backgroundColor: backgroundColor,
            cornerRadius: cornerRadius,
            shadowRadius: shadowRadius
        ))
    }
}

// Loading state modifier
struct LoadingModifier: ViewModifier {
    let isLoading: Bool
    
    func body(content: Content) -> some View {
        ZStack {
            content
                .disabled(isLoading)
                .blur(radius: isLoading ? 2 : 0)
            
            if isLoading {
                ProgressView()
                    .scaleEffect(1.5)
            }
        }
    }
}

extension View {
    func loading(_ isLoading: Bool) -> some View {
        modifier(LoadingModifier(isLoading: isLoading))
    }
}

// Usage
struct ProductCard: View {
    let product: Product
    @State private var isLoading = false
    
    var body: some View {
        VStack(alignment: .leading) {
            RemoteImage(
                url: product.imageURL,
                placeholder: Image(systemName: "photo")
            )
            .frame(height: 200)
            
            Text(product.name)
                .font(.headline)
            
            Text(product.price, format: .currency(code: "USD"))
                .font(.title2)
                .fontWeight(.bold)
        }
        .cardStyle()
        .loading(isLoading)
        .onTapGesture {
            Task {
                isLoading = true
                await purchaseProduct()
                isLoading = false
            }
        }
    }
    
    private func purchaseProduct() async {
        // Purchase logic
    }
}





Opaque Types and some View

// Understanding opaque return types
protocol Shape {
    func area() -> Double
}

// Without opaque types - exposes implementation
func makeCircle() -> Circle {
    return Circle(radius: 5)
}

// With opaque types - hides implementation
func makeShape() -> some Shape {
    return Circle(radius: 5) // Could return any Shape
}

// SwiftUI leverages this heavily
struct ContentView: View {
    var body: some View { // Opaque type
        VStack {
            Text("Hello")
            Button("Tap me") { }
        }
        // Compiler knows exact type, we don't need to
    }
}

// Custom containers with opaque types
struct ConditionalContainer: View {
    let condition: Bool
    let trueContent: TrueContent
    let falseContent: FalseContent
    
    var body: some View {
        Group {
            if condition {
                trueContent
            } else {
                falseContent
            }
        }
    }
}

// ViewBuilder for custom container views
struct CustomCard: View {
    let title: String
    @ViewBuilder let content: () -> Content
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(title)
                .font(.headline)
                .padding(.bottom, 4)
            
            content()
        }
        .cardStyle()
    }
}

// Usage
struct ProfileCard: View {
    let user: User
    
    var body: some View {
        CustomCard(title: "Profile") {
            HStack {
                AsyncImage(url: user.avatarURL)
                    .frame(width: 50, height: 50)
                    .clipShape(Circle())
                
                VStack(alignment: .leading) {
                    Text(user.name)
                        .font(.headline)
                    Text(user.email)
                        .font(.caption)
                        .foregroundColor(.secondary)
                }
            }
            
            Divider()
            
            Text(user.bio)
                .font(.body)
        }
    }
}





Advanced Composition Patterns

// Generic list view with pull-to-refresh
struct RefreshableList: View {
    let items: [Item]
    let onRefresh: () async -> Void
    @ViewBuilder let rowContent: (Item) -> RowContent
    
    var body: some View {
        List(items, id: \.id) { item in
            rowContent(item)
        }
        .refreshable {
            await onRefresh()
        }
    }
}

// Environment-based theming
struct Theme {
    let primaryColor: Color
    let secondaryColor: Color
    let backgroundColor: Color
    let textColor: Color
}

private struct ThemeKey: EnvironmentKey {
    static let defaultValue = Theme(
        primaryColor: .blue,
        secondaryColor: .gray,
        backgroundColor: .white,
        textColor: .black
    )
}

extension EnvironmentValues {
    var theme: Theme {
        get { self[ThemeKey.self] }
        set { self[ThemeKey.self] = newValue }
    }
}

// Themed button component
struct ThemedButton: View {
    let title: String
    let action: () -> Void
    
    @Environment(\.theme) private var theme
    
    var body: some View {
        Button(title, action: action)
            .foregroundColor(.white)
            .padding()
            .background(theme.primaryColor)
            .cornerRadius(8)
    }
}

// App with theme
struct ThemedApp: View {
    @State private var isDarkMode = false
    
    private var currentTheme: Theme {
        isDarkMode ? darkTheme : lightTheme
    }
    
    private let lightTheme = Theme(
        primaryColor: .blue,
        secondaryColor: .gray,
        backgroundColor: .white,
        textColor: .black
    )
    
    private let darkTheme = Theme(
        primaryColor: .orange,
        secondaryColor: .secondary,
        backgroundColor: .black,
        textColor: .white
    )
    
    var body: some View {
        NavigationView {
            VStack {
                Toggle("Dark Mode", isOn: $isDarkMode)
                    .padding()
                
                ThemedButton("Primary Action") {
                    print("Action performed")
                }
                
                Spacer()
            }
            .navigationTitle("Themed App")
        }
        .environment(\.theme, currentTheme)
        .background(currentTheme.backgroundColor)
    }
}





Performance Optimization

// Lazy loading with LazyVStack
struct OptimizedListView: View {
    let items: [LargeDataItem]
    
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 8) {
                ForEach(items) { item in
                    ExpensiveRowView(item: item)
                        .id(item.id) // Explicit ID for better performance
                }
            }
            .padding()
        }
    }
}

// Memoization with @State and computed properties
struct OptimizedCalculationView: View {
    @State private var numbers: [Int] = []
    @State private var calculationResult: Int?
    
    // Expensive calculation only when numbers change
    private var expensiveCalculation: Int {
        if calculationResult == nil {
            calculationResult = numbers.reduce(0) { result, number in
                // Simulate expensive operation
                Thread.sleep(forTimeInterval: 0.001)
                return result + number * number
            }
        }
        return calculationResult ?? 0
    }
    
    var body: some View {
        VStack {
            Text("Result: \(expensiveCalculation)")
            
            Button("Add Random Number") {
                numbers.append(Int.random(in: 1...100))
                calculationResult = nil // Invalidate cache
            }
        }
    }
}

// Efficient image caching
@MainActor
class ImageCache: ObservableObject {
    private var cache: [URL: UIImage] = [:]
    
    func image(for url: URL) -> UIImage? {
        return cache[url]
    }
    
    func setImage(_ image: UIImage, for url: URL) {
        cache[url] = image
    }
}

struct CachedAsyncImage: View {
    let url: URL
    @StateObject private var imageCache = ImageCache()
    @State private var image: UIImage?
    
    var body: some View {
        Group {
            if let image = image {
                Image(uiImage: image)
                    .resizable()
            } else {
                ProgressView()
                    .task {
                        await loadImage()
                    }
            }
        }
    }
    
    private func loadImage() async {
        // Check cache first
        if let cachedImage = imageCache.image(for: url) {
            image = cachedImage
            return
        }
        
        // Load from network
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            if let loadedImage = UIImage(data: data) {
                imageCache.setImage(loadedImage, for: url)
                image = loadedImage
            }
        } catch {
            print("Failed to load image: \(error)")
        }
    }
}





Best Practices

  • Embrace declarative thinking - describe what, not how
  • Use @StateObject for view models and @ObservedObject for shared objects
  • Leverage async/await for modern data loading patterns
  • Create reusable components with ViewBuilder and generics
  • Use environment values for app-wide configuration
  • Optimize with lazy loading for large datasets
  • Cache expensive operations and invalidate when needed