You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI实现带动画的L形自定义菜单及导航栏适配问题问询

SwiftUI实现带动画的L形自定义菜单及导航栏适配问题问询

大家好!我觉得苹果系统自带的菜单和运动应用都挺酷的,但我想做个更有新意的自定义菜单,目前已经写出了一段代码,实现了一个带展开动画的下拉菜单效果:

完整代码

import SwiftUI

struct ContentView: View {
    
    @State private var isExpanded = false
    @State private var pillWidth: CGFloat = 0
    
    let height: CGFloat = 40

    var body: some View {

        ZStack(alignment: .topLeading) {

            VStack(alignment: .leading, spacing: 3) {

                // PILL
                HStack(spacing: 3) {

                    Button {
                        withAnimation(.spring(response: 0.35, dampingFraction: 0.85)) {
                            isExpanded.toggle()
                        }
                    } label: {

                        HStack(spacing: 3) {
                            Image(systemName: "person.crop.square.on.square.angled.fill")
                            Text("1 Sign")
                                .fontWeight(.regular)
                        }
                        .padding(.horizontal, 16)
                        .frame(height: height)
                        .background(
                            Color.gray.opacity(0.2)
                                .clipShape(
                                    CustomCorners(
                                        tl: height/2,
                                        tr: 2,
                                        bl: height/2,
                                        br: 2
                                    )
                                )
                        )
                    }

                    Image(systemName: "chevron.right")
                        .rotationEffect(.degrees(isExpanded ? 90 : 0))
                        .animation(.easeInOut(duration: 0.2), value: isExpanded)
                        .foregroundColor(.gray)
                        .frame(width: height, height: height)
                        .background(
                            Color.gray.opacity(0.2)
                                .clipShape(
                                    CustomCorners(
                                        tl: 2,
                                        tr: height/2,
                                        bl: 2,
                                        br: height/2
                                    )
                                )
                        )
                }
                .background(
                    GeometryReader { geo in
                        Color.clear
                            .onAppear {
                                pillWidth = geo.size.width
                            }
                    }
                )

                // DROPDOWN
                if isExpanded {

                    VStack(alignment: .leading, spacing: 15) {

                        row("2 Sign")
                        row("3 Sign")
                        row("4 Sign")

                    }
                    .padding(.vertical, 17)
                    .frame(width: pillWidth)
                    .background(
                        RoundedRectangle(cornerRadius: 28)
                            .fill(Color.gray.opacity(0.2))
                    )
                    .scaleEffect(y: isExpanded ? 1 : 0, anchor: .top)
                    .opacity(isExpanded ? 1 : 0)
                    .offset(y: isExpanded ? 0 : -10)
                    .animation(.spring(response: 0.35, dampingFraction: 0.85), value: isExpanded)
                }
            }
        }
        .padding(40)
    }
}

func row(_ title: String) -> some View {
    HStack {
        Text(title)
        Spacer()
        Image(systemName: "chevron.right")
    }
    .padding(.horizontal, 16)
    .frame(height: 28)
}


struct CustomCorners: Shape {
    var tl: CGFloat = 0
    var tr: CGFloat = 0
    var bl: CGFloat = 0
    var br: CGFloat = 0

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath()
        
        path.move(to: CGPoint(x: rect.minX + tl, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.maxX - tr, y: rect.minY))
        path.addArc(withCenter: CGPoint(x: rect.maxX - tr, y: rect.minY + tr),
                    radius: tr, startAngle: .pi * 1.5, endAngle: 0, clockwise: true)

        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - br))
        path.addArc(withCenter: CGPoint(x: rect.maxX - br, y: rect.maxY - br),
                    radius: br, startAngle: 0, endAngle: .pi * 0.5, clockwise: true)

        path.addLine(to: CGPoint(x: rect.minX + bl, y: rect.maxY))
        path.addArc(withCenter: CGPoint(x: rect.minX + bl, y: rect.maxY - bl),
                    radius: bl, startAngle: .pi * 0.5, endAngle: .pi, clockwise: true)

        path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + tl))
        path.addArc(withCenter: CGPoint(x: rect.minX + tl, y: rect.minY + tl),
                    radius: tl, startAngle: .pi, endAngle: .pi * 1.5, clockwise: true)

        return Path(path.cgPath)
    }
}


#Preview {
    ContentView()
}

当前实现效果

当前菜单展开效果

我现在想为自己的应用SignDict实现一个L形的带动画菜单,参考样式如下:
L形菜单样式参考

另外还有个小问题:我尝试把这个自定义菜单视图添加到导航栏的按钮项中,但出现了不少bug,有没有大佬能给点实现思路或者适配建议呀?

(抱歉我的英语语法不太好,希望大家能理解我的需求 😊)

备注:内容来源于stack exchange,提问作者Antonio A. Chavez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 18:23:14