Home > Enterprise >  In SwiftUI, is there a way to preserve the type when applying a view modifier?
In SwiftUI, is there a way to preserve the type when applying a view modifier?

Time:02-18

My question is simply as the title states. I want to take some View, apply a view modifier to it, and still be able to keep it as that original type instead of it becoming a some View.

Say we have a simple view like this:

struct SomeView: View {
    let image: Image

    var body: some View {
        image
    }
}

In the PreviewProvider, we can test this out with a system image from SF Symbols:

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            SomeView(image: Image.init(systemName: "pawprint"))
                .previewDisplayName("pawprint")
        }
        .previewLayout(.sizeThatFits)
    }
}

And it works:

pawprint

Now let's try to add a second preview using the same SF Symbol but applying the .font view modifier to increase the size:

    static var previews: some View {
        Group {
            SomeView(image: Image.init(systemName: "pawprint"))
                .previewDisplayName("SomeView pawprint")

            // Error: Cannot convert value of type 'some View' to expected argument type 'Image'
            SomeView(image: Image.init(systemName: "pawprint").font(.system(size: 64)))
                .previewDisplayName("SomeView bigPawprint")
        }
        .previewLayout(.sizeThatFits)
    }

Yep, that's about right, because the view modifier .font returns the opaque type some View. But if we apply the "Fix-it button" suggestion to force-cast (sure, let's do that, for science), then it compiles, but crashes the previewer. Also crashes if we try to run in a simulator, so it's not just a Previewer bug.

And yet, there's no problem at all just displaying it as some View:

        Group {
            SomeView(image: Image.init(systemName: "pawprint"))
                .previewDisplayName("SomeView pawprint")

            // Crashes
            // SomeView(image: Image.init(systemName: "pawprint").font(.system(size: 64)) as! Image)
                .previewDisplayName("SomeView big pawprint")

            // Works
            Image.init(systemName: "pawprint").font(.system(size: 64))
                .previewDisplayName("View Modifier big pawprint")
        }
        .previewLayout(.sizeThatFits)

View modifier big pawprint

So, how can I do something like this on a View where I apply a view modifier, but I can still use the original View type?

CodePudding user response:

No. And because most of the view modifier types are unknown to us (such as whatever backs the font modifier), you'll have to emulate demo

Tested with Xcode 13.2 / iOS 15.2

Alternate 1: If it is only about image mocking and SomeView is restricted to Image only, then it is possible to use configured UIImage, like

SomeView(image: Image(uiImage: UIImage(systemName: "pawprint", withConfiguration: UIImage.SymbolConfiguration(pointSize: 64))!))
    .previewDisplayName("SomeView bigPawprint")

Alternate 2: Use generics, like

struct SomeView<V: View>: View {
    let image: V

    var body: some View {
        image
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            SomeView(image: Image.init(systemName: "pawprint"))
                .previewDisplayName("SomeView pawprint")

            // this works
            SomeView(image: Image.init(systemName: "pawprint").font(.system(size: 64)))
                .previewDisplayName("SomeView bigPawprint")
        }
        .previewLayout(.sizeThatFits)
    }
}
  • Related