發佈&選擇發佈,使用SwiftUI搭建一個新建發佈彈窗(下)
theme: smartblue
攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第24天,點擊查看活動詳情。
承接上一章節的內容,在上一章節中,我們完成了“新建發佈”的入口和背景蒙層的搭建,那麼本章來進入重點,我們來完成彈窗的交互。
樣式預覽
彈窗視圖
我們來分析下“新建發佈”彈窗的內容,它包括一個提示的下拉條,一排橫向佈局的發佈功能按鈕,一個關閉彈窗的按鈕。
我們構建一個新的視圖,示例:
``` // 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
視圖中組合樣式視圖內容,示例:
``` // 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) } } ```
交互動畫
彈出關閉
完成了彈窗視圖後,我們回到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
變量關閉彈窗。
項目預覽
完成全部後,我們整體預覽下效果。
恭喜你,完成了本章的全部內容!
快來動手試試吧。
如果本專欄對你有幫助,不妨點贊、評論、關注~
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(一)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(八)
- 實戰教程·什麼年代了還在敲傳統木魚?(二)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(七)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(六)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(五)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(四)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(三)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(二)
- 實戰教程·什麼年代了還在敲傳統木魚?(一)
- 技術下午茶:產品經理是如何工作的?如何才算一份好的需求文檔?如何設計一個簡單的列表,它應該具備哪些基本功能?
- 發佈&選擇發佈,使用SwiftUI搭建一個新建發佈彈窗(上)
- 發佈&選擇發佈,使用SwiftUI搭建一個新建發佈彈窗(下)
- 使用SwiftUI搭建一個風箏搖擺動畫,實現放風箏的夢想~
- SwiftUI100天:使用SwiftUI搭建一個計時器App
- 實戰編程·使用SwiftUI從0到1完成一款iOS筆記App(三)
- 初識MVVM·關於啟動頁、引導頁、登錄頁的設計細節和交互邏輯
- 誰説程序員不懂浪漫,教你使用SwiftUI搭建一個電子相冊送給她吧~
- 實戰編程·刻在男人DNA裏的浪漫,空氣投籃(二)
- 實戰編程·使用SwiftUI從0到1完成一款iOS筆記App(四)