我创建了一个简单的自定义
MenuButton
。点击时,该按钮会在其上方显示一些菜单项。虽然这工作正常,但我想为整个屏幕添加一个覆盖视图,它会变暗并遮挡底层视图。点击覆盖层应该会关闭菜单。
这会产生两个问题:
没有覆盖层,
MenuButton
有一些自定义尺寸,例如50x50 并且可以正常放置在其周围视图层次结构中。添加覆盖层时,MenuButton
与屏幕一样大,因此无法再正确放置。
当菜单处于活动状态时显示叠加层时,它只能显示在视图层次结构中自行消失的视图。
有一个干净的解决方案来解决这个问题吗?
struct MenuButton: View {
@State private var isExpanded = false
let buttons: [String]
let buttonSize: CGFloat = 60
let itemButtonSize: CGFloat = 50
var body: some View {
ZStack {
// Overlay
/*Color.black.opacity(0.2)
.edgesIgnoringSafeArea(.all)
.opacity(isExpanded ? 1 : 0)
.onTapGesture {
isExpanded = false
}*/
ForEach(buttons.indices, id: \.self) { index in
VStack {
Image(systemName: buttons[index])
.frame(width: itemButtonSize, height: itemButtonSize)
.background(Color(.systemGray6))
.clipShape(Circle())
}
.offset(
x: 0,
y: isExpanded ? Double(index+1) * (-itemButtonSize - 20) : Double(index+1) * (-itemButtonSize + 20)
)
.opacity(isExpanded ? 1 : 0)
.animation(isExpanded ? .spring(response: 0.2, dampingFraction: 0.5, blendDuration: 0).delay(Double(index) * 0.05) : .easeOut(duration: 0.2), value: isExpanded)
}
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Image(systemName: isExpanded ? "xmark" : "plus")
.frame(width: buttonSize, height: buttonSize)
.foregroundColor(.gray)
.background(Color(.systemGray6))
.clipShape(Circle())
}
}
}
}
struct MenuView: View {
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
MenuButton(buttons: ["circle", "star", "bell"])
.padding()
}
Text("Bottom")
}
}
}
#Preview {
MenuView()
}
fullScreenCover(isPresented:onDismiss:content:)
显示菜单。您可能想要使用的一些其他技术如下:
.presentationBackground(Color.clear)
以隐藏默认背景。disablesAnimations = true
来禁用此功能。isShowing
并使用它来触发动画。在 .onAppear
中将标志设置为 true。isShowing
设置为 false 来隐藏菜单,并带有动画。动画完成后,通过将 isExpanded
设置为 false 来隐藏封面。这是显示其工作原理的更新示例:
struct MenuButton: View {
@State private var isExpanded = false
@State private var isShowing = false
let buttons: [String]
let buttonSize: CGFloat = 60
let itemButtonSize: CGFloat = 50
var body: some View {
Button {
var trans = Transaction()
trans.disablesAnimations = true
withTransaction(trans) {
isExpanded.toggle()
}
} label: {
Image(systemName: isExpanded ? "xmark" : "plus")
.frame(width: buttonSize, height: buttonSize)
.foregroundColor(.gray)
.background(Color(.systemGray6))
.clipShape(Circle())
}
.fullScreenCover(isPresented: $isExpanded) {
ZStack {
Color.black
.opacity(0.2)
.edgesIgnoringSafeArea(.all)
.opacity(isShowing ? 1 : 0)
.onTapGesture {
withAnimation {
isShowing = false
} completion: {
isExpanded = false
}
}
ForEach(buttons.indices, id: \.self) { index in
VStack {
Image(systemName: buttons[index])
.frame(width: itemButtonSize, height: itemButtonSize)
.background(Color(.systemGray6))
.clipShape(Circle())
}
.offset(
x: 0,
y: isShowing ? Double(index+1) * (-itemButtonSize - 20) : Double(index+1) * (-itemButtonSize + 20)
)
.opacity(isShowing ? 1 : 0)
.animation(isShowing ? .spring(response: 0.2, dampingFraction: 0.5, blendDuration: 0).delay(Double(index) * 0.05) : .easeOut(duration: 0.2), value: isShowing)
}
}
.presentationBackground(Color.clear)
.onAppear {
withAnimation {
isShowing = true
}
}
.onDisappear { isShowing = false }
}
}
}