DEV Community

The 7 SwiftUI Mistakes I See Every Junior Developer Make (And How to Fix Them)

I've reviewed hundreds of SwiftUI projects. The same mistakes keep showing up — and they're not the ones you'd expect.

These aren't syntax errors. They're architectural decisions that seem fine at first but create real pain as your app grows.

Mistake #1: Putting Everything in the View

This is the most common one. Views that are 300+ lines with networking calls, state management, data transformation, and UI all mixed together.

The problem:

struct ProfileView: View {
    @State private var user: User?
    @State private var isLoading = false
    @State private var posts: [Post] = []
    @State private var error: String?

    var body: some View {
        VStack {
            // 200 lines of UI code mixed with logic
        }
        .onAppear {
            Task {
                isLoading = true
                let url = URL(string: "https://api.example.com/user")!
                let (data, _) = try await URLSession.shared.data(from: url)
                user = try JSONDecoder().decode(User.self, from: data)
                // ... more networking code
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The fix: Use MVVM. Your View should only describe UI. Everything else goes in a ViewModel.

@MainActor
class ProfileViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var error: String?

    func loadProfile() async {
        isLoading = true
        defer { isLoading = false }
        // All logic lives here
    }
}

struct ProfileView: View {
    @StateObject private var vm = ProfileViewModel()

    var body: some View {
        // Clean UI code only
    }
}
Enter fullscreen mode Exit fullscreen mode

Mistake #2: Abusing @State for Everything

@State is for local, view-owned data. If data needs to be shared between views or persisted, you need a different tool.

Rule of thumb:

  • Data owned by this view only → @State
  • Data passed from parent → @Binding or let
  • Shared data (ViewModel) → @StateObject / @ObservedObject
  • App-wide data → @EnvironmentObject

Getting this wrong leads to data being out of sync across your app.

Mistake #3: Not Breaking Down Views

If your view body has more than ~50 lines, it probably needs to be broken into subviews.

Don't do this:

var body: some View {
    VStack {
        // Header section (30 lines)
        // Profile card (40 lines)  
        // Stats section (25 lines)
        // Posts list (50 lines)
        // Footer (20 lines)
    }
}
Enter fullscreen mode Exit fullscreen mode

Do this:

var body: some View {
    VStack {
        HeaderSection()
        ProfileCard(user: user)
        StatsSection(stats: stats)
        PostsList(posts: posts)
        FooterView()
    }
}
Enter fullscreen mode Exit fullscreen mode

Smaller views = easier testing, better previews, cleaner diffs.

Mistake #4: Ignoring the Environment

SwiftUI's environment system is incredibly powerful but underused. Instead of passing data through 5 levels of view hierarchy, inject it once.

// Instead of prop drilling:
ParentView(theme: theme)  ChildView(theme: theme)  GrandchildView(theme: theme)

// Use environment:
ParentView()
    .environmentObject(theme)

// Any descendant can access it:
struct GrandchildView: View {
    @EnvironmentObject var theme: ThemeManager
}
Enter fullscreen mode Exit fullscreen mode

Mistake #5: Fighting Navigation Instead of Embracing It

Pre-iOS 16 navigation was painful. But many developers still use old patterns when NavigationStack exists.

// Modern navigation pattern
NavigationStack(path: $router.path) {
    RootView()
        .navigationDestination(for: Route.self) { route in
            switch route {
            case .profile(let id): ProfileView(id: id)
            case .settings: SettingsView()
            case .detail(let item): DetailView(item: item)
            }
        }
}
Enter fullscreen mode Exit fullscreen mode

This gives you programmatic navigation, deep linking support, and clean architecture.

Mistake #6: Not Using ViewModifiers

If you're applying the same set of modifiers to multiple views, create a custom ViewModifier.

struct CardStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color(.systemBackground))
            .cornerRadius(12)
            .shadow(color: .black.opacity(0.1), radius: 8, y: 4)
    }
}

extension View {
    func cardStyle() -> some View {
        modifier(CardStyle())
    }
}

// Usage: clean and consistent
ProfileCard().cardStyle()
StatsCard().cardStyle()
Enter fullscreen mode Exit fullscreen mode

Mistake #7: Skipping Previews

Previews aren't just a nice-to-have. They're your fastest feedback loop. Every view should have a preview with realistic data.

#Preview {
    ProfileView()
        .environmentObject(ThemeManager())
        .environmentObject(Router())
}

#Preview("Loading State") {
    ProfileView()
        .environmentObject(ThemeManager())
}

#Preview("Error State") {
    ProfileView()
        .environmentObject(ThemeManager())
}
Enter fullscreen mode Exit fullscreen mode

Preview every state: loading, error, empty, populated. Your future self will thank you.

The Pattern

Notice something? Most of these mistakes come from the same root cause: treating SwiftUI like UIKit.

SwiftUI has its own mental model. The faster you adopt it, the better your code becomes.


I've built 27 products with SwiftUI and packaged all these patterns into production-ready templates and components. If you want to skip the learning curve and write clean SwiftUI from day one:

t.me/SwiftUIDaily — daily tips, code reviews, and ready-to-use SwiftUI resources.

Which of these mistakes have you made? (No judgment — I made all of them.) Share your experience in the comments.

Top comments (0)