I'd like to reuse some computed local values inside a GeometryReader that depend on the reader, e.g.
struct ContentView: View {
let verticalPaddingFraction: CGFloat = 0.05
let horizontalPaddingFraction: CGFloat = 0.05
var body: some View {
GeometryReader { reader in
// want to define corners here...
Path { p in
let bottomLeadingCorner = CGPoint(
x: reader.size.width * self.horizontalPaddingFraction,
y: reader.size.height * (1 - self.verticalPaddingFraction))
let bottomTrailingCorner = CGPoint(
x: reader.size.width * (1 - self.horizontalPaddingFraction),
y: reader.size.height * (1 - self.verticalPaddingFraction))
p.move(to: bottomLeadingCorner)
p.addLine(to: bottomTrailingCorner)
}
.stroke(Color.black, lineWidth: 5.0)
// now, would like a new Path with access to bottomLeadingCorner, etc.
}
}
}
I define corners inside the Path closure - how can I make them visible to another Path? Of course, if all I cared about was drawing from corner to corner I could just draw inside a rectangle. I'm interested in the general principle and chose corners here to make the example as simple and short as possible.
struct ContentView: View {
let verticalPaddingFraction: CGFloat = 0.05
let horizontalPaddingFraction: CGFloat = 0.05
var body: some View {
GeometryReader { reader in
let bottomLeadingCorner = CGPoint(
x: reader.size.width * self.horizontalPaddingFraction,
y: reader.size.height * (1 - self.verticalPaddingFraction))
let bottomTrailingCorner = CGPoint(
x: reader.size.width * (1 - self.horizontalPaddingFraction),
y: reader.size.height * (1 - self.verticalPaddingFraction))
Path { p in
p.move(to: bottomLeadingCorner)
p.addLine(to: bottomTrailingCorner)
}
.stroke(Color.black, lineWidth: 5.0)
// now, would like a new Path with access to bottomLeadingCorner, etc.
}
}
}
Thanks for taking the time to offer a suggestion. Unfortunately, that code does not compile - you get the error messages: Closure containing a declaration cannot be used with function builder 'ViewBuilder'.
I've been Googling around with that error message, but people seem to mostly hit it when trying to declare a local variable in body, with the solution being to add a return statement to the "view code" (basically). But there is no analogous trick to pull inside a GeometryReader.
You can use the same solution, with the caveat that the compiler is too dumb to realize what type it's returning, so you have to write it explicitly (<AnyView> in my example)
struct ContentView: View {
let verticalPaddingFraction: CGFloat = 0.05
let horizontalPaddingFraction: CGFloat = 0.05
var body: some View {
GeometryReader<AnyView> { reader in
let bottomLeadingCorner = CGPoint(
x: reader.size.width * self.horizontalPaddingFraction,
y: reader.size.height * (1 - self.verticalPaddingFraction))
let bottomTrailingCorner = CGPoint(
x: reader.size.width * (1 - self.horizontalPaddingFraction),
y: reader.size.height * (1 - self.verticalPaddingFraction))
let topLeadingCorner = CGPoint(
x: reader.size.width * self.horizontalPaddingFraction,
y: reader.size.height * self.verticalPaddingFraction)
return AnyView(
Group {
Path { p in
p.move(to: bottomLeadingCorner)
p.addLine(to: bottomTrailingCorner)
}.stroke(Color.black, lineWidth: 5.0)
Path { p in
p.move(to: bottomLeadingCorner)
p.addLine(to: topLeadingCorner)
}.stroke(Color.black, lineWidth: 5.0)
Path { p in
p.move(to: topLeadingCorner)
p.addLine(to: bottomTrailingCorner)
}.stroke(Color.black, lineWidth: 5.0)
}
)
}
}
}
Ah, maybe I should've used "not smart enough" instead of "too dumb" :) swift compiler often says "I'm not sure what you mean, could you clarify yourself?" and I like it (compared to alternatives)
Or, if you want to have compiler still be relatively smarter than you (or at least, than me), and not to rely on type erasure, which should be avoided for a lot of reasons. How about make it a function:
var body: some View {
GeometryReader(content: geometricView(with:))
}
func geometricView(with geometry: GeometryProxy) -> some View {
let buttomLeadingCorner = ...
let bottomTrailing = ...
return Group {
...
}
}
Refactoring like this also helps a lot with compilation.
If I could ask a follow up on this, would it be best practice to use a ZStack for multiple calls like this?, e.g.,
var body: some View {
ZStack {
GeometryReader(content: drawXYAxis(with:))
GeometryReader(content: drawXAxisTicks(with:))
GeometryReader(content: drawYAxisTicks(with:))
}
}
That appears to basically work, but I don't know if that is recommended.
Also, how would I pass data to one of these GeometryReaders? Looking at Apple Developer Documentation it looks like the content: argument takes a closure like (GeometryProxy) -> Content, but how do I sneak data in there too? Do I need to use the environment or some observable object business?
I did solve breaking down. I got expression too complex...
I DO precalc in a previous nested loop.
struct MyGridView : View {
var room: Room
let MaxRows = 2
let MaxCols = 2
var body: some View {
**// precalc, as GeometryReader calls its body loop twice**
let si = FirstLevelQuestionsManager.shared
var firstLevelQuestions = FirstLevelQuestions()
var troubleShootings = TroubleShootings()
let devids = room.devids
for row in 0..<self.MaxRows {
for col in 0..<self.MaxCols {
let flq = si.titleAndTextAt(row: row, col: col, ncols: self.MaxCols)
firstLevelQuestions.append(flq)
}
}
let gr = GeometryReader { (geometry : GeometryProxy) in
VStack() {
ForEach(0..<self.MaxRows) { (row: Int) in
HStack {
ForEach(0..<self.MaxCols) { (col: Int) -> GridCellView in
let index = col + row * self.MaxCols
return GridCellView(
w: (geometry.size.width / CGFloat(self.MaxCols))
titleAndText: firstLevelQuestions[index],
room: self.room,
troubleShootings: nil)
}
}
}
}
} // GeometryReader
return gr
}