1

I would like to use SwiftUI previews for my view, which is built as a container that contains other Views. For example,

public struct ContainerView<Content: View>: View {

    private let title: String
    private let mainView: Content
    
    public init(title: String,
                @ViewBuilder mainView: () -> Content) {
        self.title = title
        self.mainView = mainView()
    }

    public var body: some View {
        VStack {
            Text(title)
            mainView
        }
    }
}

When I add a #Preview to it, it gives this error: Type of expression is ambiguous without a type annotation. Here's an example of a preview:

#Preview {
    let mainView = {
        Text("some view")
    }
    return SummarySectionView(title: "title string",
                              mainView: mainView)
}

Is is a limitation of Swift previews? Should I use another macro/function to display the preview?

Note: this works as soon as a remove the <Content: View> code, with the mainView attribute and so forth...

EDIT: Based on the comments, I was able to find what was wrong. My less contrived code had a second view parameter, like so:

public struct ContainerView<Content: View>: View {

    private let title: String
    private let mainView: Content
    private let secondaryView: Content
    
    public init(title: String,
                @ViewBuilder mainView: () -> Content,
                @ViewBuildeer secondaryView: () -> Content) {
        self.title = title
        self.mainView = mainView()
        self.secondaryView = secondaryView()
    }

    public var body: some View {
        VStack {
            Text(title)
            mainView
            secondaryView
        }
    }
}

Which is fine, except if my 2 views are different, like so:

#Preview {
    let mainView = {
        Text("some view")
    }
    let secondaryView = {
        Button(action: {}, label: {
            Text("Button example")
        })
    }
    return ContainerView(title: "title string",
                              mainView: mainView,
                              secondaryView: secondaryView)
}

Because I thought that mainView and secondaryView were both of type View, it would work, but it does not. Changing the view declaration to this: public struct ContainerView<MainContent: View, SecondaryView: View>: View and making the relevant changes everywhere else resolves my issue.

5
  • 1
    SummarySectionView != ContainerView, please fix your code. If I use ContainerView in the preview instead it works fine. Commented Jun 1, 2024 at 20:03
  • Very good catch... it's a left over of the real code (I renamed the classes and properties in the editor of the Question here). Indeed, this works. In my code, though, I had a second view of type Content but where I'd pass in a Button view instead. Is that why I was seeing the error? Both Text and Button are of type View; can't they both respect the same <Content: View> constraint? Commented Jun 1, 2024 at 23:48
  • Please edit the question to show some code where you "had a second view of type Content but where I'd pass in a Button view instead" then. It sounds like you are trying to get Content to be Text and Button at the same time. That obviously doesn't work - Content can only be one type. My guess is that you need a second type parameter, but I can't be sure without seeing your code. Commented Jun 2, 2024 at 1:23
  • Maybe this question is more about understanding generics in swift than about views. Commented Jun 2, 2024 at 6:22
  • Thank you for your comments. Based on them, I was able to make changes and make it work. Commented Jun 2, 2024 at 11:03

1 Answer 1

0

The view had to have 2 generic parameters, one for each view. The struct would look like this:

public struct ContainerView<MainContent: View, SecondaryContent: View>: View {

    private let title: String
    private let mainView: MainContent
    private let secondaryView: SecondaryContent
    
    public init(title: String,
                @ViewBuilder mainView: () -> MainContent,
                @ViewBuildeer secondaryView: () -> SecondaryContent) {
        self.title = title
        self.mainView = mainView()
        self.secondaryView = secondaryView()
    }

    public var body: some View {
        VStack {
            Text(title)
            mainView
            secondaryView
        }
    }
}

With this, I can have a Preview like so:

#Preview {
    let mainView = {
        Text("some view")
    }
    let secondaryView = {
        Button(action: {}, label: {
            Text("Button example")
        })
    }
    return ContainerView(title: "title string",
                         mainView: mainView,
                         secondaryView: secondaryView)
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.