發佈&選擇發佈,使用SwiftUI搭建一個新建發佈彈窗(下)

語言: CN / TW / HK

theme: smartblue

攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第24天,點擊查看活動詳情

承接上一章節的內容,在上一章節中,我們完成了“新建發佈”的入口背景蒙層的搭建,那麼本章來進入重點,我們來完成彈窗的交互。

樣式預覽

1.png

彈窗視圖

我們來分析下“新建發佈”彈窗的內容,它包括一個提示的下拉條,一排橫向佈局的發佈功能按鈕,一個關閉彈窗的按鈕。

我們構建一個新的視圖,示例:

``` // 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類型的變量imagetext,分別代表操作按鈕中的圖標圖片和操作按鈕名稱。

當我們點擊按鈕的時候,切換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視圖中組合樣式視圖內容,示例:

``` // MARK: 底部彈窗 struct SlideOutMenu: View {     @Binding var showMaskView: Bool

var body: some View {          VStack {

Spacer()

//構建彈窗視圖元素             VStack {                 // 下拉條                 pullDownBtnView()

Spacer()

// 操作按鈕                 HStack(spacing: 20) {                     operateBtnView(image: "magazine.fill", text: "寫文章")                     operateBtnView(image: "doc.plaintext.fill", text: "發沸點")                     operateBtnView(image: "book.fill", text: "提問題")                     operateBtnView(image: "paperplane.fill", text: "傳資源")                 }

Spacer()

// 關閉按鈕                 colseBtnView()             }             .padding()             .frame(maxWidth: .infinity, maxHeight: 320)             .background(Color.white)             .cornerRadius(10, antialiased: true)         }.edgesIgnoringSafeArea(.bottom)         } } ```

2.png

交互動畫

彈出關閉

完成了彈窗視圖後,我們回到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動畫加了一個從下向上展示的過渡動畫。

3.gif

向下拖動關閉

新建發佈彈窗除了常規的點擊關閉按鈕關閉彈窗外,點擊彈窗向下拖動時關閉彈窗,要實現這個功能,我們回到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變量關閉彈窗。

項目預覽

完成全部後,我們整體預覽下效果。

4.gif

恭喜你,完成了本章的全部內容!

快來動手試試吧。

如果本專欄對你有幫助,不妨點贊、評論、關注~

「其他文章」