I'm trying to create a custom view that uses a @ViewBuilder for it's content, but then has specialized initializers like the one for Label(_:systemImage:) but I seem to be running into an issue.
If I leave the line that I have commented out, out, then it builds fine but If I comment back in the line .imageScale(.large) I get this error:
Cannot convert return expression of type 'some View' to return type 'Image'
I feel like I'm just not understanding why this error is happening. Is it not possible to make an initializer where I can customize the returned image?
Why does using my IconView work in this way:
struct ContentView: View {
var body: some View {
IconView() {
Image(systemName: "swift")
.imageScale(.large) // ← why does this work here?
}
}
}
Or just drop the where Content == Image. SwiftUI modifiers are designed so that you can apply them to wrapper views and they’ll apply to all the relevant inner views. That’s why you can do Image(systemName: "arrow.right").border(Color.red).imageScale(.large), even though there’s a border view wrapped around the Image.
Or just drop the where Content == Image . SwiftUI modifiers are designed so that you can apply them to wrapper views and they’ll apply to all the relevant inner views.
Originally I didn't have the where clause but I couldn't get any combination of what I was trying to do to work.I then I looked into Label since it had a similar initializer to what I was trying to do. The header has it defined this way:
extension Label where Title == Text, Icon == Image {
/// Creates a label with a system icon image and a title generated from a
/// localized string.
///
/// - Parameters:
/// - titleKey: A title generated from a localized string.
/// - systemImage: The name of the image resource to lookup.
public init(_ titleKey: LocalizedStringKey, systemImage name: String)
Which made think that that was the direction I need to go down, because if I leave out the where then even returning just an Image with no modifiers doesn't work:
Because the imageScale modifier returns some View, you cannot write a constraint matching its return type. So you must avoid the need for its return type. Try introducing a wrapper view type that applies the modifier. Then you can use the wrapper view type in your constraint.
struct _IconView_SystemImageContent: View {
let name: String
var body: some View {
Image(systemName: name)
.imageScale(.large)
}
}
extension IconView where Content == _IconView_SystemImageContent {
init(systemName: String) {
self.init {
_IconView_SystemImageContent(name: systemName)
}
}
}
Huh. I would also have expected this to work, but I can see why it might not. This is probably for the best, because otherwise the compiler would be invisibly making an ABI promise that this initializer will always produce an IconView<Image>. You clearly want the flexibility to produce something else, namely an Image wrapped in whatever modifier view .imageScale() returns.
Can you do extension IconView where Content == some View?
Edit: nope:
error: repl.swift:10:37: error: 'some' types are only permitted in properties, subscripts, and functions
extension IconView where Content == some View {
Maybe one of the experts (@hborla, @Douglas_Gregor, @Slava_Pestov) know the right spelling to convince the type system that your initializer produces an ImageView<some View>.
import SwiftUI
let l = Label("Hello", systemImage: "arrow.right")
print(type(of: l)) // Label<Text, Image>
The trick might be that this initializer can never modify its Image, and all modifiers have to be inherited from the Environment (including the inherited LabelStyle).