I'm trying to create a tutorial framework in SwiftUI that finds a specific view and highlights it by darkening the rest of the screen.
For example: Say I have three circles...
And I want to highlight the blue one...
Here's what I have come up with so far.
- Create a ZStack.
- Place semi-transparent black background on top.
- Add inverted mask to background to punch hole in it to reveal blue circle.
This works, but I need the size and location of the blue circle, in order to know where to place the mask.
In order to achieve this, I have to write some hacky code with GeometryReader
. Ie: Create a geometry reader inside of the blue circle's overlay modifier and return a clear background. This allows me to retrieve the dynamic size and location of the view. If I just wrapped the blue circle in a normal GeometryReader
statement it would remove the dynamic size and position of the view.
Lastly I store the frame
of the blue circle, and set the frame
and position
of the mask using it, thus achieving what I want, a cutout over the top of the blue circle in the dark overlay.
All this being said I'm getting a runtime error "Modifying state during view update, this will cause undefined behavior."
Also approach seems very hack and sticky. Ideally I'd like to create a separate framework where I could target a view, then add an overlay view with a specific shape cut out in order to highlight the specific view.
Here's the code from the above example:
@State var blueFrame: CGRect = .zero
var body: some View {
ZStack {
VStack {
Circle()
.fill(Color.red)
.frame(width: 100, height: 100)
ZStack {
Circle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.overlay {
GeometryReader { geometry -> Color in
let geoFrame = geometry.frame(in: .global)
blueFrame = CGRect(x: geoFrame.origin.x + (geoFrame.width / 2),
y: geoFrame.origin.y + (geoFrame.height / 2),
width: geoFrame.width,
height: geoFrame.height)
return Color.clear
}
}
}
Circle()
.fill(Color.green)
.frame(width: 100, height: 100)
}
Color.black.opacity(0.75)
.edgesIgnoringSafeArea(.all)
.reverseMask {
Circle()
.frame(width: blueFrame.width + 10, height: blueFrame.height + 10)
.position(blueFrame.origin)
}
.ignoresSafeArea()
}
}