发布&选择发布,使用SwiftUI搭建一个新建发布弹窗(下)
样式预览
弹窗视图
我们来分析下“新建发布”弹窗的内容,它包括一个提示的下拉条,一排横向布局的发布功能按钮,一个关闭弹窗的按钮。
我们构建一个新的视图,示例:
// MARK: 底部弹窗 struct SlideOutMenu: View { @Binding var showMaskView: Bool var body: some View { VStack { Spacer() //构建弹窗视图元素 } } }
上述代码中,我们创建了一个新视图SlideOutMenu
。以及还使用@Binding
声明了一个变量,方便我们在ContentView
视图中做双向绑定。由于弹窗在底部,我们使用VStack
纵向布局,然后使用Spacer
将弹窗撑开到底部。
完成后,我们来完成弹窗的样式部分。
下拉条
我们一块一块内容完成它,首先是下拉条,示例:
// 下拉条 func pullDownBtnView() -> some View { Rectangle() .foregroundColor(Color(.systemGray4)) .cornerRadius(30) .frame(width: 50, height: 5) }
上述代码中,我们构建了一个Rectangle
矩形,并赋予了颜色systemGray4
灰色和圆角,尺寸我们使用规定的长宽。
发布功能按钮
发布功能按钮部分,由于具备相同的样式,我们可以使用结构体的方式构建基础样式,再在视图中调用,也可以使用创建视图的方法构建基础样式再调用。两种方法都可以使用,示例:
// 操作功能 func operateBtnView(image: String, text: String) -> some View { Button(action: { self.showMaskView = false }) { VStack(spacing: 15) { Image(systemName: image) .font(.system(size: 30)) .foregroundColor(.black) .frame(width: 80, height: 80) .background(Color(.systemGray6)) .cornerRadius(8) Text(text) .font(.system(size: 17)) .foregroundColor(.black) } } }
上述代码中,我们构建了一个框架视图operateBtnView
,传入两个String
类型的变量image
、text
,分别代表操作按钮中的图标图片和操作按钮名称。
当我们点击按钮的时候,切换showMaskView
状态关闭蒙层。
关闭按钮
关闭按钮样式也比较简单,我们依旧单独构建样式部分,示例:
// 关闭按钮 func colseBtnView() -> some View { Button(action: { self.showMaskView = false }) { Image(systemName: "xmark") .font(.system(size: 24)) .foregroundColor(.gray) .padding(.bottom, 20) } }
上述代码中,我们单独构建按样式视图colseBtnView
。当我们点击关闭按钮的时候,也调用切换showMaskView
状态关闭蒙层。
样式组合
完成上述3个视图后,我们在SlideOutMenu
视图中组合样式视图内容,示例:
交互动画
弹出关闭
完成了弹窗视图后,我们回到ContentView
视图中,将弹窗视图附上,示例:
var body: some View { ZStack { VStack { topBarMenu() Spacer() } if showMaskView { MaskView(showMaskView: $showMaskView) SlideOutMenu(showMaskView: $showMaskView) .transition(.move(edge: .bottom)) .animation(.interpolatingSpring(stiffness: 200.0, damping: 25.0, initialVelocity: 10.0)) } } }
上述代码中,我们根据showMaskView
变量状态决定是否展示背景蒙层视图和弹窗视图,然后在展示SlideOutMenu
新建发布弹窗时,使用transition
过渡和animation
动画加了一个从下向上展示的过渡动画。
向下拖动关闭
新建发布弹窗除了常规的点击关闭按钮关闭弹窗外,点击弹窗向下拖动时关闭弹窗,要实现这个功能,我们回到SlideOutMenu
弹窗视图中,首要声明2个变量,示例:
@State private var offsetY = CGSize.zero @State var isAllowToDrag: Bool = false
上述代码中,offsetY
变量存储拖动时弹窗Y轴的位置,用来判断用户在向上拖动还是向下拖动,也为了确定向下拖动Y轴到某一位置的,触发关闭弹窗交互。
变量isAllowToDrag
是承接offsetY
变量,当我们判断向上拖动时,禁用弹窗拖动,防止弹窗向上拖动,保证只能向下拖动。
然后在SlideOutMenu
主要内容中使用拖动修饰符,示例:
// MARK: 底部弹窗 struct SlideOutMenu: View { @Binding var showMaskView: Bool var body: some View { VStack { //隐藏了弹窗视图代码 } .padding() .frame(maxWidth: .infinity, maxHeight: 320) .background(Color.white) .cornerRadius(10, antialiased: true) .offset(y: isAllowToDrag ? offsetY.height : 0) .gesture( DragGesture() .onChanged { gesture in // 如果向下拖动 if gesture.translation.height > 0 { self.isAllowToDrag = true self.offsetY = gesture.translation } } .onEnded { _ in // 如果拖动位置大于100 if (self.offsetY.height) > 100 { self.showMaskView = false } else { self.offsetY = .zero } } ) }.edgesIgnoringSafeArea(.bottom) } }
上述代码中,我们给弹窗内容加了offset
偏移量修饰符,拖动时,如果isAllowToDrag
允许拖动,则拖动位置为offsetY.height
偏移的Y轴位置,否则就是0。
然后使用gesture
手势修饰符,使用DragGesture
拖动手势,当onChanged
拖动改变时,先判断是不是向下拖动,如果是则启用isAllowToDrag
变量,然后拖动后让视图回到原来的位置。
当弹窗视图onEnded
拖动结束时,判断拖动的Y轴的位置offsetY.height
是不是大于100
,也就是弹窗宽度的大约1/3
的位置,如果时则修改showMaskView
变量关闭弹窗。
项目预览
完成全部后,我们整体预览下效果。
恭喜你,完成了本章的全部内容!
快来动手试试吧。