Search code examples
iosswiftswiftuicollectionview

Custom UIHostingConfiguration


in iOS16, UIHostingConfiguration was newly introduced.

I tired to using it but meet a problems of margin. I did not configure margin but that struct default values have a margin. (not sure, but depend on size...)

so I add like this.

UIHostingConfiguration {
    ContentView()
}
.margin(.all, 0)

this code works well. but I want to make a new init or new struct for removing .margin(.all, 0) < --- this line.

I tired like bottom

extension UIHostingConfiguration {
    static func noMargin<Content: View>(content: @ViewBuilder () -> Content) -> UIHostingConfiguration {
        return UIHostingConfiguration(content: content)
            .margins(.all, 0)
    }
}

but it comes out error like this.

Cannot convert value of type '() -> Content' to expected argument type '() -> Content'

please tell me is there any good idea.


Solution

  • Notice that UIHostingConfiguration has two generic type parameters - Content and Background

    The initialiser of UIHostingConfiguration you are using requires that Background is EmptyView, i.e. the initialiser creates a configuration with no background. The background can be set later with the background modifier.

    If you call this initialiser without specifying type parameters, you are implicitly using Self.Content and Self.Background, and since you did not constrain Self.Background, it could be any View. For example, if I did:

    UIHostingConfiguration<Text, Rectangle>.noMargins { ... }
    

    then Self.Background would be Rectangle!

    You can constrain Self.Background to fix this issue:

    extension UIHostingConfiguration where Background == EmptyView
    

    The consequence of implicitly using Self.Content is that Self.Content could be different from the Content type parameter of the noMargins method, which is why you get the seemingly nonsense "cannot convert from () -> Content to () -> Content" error. It's actually saying "cannot convert from () -> Content to () -> Self.Content".

    To fix this, simply remove the Content type parameter from noMargins. Then the content view builder parameter would be of type () -> Self.Content.

    extension UIHostingConfiguration where Background == EmptyView {
        static func noMargin(@ViewBuilder content: () -> Content) -> UIHostingConfiguration {
            return UIHostingConfiguration<Content, EmptyView>(content: content)
                .margins(.all, 0)
        }
    }
    

    Note that explicitly specifying the initialiser's type arguments also works, but then the caller of noMargins would also need to specify the type arguments of UIHostingConfiguration, since there is no other way to infer what Content and Background should be on the caller's side.

    extension UIHostingConfiguration {
        static func noMargin<Content: View>(@ViewBuilder content: () -> Content) -> UIHostingConfiguration<Content, EmptyView> {
            return UIHostingConfiguration<Content, EmptyView>(content: content)
                .margins(.all, 0)
        }
    }
    

    Caller:

    // Caller needs to put two random views as the type parameter. Anything will work.
    UIHostingConfiguration<Text, Spacer>.noMargin {
        ...
    }