genericsswiftuiswift5.7

SwiftUI and Swift 5.7: Issue with View protocol and Opaque Types


I have a compilation error when i try to return different custom views that conform to the View protocol from a method. Here is a simple example that describes the issue.

I want to have a method that returns a different custom view based on the enum case. When i try to achieve that, i get different compile errors:

    enum AppScreen: String, CaseIterable {
        case home
        case detail
    }
    
    struct ContentView: View {
        var body: some View {
            NavigationView {
                List {
                    ForEach(AppScreen.allCases, id: \.self) { screen in
                        NavigationLink(destination: getSomeView(screen)) {
                            Text(screen.rawValue)
                        }
                    }
                }
                List {
                    ForEach(AppScreen.allCases, id: \.self) { screen in
                        NavigationLink(destination: getAnyView(screen)) {
                            Text(screen.rawValue)
                        }
                    }
                }
            }
        }
    
        private func getSomeView(_ screen: AppScreen) -> some View {
            switch screen {
            case .home:
                return HomeView()
            case .detail:
                return DetailView()
            }
        }
        
        private func getAnyView(_ screen: AppScreen) -> any View {
            switch screen {
            case .home:
                return HomeView()
            case .detail:
                return DetailView()
            }
        }
    }

The method getSomeView throws the following compile error: Function declares an opaque return type 'some View', but the return statements in its body do not have matching underlying types

The method getAnyView compiles, but i get the following error when i call it as the destination for the NavigationLink: Type 'any View' cannot conform to 'View'

I'm learning SwiftUI and the new generics features from Swift 5.7. I believe that the behavior that i'm looking for can be achieved. Any help or guidance will be appreciated, thanks in advance!


Solution

  • This is how to achieve what you are trying to do.

    1. Mark getSomeView() with @ViewBuilder. This makes it work like var body which is also a ViewBuilder allowing you to build different types of views.
    2. Remove the return statements.

    Here is a standalone example based upon your original code:

    enum AppScreen: String, CaseIterable {
        case home
        case detail
    }
    
    struct HomeView: View {
        var body: some View {
            Text("HomeView")
        }
    }
    
    struct DetailView: View {
        var body: some View {
            Text("DetailView")
        }
    }
    
    struct ContentView: View {
        var body: some View {
            NavigationView {
                List {
                    ForEach(AppScreen.allCases, id: \.self) { screen in
                        NavigationLink(destination: getSomeView(screen)) {
                            Text(screen.rawValue)
                        }
                    }
                }
            }
        }
    
        @ViewBuilder
        private func getSomeView(_ screen: AppScreen) -> some View {
            switch screen {
            case .home:
                HomeView()
            case .detail:
                DetailView()
            }
        }
    }