你总是可以做自己的卷饼.可采用下列技术:
- 将弹出框显示为
ZStack
中的顶层.
- 使用
.matchedGeometryEffect
定位.
下面是一个例子来说明它是如何工作的:
struct ContentView: View {
enum PopoverTarget {
case text1
case text2
case text3
}
@State private var popoverTarget: PopoverTarget?
@Namespace private var nsPopover
@ViewBuilder
private var customPopover: some View {
if let popoverTarget {
Text("Popover for \(popoverTarget)")
.padding()
.foregroundStyle(.gray)
.background {
RoundedRectangle(cornerRadius: 10)
.fill(Color(white: 0.95))
.shadow(radius: 6)
}
.matchedGeometryEffect(
id: popoverTarget,
in: nsPopover,
properties: .position,
isSource: false
)
}
}
private func popoverPlaceholder(
target: PopoverTarget,
xOffset: CGFloat = 0,
yOffset: CGFloat = 0
) -> some View {
Color.clear
.matchedGeometryEffect(id: target, in: nsPopover)
.offset(x: xOffset, y: yOffset)
}
private func showPopover(target: PopoverTarget) {
if popoverTarget != nil {
withAnimation {
popoverTarget = nil
} completion: {
popoverTarget = target
}
} else {
popoverTarget = target
}
}
var body: some View {
ZStack {
VStack {
Text("Text 1")
.padding()
.background(.blue)
.onTapGesture { showPopover(target: .text1) }
.background { popoverPlaceholder(target: .text1, yOffset: 70) }
.padding(.top, 50)
.padding(.leading, 100)
.frame(maxWidth: .infinity, alignment: .leading)
Text("Text 2")
.padding()
.background(.orange)
.onTapGesture { showPopover(target: .text2) }
.background { popoverPlaceholder(target: .text2, xOffset: -40, yOffset: -70) }
.padding(.top, 100)
.padding(.trailing, 40)
.frame(maxWidth: .infinity, alignment: .trailing)
Spacer()
Text("Text 3")
.padding()
.background(.green)
.onTapGesture { showPopover(target: .text3) }
.background { popoverPlaceholder(target: .text3, yOffset: -70) }
.padding(.bottom, 250)
}
customPopover
.transition(
.opacity.combined(with: .scale)
.animation(.bouncy(duration: 0.25, extraBounce: 0.2))
)
}
.foregroundStyle(.white)
.contentShape(Rectangle())
.onTapGesture {
popoverTarget = nil
}
}
}