2

This modifier on a SwiftUI view compiles fine:

.background(GeometryReader { p in
    return Rectangle().opacity(0)
})

(I know I don't need the return, but I'm about to add another line.)

When I add a print call, it no longer compiles.

.background(GeometryReader { p in
    print("hi")
    return Rectangle().opacity(0)
})

The error points to background and says:

Expression type '(_, Alignment) -> some View' is ambiguous without more context

I don't understand why it's confused now about the type of thing passed to .background(...). It's got the same clear return expression as before. How can I fix the code to satisfy the type checker?

Rob N
  • 15,024
  • 17
  • 92
  • 165

2 Answers2

3

This error occurs because multi-statement closures don't take part in type inference. The Content generic parameter of the initialiser of GeometryReader cannot be inferred because the only information you provide to it, the closure, is not considered for type-inference!

So you just need to specify what type you are returning:

.background(GeometryReader {
    p -> Rectangle in // note here
    print("Hello")
    return Rectangle()
})

You probably shouldn't do this though, because you are supposed to only put Views in a view builder closure. SwiftUI is designed to be very declarative, almost its own DSL, I would say.

Edit: I found this answer, which adds a View called Print. I guess you can put one of these alongside whatever view you are setting the background of, in a Group.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Sorry, I edited my question just as you answered. Your answer worked, but now that I added the `.opacity` modifier, I don't know which type to use. Writing `some View` in place of `Rectangle` did not cut it. – Rob N Jan 31 '20 at 17:48
  • @RobN This is why you shouldn't write other statements view builders. You're supposed to just write `View`s, and only `View`s in a view builder closure. You can have `if...else` statements, but that's about all the control you have. As you probably found out already, you practically can't declare variables, or do a for/while loop in a view builder, same goes for `print`. – Sweeper Jan 31 '20 at 17:53
  • Okay, I can adapt that `Print` view to my needs. The original design proposal for function builders, linked off the Swift forum (https://forums.swift.org/t/function-builders/25167) had more things allowed, like local variables. So I think some of these restrictions are just that it's currently all half implemented. – Rob N Jan 31 '20 at 18:08
  • 1
    Well, it's not that you physically can't write variable declarations. I said "practically can't", because whenever you do, the closure gets ignored in type inference. The type might still be abled to be inferred by other means, but because of the contexts in which view builders are usually used (opaque return type), type inference almost always fails. – Sweeper Jan 31 '20 at 18:17
1

Writing my own modifier I was able to create a background for views. Sweeper I think has the right idea, you'll need to add a return statement because geometry reader is no longer a single line implicit return.

extension View {
  func myBackground() -> some View {
    print("hi")
    return background(GeometryReader { geometry in
      Rectangle().path(in: geometry.frame(in: .local))
    })
  }
}

Then I tested the modifier in the context below.

struct ContentView: View {

  var body: some View {
    Text("Foreground Label").foregroundColor(.green).myBackground()
  }

}
jnblanchard
  • 1,182
  • 12
  • 12