DEV Community

10 SwiftUI Mistakes Every Beginner Makes (And How to Fix Them)

When I started learning SwiftUI two years ago, I made every mistake in the book. After building 10+ production apps, here are the most common pitfalls I see beginners fall into — and how to fix each one.

1. Putting Everything in One View

The mistake:

struct ContentView: View {
    @State private var username = ""
    @State private var password = ""
    @State private var isLoggedIn = false
    @State private var posts: [Post] = []
    @State private var isLoading = false
    // ... 20 more state variables

    var body: some View {
        // 200+ lines of nested views
    }
}
Enter fullscreen mode Exit fullscreen mode

The fix: Break your views into small, focused components. Each view should do ONE thing.

struct LoginView: View {
    @State private var username = ""
    @State private var password = ""

    var body: some View {
        VStack(spacing: 16) {
            UsernameField(text: $username)
            PasswordField(text: $password)
            LoginButton(username: username, password: password)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Rule of thumb: If your view file is over 100 lines, it's time to extract subviews.


2. Using @State When You Need @StateObject

The mistake:

struct ProfileView: View {
    @State var viewModel = ProfileViewModel() // WRONG

    var body: some View {
        Text(viewModel.name)
    }
}
Enter fullscreen mode Exit fullscreen mode

The problem: @State will recreate your view model every time the parent view re-renders.

The fix:

struct ProfileView: View {
    @StateObject var viewModel = ProfileViewModel() // CORRECT

    var body: some View {
        Text(viewModel.name)
    }
}
Enter fullscreen mode Exit fullscreen mode

Quick rule:

  • @StateObject — you CREATE the object (owner)
  • @ObservedObject — you RECEIVE the object (child)
  • @EnvironmentObject — you access a SHARED object

3. Force Unwrapping Optionals in Views

The mistake:

Text(user.name!) // Crash if nil
Image(user.avatarURL!) // Crash if nil
Enter fullscreen mode Exit fullscreen mode

The fix: Always provide fallbacks:

Text(user.name ?? "Anonymous")

if let avatarURL = user.avatarURL {
    AsyncImage(url: avatarURL)
} else {
    Image(systemName: "person.circle.fill")
}
Enter fullscreen mode Exit fullscreen mode

Your app should never crash because of a nil value in the UI.


4. Ignoring the Environment

The mistake: Hardcoding values that should adapt:

Text("Hello")
    .foregroundColor(.black) // Invisible in dark mode!
    .font(.system(size: 16)) // Ignores accessibility
Enter fullscreen mode Exit fullscreen mode

The fix: Use semantic colors and dynamic type:

Text("Hello")
    .foregroundStyle(.primary) // Adapts to dark mode
    .font(.body) // Respects user's text size settings
Enter fullscreen mode Exit fullscreen mode

Always test your app in:

  • Dark mode
  • Large text sizes (Accessibility)
  • Different device sizes

5. Nesting NavigationStack Inside Child Views

The mistake:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            DetailView()
        }
    }
}

struct DetailView: View {
    var body: some View {
        NavigationStack { // DOUBLE NavigationStack!
            Text("Detail")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This creates a double navigation bar and weird behavior.

The fix: Only ONE NavigationStack at the root level:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            DetailView() // No NavigationStack here
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

6. Making Network Calls in View init or body

The mistake:

struct PostsView: View {
    @State var posts: [Post] = []

    var body: some View {
        List(posts) { post in
            Text(post.title)
        }
        // This gets called on EVERY re-render:
        .onAppear { loadPosts() }
    }
}
Enter fullscreen mode Exit fullscreen mode

onAppear fires every time the view appears, including when you come back from a navigation push.

The fix: Use .task and track loading state:

struct PostsView: View {
    @State var posts: [Post] = []
    @State var hasLoaded = false

    var body: some View {
        List(posts) { post in
            Text(post.title)
        }
        .task {
            guard !hasLoaded else { return }
            posts = await API.fetchPosts()
            hasLoaded = true
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

.task is automatically cancelled when the view disappears — no memory leaks.


7. Not Using Preview Properly

The mistake: Writing code, building to simulator, checking the result, going back to code. Slow feedback loop.

The fix: Use #Preview for instant feedback:

#Preview {
    ProfileCard(user: .preview)
        .padding()
}

// Create preview data:
extension User {
    static var preview: User {
        User(name: "Daniil", role: "iOS Developer")
    }
}
Enter fullscreen mode Exit fullscreen mode

Previews save hours of development time. Use them for every view.


8. Using GeometryReader Everywhere

The mistake:

GeometryReader { geo in
    Text("Hello")
        .frame(width: geo.size.width * 0.8)
}
Enter fullscreen mode Exit fullscreen mode

GeometryReader takes up all available space and breaks layouts.

The fix: Use built-in layout modifiers first:

Text("Hello")
    .frame(maxWidth: .infinity) // Full width
    .padding(.horizontal) // With padding

// Or use percentages with flexible frames:
HStack {
    Color.blue.frame(maxWidth: .infinity) // 50%
    Color.red.frame(maxWidth: .infinity)  // 50%
}
Enter fullscreen mode Exit fullscreen mode

Only use GeometryReader when you truly need the parent's exact dimensions.


9. Forgetting about Animation

The mistake: Abrupt state changes with no transitions:

Button("Toggle") {
    showDetail = true // Instant, jarring change
}
Enter fullscreen mode Exit fullscreen mode

The fix: Add withAnimation for smooth transitions:

Button("Toggle") {
    withAnimation(.spring(duration: 0.3)) {
        showDetail = true
    }
}

// Or use the .animation modifier:
Rectangle()
    .frame(width: isExpanded ? 200 : 100)
    .animation(.easeInOut, value: isExpanded)
Enter fullscreen mode Exit fullscreen mode

Subtle animations make your app feel polished and professional.


10. Not Handling Loading and Error States

The mistake:

struct PostsView: View {
    @State var posts: [Post] = []

    var body: some View {
        List(posts) { post in
            PostRow(post: post)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

What does the user see while data is loading? An empty screen. What if the request fails? Still an empty screen.

The fix: Always handle three states:

struct PostsView: View {
    @State var posts: [Post] = []
    @State var isLoading = true
    @State var error: Error?

    var body: some View {
        Group {
            if isLoading {
                ProgressView("Loading posts...")
            } else if let error {
                ContentUnavailableView(
                    "Failed to load",
                    systemImage: "wifi.slash",
                    description: Text(error.localizedDescription)
                )
            } else if posts.isEmpty {
                ContentUnavailableView.search
            } else {
                List(posts) { post in
                    PostRow(post: post)
                }
            }
        }
        .task { await loadPosts() }
    }
}
Enter fullscreen mode Exit fullscreen mode

Quick Reference Table

Mistake Fix
Massive views Extract subviews at 100 lines
@State for objects Use @StateObject
Force unwrapping Provide default values
Hardcoded colors Use semantic styles
Nested NavigationStack One at root level
onAppear for API calls Use .task modifier
No previews Create #Preview for every view
GeometryReader overuse Use built-in layout first
No animations Add withAnimation
Missing loading states Handle loading/error/empty

Want More SwiftUI Resources?

I've put together a collection of free resources for iOS developers — SwiftUI checklists, architecture templates, and practical guides.

Check them out:


What's the worst SwiftUI mistake you've made? Share in the comments!

Follow me for more SwiftUI content: @peasee163

Top comments (0)