實戰程式設計·刻在男人DNA裡的浪漫,空氣投籃(二)

語言: CN / TW / HK

theme: smartblue

前提回顧

在上一章節中,我們完成了“準備遊戲”頁面和“遊戲列表”頁面,並完成了遊戲列表的簡單互動,在本章中,我們將繼續完成其他的相關內容。

實戰程式設計

頁面切換

在整個空氣投籃專案中,“準備遊戲”頁面和“遊戲列表”頁面的互動邏輯是,開啟App展示“準備遊戲”頁面,同時喚起Watch端的授權,授權通過後,進入到“遊戲列表”頁面。

當前Watch端先行忽略,我們先完成切換切換的邏輯。首先宣告一個變數儲存切換動作,如下程式碼所示:

@State var isAffirmInWatch: Bool = false

上述程式碼中,我們聲明瞭一個Bool型別的變數isAffirmInWatch,初始狀態位false。

當isAffirmInWatch是否授權狀態為true時,我們進入到gameListView遊戲列表頁面,若沒有授權,則停留在prepareView準備遊戲頁面。如下程式碼所示:

if isAffirmInWatch { gameListView() } else { prepareView() .onTapGesture { self.isAffirmInWatch = true } }

上述程式碼中,為了演示方便,我們給prepareView準備遊戲檢視加了一個onTapGesture點選事件,當點選prepareView準備遊戲檢視時,切換isAffirmInWatch是否授權狀態為true,如此在點選時便可進入到gameListView遊戲列表檢視。

遊戲回合檢視

接下來,當用戶點選遊戲列表的遊戲項時,需要進入到遊戲詳情頁。

而對於“投籃專案”來說,一般有3~5個回合,在正式遊戲開始之前,會展示一個類似“Round1,Ready Go”的遊戲回合頁面,然後才是正式遊戲詳情頁。

為此,我們需要建立一個新的SwiftUI頁面來承載它,在Xcode左側檢視工具欄中新建一個SwiftUI檔案,命名為GameDetailView,如下圖所示:

在“遊戲回合”檢視中,我們可以看到幾個頁面元素:遊戲回合數(Round1)、遊戲規則(5米)、遊戲規則說明(距離籃筐)、遊戲結果(投中:0)。

由於上述的引數會隨著遊戲更新內容,因此需要宣告其變數進行儲存,如下程式碼所示:

@State var roundNum:Int = 1 @State var distanceNum:Int = 5 @State var gameGoal:Int = 0

上述程式碼中,我們聲明瞭3個變數:roundNum遊戲回合數、distanceNum籃筐距離、gameGoal單回合遊戲得分。這裡由於在後續要使用到數值計算,因此宣告的變數都是Int型別。

緊接著,我們來分析構建頁面,如下程式碼所示:

``` // 遊戲回合 func preStartView() -> some View { VStack { Spacer() VStack(alignment: .center, spacing: 40) { Text("Round" + String(roundNum)) .font(.system(size: 48, design: .rounded)) .bold() .foregroundColor(.white)

            VStack(alignment: .center, spacing: 20) {
                Text(String(distanceNum) + "米")
                .font(.system(size: 24))
                .foregroundColor(.white)
                .bold()

                Text("距離籃筐")
                .font(.system(size: 17))
                .foregroundColor(.white)
            }
        }
        Spacer()
        Spacer()
        Text("投中:  " + String(gameGoal))
        .font(.system(size: 17))
        .foregroundColor(.white)
    }

} ```

上述程式碼中,我們建立了一個新的檢視preStartView

分析下頁面元素,Text回合數文字為主要文字,使用48號圓形字型樣式,並設定bold加粗,foregroundColor文字填充顏色為白色,其餘文字基本修飾符型別。

這裡科普一個知識點。

由於宣告的變數是Int型別,而Text文字需要鍵入String型別的文字,因此需要將Int型別轉換為String型別。SwiftUI對於型別轉換可以直接使用型別包裹進行轉換,示例:String(roundNum),如此便可以直接將roundNum遊戲回合數轉換為String型別的引數。

另外,我們可以使用“+”對字串進行拼接,組成一個新的字串,示例:

"I"+"Love"+"You" 結果為 "ILoveYou"

迴歸正題,文字部分也是使用VStack垂直佈局檢視進行包裹元素,這裡的程式設計思想是“由中間向兩邊散開”,即相距離較近的元素可以先組合成一個容器,再和外邊的容器進行組合,便於設定檢視元素之間的間距。

完成好單個preStartView檢視後,我們在Body中展示它,如下程式碼所示:

ZStack { Color(.black).edgesIgnoringSafeArea(.all) preStartView() }

遊戲中檢視

在遊戲回合檢視展示後,使用者會進入到“遊戲中”檢視,正式開始參與遊戲。如下圖所示:

空氣投籃遊戲的遊戲檢視很簡答,還原在現實生活中的籃筐,由一個計分板和投籃的籃筐組成,而計分板分為兩塊,分別為個位數計分板和十位數計分板。

我們首先要匯入“籃筐”的圖片,同樣是在黑色背景下,我們需要一張SVG格式的向量圖,如下圖所示:

回到GameDetailView遊戲詳情頁,我們來構建遊戲中檢視的樣式,由於需要統計計分板的分數,因此需要宣告好變數部分,如下程式碼所示:

@State var unitsDigit: Int = 0 @State var tensDigit: Int = 0

上述程式碼中,unitsDigit為計分板個位數,tensDigit為計分板十位數。然後,我們再構建樣式部分,如下程式碼所示:

