GeometryReader: how to fix: "Unable to infer complex closure return type; add explicit type to disambiguate"?

I was just trying to use GeometryReader in different places to see its effect. So looks like if there is more than one view inside, it cannot infer the result type.

import SwiftUI

struct ProgressBar: View {
    static let height: Length = 8.0
    @Binding var progress: CGFloat
    @State var isShowing = false
    
    var body: some View {
        ZStack(alignment: .leading) {
            // Unable to infer complex closure return type; add explicit type to disambiguate
            GeometryReader { geometry in
            //Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
//            GeometryReader { (geometry) -> View in
            // Consecutive statements on a line must be separated by ';'
//            GeometryReader { (geometry) -> some View in
                Rectangle()
                    .foregroundColor(.gray)
                    .opacity(0.3)
                    .frame(width: geometry.size.width, height: Self.height)
                    .frame(width: geometry.size.width, height: Self.height)
                Rectangle()
                    .foregroundColor(.blue)
                    .frame(width: self.isShowing ? geometry.size.width * self.progress / 100.0 : 0.0, height: Self.height)
                    .frame(width: self.isShowing ? geometry.size.width * self.progress / 100.0 : 0.0, height: Self.height)
                    .animation(.linear(duration: 0.6))
            }
        }
        .onAppear { self.isShowing = true }
        .cornerRadius(4.0)
    }
}

#if DEBUG
struct ProgressBar_Previews: PreviewProvider {
    static var previews: some View {
        ProgressBar(progress: .constant(25.0))
    }
}
#endif

See error messages inside the code at GeometryReader()...

Documentation: Apple Developer Documentation

@frozen struct GeometryReader<Content> where Content : View
init(content: @escaping (GeometryProxy) -> Content)

So I have to somehow tell the compiler that Content is a View here. But I cannot figure out how to do this :frowning:

1 Like

It looks like that parameter of GeometryReader.init is supposed to be marked with @ViewBuilder but isn't.

You should be able to work around that by wrapping the body of the closure in an unnecessary stack, like GeometryReader { geometry in VStack { ... } }.

1 Like

I have had problems with GeometryReader even when it returns just one View, if I also do something else before returning. Like

GeometryReader { proxy in 
    let x = blabla
    return VStack(spacing: x) { ... }
}

In those cases I solved it with putting both lines in a separate function, that potentially also take the proxy as argument.

That's due to a current limitation in Swift where the closure's return type is inferred only if its content is a single expressions.

3 Likes

Another workaround is to introduce this general helper function:

func bind<Value, Answer>(_ value: Value, in body: (Value) throws -> Answer) rethrows -> Answer {
    return body(value)
}

Then you can write this:

GeometryReader { proxy in
    bind(blabla) { x in
        VStack(spacing: x) {
            ...
        }
    }
}
2 Likes

If you place two rectangles in the VStack, the error will be fixed.

    var body: some View {
        ZStack(alignment: .leading) {
            GeometryReader { geometry in
                VStack {
                    Rectangle()
                        .foregroundColor(.gray)
                        .opacity(0.3)
                        .frame(width: geometry.size.width, height: Self.height)
                        .frame(width: geometry.size.width, height: Self.height)
                    Rectangle()
                        .foregroundColor(.blue)
                        .frame(width: self.isShowing ? geometry.size.width * self.progress / 100.0 : 0.0, height: Self.height)
                        .frame(width: self.isShowing ? geometry.size.width * self.progress / 100.0 : 0.0, height: Self.height)
                        .animation(.linear(duration: 0.6))
                }
            }
        }
        .onAppear { self.isShowing = true }
        .cornerRadius(4.0)
    }

The bug is fixed in Xcode 11 Beta 5

Hi Luca and welcome, glad to see SwiftUI developers joining to the SE forums. :tada: This hopefully willChange everything (pun intended).

1 Like