Skip to content
All essays
iOSMarch 27, 202511 min

Swift UI: Modern Declarative UI Programming

Create modern UI with Swift UI and UIKit integration. Combine, state management, and hybrid apps.

Ü
Ümit Uz
Mobile & Full Stack Developer

Swift UI provides a modern declarative way to build UI. Learn to integrate with UIKit and create powerful hybrid apps.

Swift UI Fundamentals

Declarative UI

swift
import SwiftUI

struct ContentView: View {
    @State private var isOn = false

    var body: some View {
        VStack {
            Image(systemName: "swift")
                .imageScale(.large)
                .foregroundColor(.orange)
                .padding()

            Text("Hello, SwiftUI!")
                .font(.title)
                .fontWeight(.bold)

            Toggle("Enable", isOn: $isOn)
                .padding()
        }
    }
}

UIKit Integration

UIViewControllerRepresentable

swift
struct UIKitControllerView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIKitView {
        return UIKitView()
    }

    func updateUIViewController(_ uiViewController: UIKitView, context: Context) {}
}

class UIKitView: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        let label = UILabel()
        label.text = "UIKit View"
        label.textAlignment = .center
        label.frame = view.bounds
        view.addSubview(label)
    }
}

UIViewRepresentable

struct CustomUIView: UIViewRepresentable {
    var body: some View {
        CustomUIViewRepresentable()
    }
}

struct CustomUIViewRepresentable: UIViewRepresentable {
    func makeUIView(context: Context) -> UICustomView {
        UICustomView(frame: .zero)
    }

    func updateUIView(_ uiView: UICustomView, context: Context) {
        uiView.updateContent()
    }
}

class UICustomView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }

    private func setupView() {
        backgroundColor = .systemBackground
    }

    func updateContent() {
        setNeedsDisplay()
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)

        guard let context = UIGraphicsGetCurrentContext() else { return }

        context.setFillColor(UIColor.blue.cgColor)
        context.fill(CGRect(x: 50, y: 50, width: 100, height: 100))
    }
}

Hosting Controllers

UIHostingController

swift
import SwiftUI

class HostingControllerManager {
    func createHostingController() -> UIHostingController<ContentView> {
        let contentView = ContentView()
        let hostingController = UIHostingController(rootView: contentView)
        hostingController.title = "Swift UI View"
        return hostingController
    }

    func presentSwiftUIView(in viewController: UIViewController) {
        let contentView = ContentView()
        let hostingController = UIHostingController(rootView: contentView)
        hostingController.modalPresentationStyle = .pageSheet
        viewController.present(hostingController, animated: true)
    }
}

State Management

ObservableObject

swift
class AppStateManager: ObservableObject {
    @Published var isLoggedIn = false
    @Published var user: User?
    @Published var isLoading = false

    func login() async throws {
        isLoading = true
        defer { isLoading = false }

        // Simulate login
        try await Task.sleep(nanoseconds: 2 * 1_000_000_000)

        await MainActor.run {
            self.user = User(id: "123", name: "John Doe")
            self.isLoggedIn = true
        }
    }

    func logout() {
        isLoggedIn = false
        user = nil
    }

    func refreshUser() async throws {
        // Refresh user data
    }
}

struct User {
    let id: String
    let name: String
}

struct LoginView: View {
    @StateObject private var stateManager = AppStateManager()

