自定义菜单视图:当菜单处于所有其他视图之上时,如何将菜单放置在视图层次结构之外?

问题描述 投票:0回答:1

我创建了一个简单的自定义

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()
}

enter image description here enter image description here

ios swiftui view-hierarchy
1个回答
0
投票

您可以尝试使用

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 }
        }
    }
}

Animation

© www.soinside.com 2019 - 2024. All rights reserved.