SwiftUI 实现侧滑菜单 Side Menu

2,493 阅读2分钟

SwiftUI 实现侧滑菜单 Side Menu

效果

iShot2021-09-08 09.43.45.gif

代码

代码里都有相关注释

源码 github 链接:gist.github.com/RandyWei/05…


//
//  ContentView.swift
//  SiderMenuDemo01
//
//  Created by RandyWei on 2021/9/7.
//

import SwiftUI

struct ContentView: View {
   
   //划动偏移量
   @GestureState var offset:CGFloat = 0
   
   //滑动应该停留在某个点
   //停留点: 屏幕宽度的3/5
   let maxOffset:CGFloat = UIScreen.main.bounds.width * 3 / 5
   
   //滑动展开之后的 offset
   @State var expandOffset:CGFloat = 0
   
   //回弹点:最大停留点/2
   private var springOffset:CGFloat{
       maxOffset / 2
   }
   //缩放比例,默认是1
   @State private var scaleRatio:CGFloat = 1
   
   //最小 可缩放值
   let minScale:CGFloat = 0.9
   
   
   private var dragGesture: some Gesture {
       DragGesture()
           .updating($offset, body: { value, out, _ in
               //判断是否反向滑动,如果是展开状态需要反向滑动
               if value.translation.width >= 0 || expandOffset != 0 {
                   out = value.translation.width
               }
           })
           .onChanged { value in
               //为了顺畅给缩放增加过渡
               if value.translation.width >= 0 {
                   //对缩放比例进行计算:缩放值 = 划动比例 * 可缩放值(1-minScale)
                   //因为是往小了缩,所以是1-缩放值
                   scaleRatio = 1 - (value.translation.width / maxOffset) * (1 - minScale)
               } else {
                   //反向value.translation.width是负数 ,所以+maxOffset变为正值
                   scaleRatio = 1 - ((maxOffset + value.translation.width) / maxOffset) * (1 - minScale)
               }
           }
           .onEnded { value in
               //需要判断滑动是否超过某个点来决定是重置还是停留
               if value.translation.width >= springOffset {
                   expandOffset = maxOffset
                   //停止后,缩小 到0.9
                   scaleRatio = minScale
               } else {
                   expandOffset = 0
                   scaleRatio = 1
               }
           }
   }
   
   var body: some View {
       
       ZStack{
           
           //侧边菜单层
           SideMenuView()
           
           //功能区域
           FeatureView()
               .offset(x: offset + expandOffset)
               .scaleEffect(scaleRatio)
               .animation(.easeInOut(duration: 0.05))
               .gesture(dragGesture)
               
           
       }
       
   }
}

struct FeatureView:View {
   
   var body: some View{
       
       GeometryReader{proxy in
           VStack{
               HStack{
                   Image(systemName: "list.dash")
                       .resizable()
                       .frame(width: 20, height: 20, alignment: .center)
                   
                   Text("功能区域")
                       .font(.title)
                   
                   Spacer()
               }
               
               ScrollView(.vertical, showsIndicators: false, content: {
                   
                   VStack{
                       
                       ForEach(0..<50){_ in
                           
                           HStack{
                               
                               Image(systemName: "person")
                                   .resizable()
                                   .frame(width: 80, height: 80, alignment: .center)
                               
                               VStack(alignment: .leading){
                                   Text("titletitletitletitletitle")
                                       .font(.title)
                                   
                                   Spacer()
                                   
                                   Text("bodybodybodybodybodybody")
                                       .font(.body)
                               }
                               
                           }
                           
                       }.redacted(reason: .placeholder)
                   }
                   
               })
           }
           .padding(.horizontal)
           .padding(.top, 8 + proxy.safeAreaInsets.top)
           .frame(maxWidth:.infinity,maxHeight: .infinity,alignment: .topLeading)
           .background(Color.white)
           .cornerRadius(30)
           .shadow(radius: 10)
           .ignoresSafeArea()
       }
       
   }
}

struct SideMenuView:View {
   var body: some View{
       
       GeometryReader{proxy in
           VStack(alignment:.leading){
               //祖传头像
               Image("avatar")
                   .resizable()
                   .aspectRatio(contentMode: .fill)
                   .frame(width: 100, height: 100, alignment: .center)
                   .clipShape(Circle())
               
               Text("韦爵爷")
                   .font(.title)
               
               Text("这个人很懒,什么都没留下")
               
               //菜单
               
               HStack{
                   Image(systemName: "archivebox")
                   Text("菜单一")
               }
               .padding(.top)
               
               HStack{
                   Image(systemName: "note.text")
                   Text("菜单二")
               }
               .padding(.top)
               
               
               HStack{
                   Image(systemName: "gearshape")
                   Text("个人设置")
               }
               .padding(.top)
               
               Spacer()
               
               HStack{
                   Image(systemName: "signature")
                   Text("退出登录")
               }
               .padding(.top)
               
           }
           .foregroundColor(.white)
           .padding(.horizontal)
           .padding(.top, 8 + proxy.safeAreaInsets.top)
           .padding(.bottom, 8 + proxy.safeAreaInsets.bottom)
           .frame(maxWidth:.infinity,maxHeight: .infinity,alignment: .topLeading)
           .background(Color.orange)
           .ignoresSafeArea()
       }
       
   }
}

struct ContentView_Previews: PreviewProvider {
   static var previews: some View {
       ContentView()
   }
}

相关视频

Swift UI侧滑菜单Side Menu-哔哩哔哩