skip to Main Content

The code:

import SwiftUI

public struct Snackbar<Content>: View where Content: View {
    private var content: Content

// Works OK
    public init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    init(_ text: String) {
        self.init {
            Text(text) // cannot convert value of type 'Text' to closure result type 'Content'
                .font(.subheadline)
                .foregroundColor(.white)
                .multilineTextAlignment(.leading)
        }
    }

    public var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: 4) {
                content
            }
            Spacer()
        }
        .frame(maxWidth: .infinity,
               minHeight: 26)
        .padding(.fullPadding)
        .background(Color.black)
        .clipShape(RoundedRectangle(cornerRadius: .defaultCornerRadius))
        .shadow(color: Color.black.opacity(0.125), radius: 4, y: 4)
        .padding()
    }
}

I’m getting this error:

cannot convert value of type ‘Text’ to closure result type ‘Content’

The goal I’m trying to achieve is to have 2 separate initializers, one for the content of type View and the other is a shortcut for a string, which will place a predefined Text component with some styling in place of Content.

Why am I getting this error if Text is some View and I think it should compile.

3

Answers


  1. One way is to make content optional and use another text var and show view based on a nil value.

    public struct Snackbar<Content>: View where Content: View {
        private var content: Content? // <= Here
        private var text: String = "" // <= Here
        
        // Works OK
        public init(@ViewBuilder content: () -> Content) {
            self.content = content()
        }
        
        init(_ text: String) {
            self.text = text // <= Here
        }
        
        public var body: some View {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    if let content = content { // <= Here
                        content
                    } else {
                        Text(text)
                            .font(.subheadline)
                            .foregroundColor(.white)
                            .multilineTextAlignment(.leading)
                    }
                }
                Spacer()
            }
            // Other code
    
    


    You can also use AnyView

    public struct Snackbar: View {
        private var content: AnyView // Here
        
        // Works OK
        public init<Content: View>(@ViewBuilder content: () -> Content) {
            self.content = AnyView(content()) // Here
        }
        
        init(_ text: String) {
            self.content = AnyView(Text(text)
                                .font(.subheadline)
                                .foregroundColor(.white)
                                .multilineTextAlignment(.leading)
            ) // Here
        }
        
        public var body: some View {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    self.content
                }
                Spacer()
            }
    
    Login or Signup to reply.
  2. You can specify the type of Content.

    Code:

    public struct Snackbar<Content>: View where Content: View {
        private var content: Content
    
    // Works OK
        public init(@ViewBuilder content: () -> Content) {
            self.content = content()
        }
    
        init(_ text: String) where Content == ModifiedContent<Text, _EnvironmentKeyWritingModifier<TextAlignment>> {
            self.init {
                Text(text)
                    .font(.subheadline)
                    .foregroundColor(.white)
                    .multilineTextAlignment(.leading) as! ModifiedContent<Text, _EnvironmentKeyWritingModifier<TextAlignment>>
            }
        }
    
        /* ... */
    }
    

    The only difference here is the where after the init and the force-cast to the type inside the init.

    To avoid the specific type, you can abstract this into a separate view:

    init(_ text: String) where Content == ModifiedText {
        self.init {
            ModifiedText(text: text)
        }
    }
    
    /* ... */
    
    struct ModifiedText: View {
        let text: String
    
        var body: some View {
            Text(text)
                .font(.subheadline)
                .foregroundColor(.white)
                .multilineTextAlignment(.leading)
        }
    }
    
    Login or Signup to reply.
  3. The general-purpose solution to this is to provide a wrapper that is semantically equivalent to some View. AnyView is built in and serves that purpose.

    init(_ text: String) where Content == AnyView {
      self.init {
        AnyView(
          Text(text)
            .font(.subheadline)
            .foregroundColor(.white)
            .multilineTextAlignment(.leading)
        )
      }
    }
    

    Also, change your code to

    private let content: () -> Content
    
    public init(@ViewBuilder content: @escaping () -> Content) {
      self.content = content
    }
    

    so that you don’t have to wrap the result of content in another closure.

    VStack(alignment: .leading, spacing: 4, content: content)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search