// 遊戲頁面 func playGameView() -> some View { VStack(alignment: .center, spacing: 40) { HStack(alignment: .center, spacing: 20) { Text(String(unitsDigit)) .font(.system(size: 120)) .bold() .foregroundColor(.white) .padding(40) .background(Color.gray) .cornerRadius(8) Text(String(tensDigit)) .font(.system(size: 120)) .bold() .foregroundColor(.white) .padding(40) .background(Color.gray) .cornerRadius(8) } Image("ball") .resizable() .aspectRatio(contentMode: .fit) .frame(maxWidth: UIScreen.main.bounds.size.width / 2) } }

上述程式碼,在playGameView檢視中,我們使用HStack橫向佈局容器包裹了計分板的樣式內容。

關於Text文字背景部分,SwiftUI提供的方法是使用padding撐開距離,再使用background背景顏色填充撐開的間距,最後再使用cornerRadius設定圓角。

如此,便實現了計分板的樣式效果,圖片部分這裡就不多說了。

同樣,前期什麼的Int型別的引數,在Text文字元件中使用需要轉換成String字串型別,方可使用。

此時我們就完成了2個頁面:preStartView遊戲回合檢視、playGameView遊戲中檢視。這裡做頁面的切換,我們也可以宣告一個引數來進行狀態的切換,如下程式碼所示:

@State var isReady:Bool = false

然後通過宣告好的isReady引數進行頁面間的切換,如下程式碼所示:

if isReady { playGameView() } else { preStartView() }

進入&返回

經過兩個章節的學習,我們完成了兩個主要的檢視:ContentView首頁檢視、GameDetailView遊戲詳情檢視,共4個頁面,接下來,我們要將這4個頁面串起來。

回到ContentView首頁檢視,我們盤一盤邏輯,在使用者點選遊戲項時,將會進入到GameDetailView遊戲詳情檢視,遊戲詳情頁首先會展示回合檢視,然後再開始遊戲。

瞭解了基本的互動邏輯後,我們先完成頁面之間的跳轉,這裡可以使用基於NavigationView頂部導航欄的跳轉方式,如下程式碼所示:

NavigationView { ZStack { Color(.black).edgesIgnoringSafeArea(.all) if isAffirmInWatch { gameListView() } else { prepareView() .onTapGesture { self.isAffirmInWatch = true } } } }

上述程式碼中,需要使用NavigationView將整個檢視包裹起來,然後再在gameListView遊戲列表檢視中新增跳轉方法,如下程式碼所示:

NavigationLink(destination: GameDetailView()) { gameRowView(gameName: "投籃", gameHelpText: "手舉球開始遊戲", gameImage: "basketball") }

如此,當我們點選“投籃”的遊戲項時,就會跳轉到GameDetailView遊戲詳情頁。

我們來到GameDetailView遊戲詳情頁,由於當前GameDetailView遊戲詳情頁的isReady引數變數為false,因此GameDetailView遊戲詳情頁會展示preStartView遊戲回合檢視。

我們希望的互動是preStartView遊戲回合檢視在顯示2秒後自動到playGameView遊戲中檢視。

這裡在頁面載入時增加多一個方法,如下程式碼所示:

ZStack { Color(.black).edgesIgnoringSafeArea(.all) if isReady { playGameView() } else { preStartView() } } .onAppear(){ DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.isReady = true } }

上述程式碼中,我們在GameDetailView遊戲詳情頁onAppear展示時添加了一個在主執行緒上的操作,即基於當前時間2秒鐘後,切換isReady狀態。

如此,我們便實現了在使用者進入GameDetailView遊戲詳情頁時,先展示preStartView遊戲回合檢視,再展示preStartView遊戲中檢視了。

進入操作有了,最後是返回操作。

原有的返回按鈕太醜了,我們可以自定義一個返回按鈕,如下程式碼所示:

// 返回上一頁 func backButton() -> some View { Button(action: { }) { Image(systemName: "chevron.left.circle") .font(.system(size: 17)) .foregroundColor(.white) } }

並將它加到GameDetailView遊戲詳情頁檢視中,如下程式碼所示:

ZStack { Color(.black).edgesIgnoringSafeArea(.all) if isReady { playGameView() } else { preStartView() } } .navigationBarBackButtonHidden(true) .navigationBarItems(leading: backButton())

返回的操作互動也很簡單,我們可以呼叫SwiftUI的通用返回方法,如下程式碼所示:

@Environment(.presentationMode) var mode

最後在點選backButton返回按鈕的時候使用返回方法,如下程式碼所示:

self.mode.wrappedValue.dismiss()

本章預覽

完成後,我們回到ContentView預覽下整體專案。

1664534091925-a33785ed-b6bb-42cc-bb18-1822cb65beaa.gif

本章小結

恭喜你,完成了本章的所有內容!

在本章中,我們構建了遊戲詳情頁的檢視,並完成了詳情頁的兩種狀態頁面,準備開始遊戲和遊戲中的狀態頁面,還實現了從首頁跳轉到詳情頁、返回首頁的全過程。

空氣投籃專案iOS端整體的互動內容基本就到這裡了,接下來我們將繼續使用MVVM開發模式調整iOS端的內容,後面還會完成Watch端的頁面及其互動。

最後如果條件成熟,我們將呼叫Apple提供的各種感測器來完成真實的互動體驗。

請保持期待吧~

版權宣告

本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!

「其他文章」