Jar*_*red 5 masking swiftui geometryreader
我正在尝试在 SwiftUI 中创建一个教程框架,它可以找到特定的视图并通过使屏幕的其余部分变暗来突出显示它。
这是我到目前为止所想到的。
这可行,但我需要蓝色圆圈的大小和位置,以便知道在哪里放置遮罩。
为了实现这一目标,我必须使用GeometryReader. 即:在蓝色圆圈的覆盖修改器内创建一个几何读取器并返回清晰的背景。这允许我检索视图的动态大小和位置。如果我只是将蓝色圆圈包裹在普通GeometryReader语句中,它将删除视图的动态大小和位置。
最后,我存储frame蓝色圆圈的 ,并使用它设置蒙版的frame和position,从而实现我想要的,在黑暗覆盖层中蓝色圆圈顶部的切口。
话虽如此,我收到了运行时错误“在视图更新期间修改状态,这将导致未定义的行为。”
而且方法似乎非常黑客和粘性。理想情况下,我想创建一个单独的框架,在其中可以定位视图,然后添加具有特定形状剪切的覆盖视图,以突出显示特定视图。
这是上面示例中的代码:
@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()
}
}
Run Code Online (Sandbox Code Playgroud)
我想你想要这样的东西:
实现此目的的一种方法是将matchedGeometryEffect聚光灯放在选定的灯光上,并使用blendMode和compositingGroup切割变暗覆盖层中的孔。
首先,让我们定义一个类型来跟踪选择的灯光:
enum Light: Hashable, CaseIterable {
case red
case yellow
case green
var color: Color {
switch self {
case .red: return .red
case .yellow: return .yellow
case .green: return .green
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以编写一个View绘制彩色灯光的程序。每个灯光都经过修改,matchedGeometryEffect使其框架可供聚光灯视图使用(稍后编写)。
struct LightsView: View {
let namespace: Namespace.ID
var body: some View {
VStack(spacing: 20) {
ForEach(Light.allCases, id: \.self) { light in
Circle()
.foregroundColor(light.color)
.matchedGeometryEffect(
id: light, in: namespace,
properties: .frame, anchor: .center,
isSource: true
)
}
}
.padding(20)
}
}
Run Code Online (Sandbox Code Playgroud)
这是聚光灯视图。它使用blendMode(.destinationOut)aCircle将圆从底层中切掉Color.black,并使用compositingGroup将混合包含在Circle和 中Color.black。
struct SpotlightView: View {
var spotlitLight: Light
var namespace: Namespace.ID
var body: some View {
ZStack {
Color.black
Circle()
.foregroundColor(.white)
.blur(radius: 4)
.padding(-10)
.matchedGeometryEffect(
id: spotlitLight, in: namespace,
properties: .frame, anchor: .center,
isSource: false
)
.blendMode(.destinationOut)
}
.compositingGroup()
}
}
Run Code Online (Sandbox Code Playgroud)
在 中HighlightingView,将 放在SpotlightView之上LightsView并为其设置动画SpotlightView:
struct HighlightingView: View {
var spotlitLight: Light
var isSpotlighting: Bool
@Namespace private var namespace
var body: some View {
ZStack {
LightsView(namespace: namespace)
SpotlightView(
spotlitLight: spotlitLight,
namespace: namespace
)
.opacity(isSpotlighting ? 0.5 : 0)
.animation(
.easeOut,
value: isSpotlighting ? spotlitLight : nil
)
}
}
}
Run Code Online (Sandbox Code Playgroud)
最后,ContentView跟踪选择状态并添加Picker:
struct ContentView: View {
@State var isSpotlighting = false
@State var spotlitLight: Light = .red
private var selection: Binding<Light?> {
Binding(
get: { isSpotlighting ? spotlitLight : nil },
set: {
if let light = $0 {
isSpotlighting = true
spotlitLight = light
} else {
isSpotlighting = false
}
}
)
}
var body: some View {
VStack {
HighlightingView(
spotlitLight: spotlitLight,
isSpotlighting: isSpotlighting
)
Picker("Light", selection: selection) {
Text("none").tag(Light?.none)
ForEach(Light.allCases, id: \.self) {
Text("\($0)" as String)
.tag(Optional($0))
}
}
.pickerStyle(.segmented)
}
.padding()
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1472 次 |
| 最近记录: |