    var body: some View {
        VStack(spacing: 20) {
            if stateManager.isLoading {
                ProgressView("Logging in...")
            } else if stateManager.isLoggedIn {
                VStack {
                    Text("Welcome, \(stateManager.user?.name ?? "")")
                    Button("Logout") {
                        stateManager.logout()
                    }
                }
            } else {
                Button("Login") {
                    Task {
                        try? await stateManager.login()
                    }
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
        }
    }
}

Environment Objects

Environment Values

swift
struct EnvironmentView: View {
    @Environment(\.colorScheme) var colorScheme
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    @Environment(\.sizeCategory) var sizeCategory

    var body: some View {
        VStack {
            Text("Color Scheme: \(colorScheme == .dark ? "Dark" : "Light")")
            Text("Size Class: \(horizontalSizeClass)")
            Text("Size Category: \(sizeCategory)")
        }
    }
}

struct CustomEnvironmentKey: EnvironmentKey {
    typealias Value = String
}

struct EnvironmentKeyView: View {
    @Environment(CustomEnvironmentKey.self) var customValue

    var body: some View {
        Text("Custom Value: \(customValue)")
    }
}

Preference Keys

UserDefaults Integration

swift
extension UserDefaults {
    struct Keys {
        static let hasCompletedOnboarding = "hasCompletedOnboarding"
        static let userTheme = "userTheme"
    }

    var hasCompletedOnboarding: Bool {
        get { bool(forKey: Keys.hasCompletedOnboarding) }
        set { set(newValue, forKey: Keys.hasCompletedOnboarding) }
    }
}

struct SettingsView: View {
    @AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
    @AppStorage("userTheme") private var userTheme = "light"

    var body: some View {
        Form {
            Section {
                Toggle("Onboarding Completed", isOn: $hasCompletedOnboarding)

                Picker("Theme", selection: $userTheme) {
                    Text("Light").tag("light")
                    Text("Dark").tag("dark")
                    Text("System").tag("system")
                }
            }
        }
    }
}

Combine Integration

Publisher

swift
import Combine

class ViewModel: ObservableObject {
    @Published var items: [Item] = []
    @Published var isLoading = false

    private var cancellables = Set<AnyCancellable>()

    func loadItems() {
        isLoading = true

        // Simulate network request
        Just([Item(id: "1", name: "Item 1"), Item(id: "2", name: "Item 2")])
            .delay(for: .seconds(2))
            .receive(on: DispatchQueue.main)
            .sink { [weak self] items in
                self?.items = items
                self?.isLoading = false
            }
            .store(in: &cancellables)
    }

    func refresh() {
        loadItems()
    }
}

struct Item: Identifiable, Equatable {
    let id: String
    let name: String
}

struct ItemsView: View {
    @StateObject private var viewModel = ViewModel()

    var body: some View {
        List {
            if viewModel.isLoading {
                ProgressView()
            } else {
                ForEach(viewModel.items) { item in
                    Text(item.name)
                }
            }
        }
        .task {
            viewModel.loadItems()
        }
        .refreshable {
            viewModel.refresh()
        }
    }
}
swift
struct NavigationExampleView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("Detail View") {
                    DetailView()
                }

                NavigationLink("Settings") {
                    SettingsView()
                }
            }
            .navigationTitle("Home")
        }
    }
}

struct DetailView: View {
    @Environment(\.dismiss) var dismiss

    var body: some View {
        VStack {
            Text("Detail View")
                .font(.title)

            Button("Back") {
                dismiss()
            }
        }
        .navigationTitle("Detail")
    }
}

struct SettingsView: View {
    var body: some View {
        VStack {
            Text("Settings")
                .font(.title)
        }
        .navigationTitle("Settings")
    }
}

Animations

SwiftUI Animations

swift
struct AnimationView: View {
    @State private var isAnimating = false

    var body: some View {
        VStack {
            Circle()
                .fill(isAnimating ? Color.blue : Color.gray)
                .frame(width: 100, height: 100)
                .rotationEffect(.degrees(isAnimating ? 360 : 0))
                .animation(.spring(response: 0.6, dampingFraction: 0.7), value: isAnimating)

            Button("Animate") {
                withAnimation {
                    isAnimating.toggle()
                }
            }
        }
    }
}

Best Practices

  1. 1Declarative: Use declarative syntax
  2. 2State Management: Use appropriate patterns
  3. 3Performance: Optimize for 60 FPS
  4. 4Accessibility: Support accessibility
  5. 5Testing: Test SwiftUI views
  6. 6Hybrid: Integrate with UIKit when needed
  7. 7Data Flow: Use unidirectional data flow
  8. 8Localization: Support multiple languages

Swift UI provides modern, declarative UI development!

Related essays

Next essay
Swift Advanced Patterns: Professional Development