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

語言: CN / TW / HK

theme: smartblue

專案背景

和往常一樣的下午,午後的陽光灑在身上,感覺有些舒服。突然腦海裡閃過一個念頭,小步快跑,起身,雙手舉起,目視前方,揮手投籃,唰.........最近一款APP幾乎引起了所有男孩子的興趣,這就是空氣投籃。

想起年少時和一群朋友走在路上,男孩子們都或多或少做過這種“傻傻的”動作,那是青春的味道。

而竟然有家公司將它做了出來,一時間馬上下載、安裝.......額.......這.......需要AppleWatch才能用。好吧,竟然沒有辦法體驗空氣投籃,不如就用用自己的專長,畫畫iOS端的頁面吧。

專案搭建

首先開啟Xcode,建立一個新的SwiftUI專案,命名為AirBall,如下圖所示:

空氣投籃分為iOS端和Watch,本章我們先來完成iOS的相關頁面。iOS端頁面操作及其流程如下圖所示:

實戰程式設計

準備遊戲檢視

首先是準備遊戲檢視,簡單分析可知,它由文字Text和圖片Image組成,背景顏色填充為黑色。Image圖片部分,需要匯入一張SVG適量圖片,使得其很好地與背景融合。如下圖所示:

匯入完成後,我們來構建頁面部分。在ContentView檔案中,我們鍵入以下程式碼:

``` // 準備遊戲 func prepareView() -> some View { VStack(alignment: .center, spacing: 80) { Spacer() Text("請確定你已啟用Apple Watch上的空氣投籃App") .font(.system(size: 17)) .foregroundColor(.white) .lineLimit(2) .lineSpacing(15) .multilineTextAlignment(.center)

    Image("watch_application")
    .resizable()
    .aspectRatio(contentMode: .fit)
    Spacer()
    Spacer()
}.frame(maxWidth: Constants.screenWidth / 2)

} ```

上述程式碼中,我們建立了一個新的View檢視prepareView準備開始遊戲檢視。

在prepareView檢視中,文字Text和Image圖片使用VStack垂直佈局容器包裹,並設定其對齊方式為居中對齊,容器內容元素間距為80。

Text文字部分的處理為設定font字型為17號字,設定foregroundColor填充色為白色,由於文字過長可能導致頁面無法展示的原因,這裡設定lineLimit文字顯示行數為2行,並設定換行時multilineTextAlignment文字對齊方式為居中對齊。

Image圖片部分,設定resizable圖片縮放,並設定aspectRatio保持原本的寬高比避免變形。

最後使用frame設定VStack垂直佈局容器的寬度,為螢幕寬度的一半。為了增強使用者體驗,在VStack垂直佈局容器中使用Spacer佔位符,下方設定2個,上方設定一個,這個視覺元素就會展示在螢幕上部分2/3的位置,又是一個小技巧。

完成後,我們在Body中展示,如下程式碼所示:

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

上述程式碼中,我們使用ZStack疊加檢視,將prepareView準備遊戲檢視和Color顏色疊加,顏色部分使用edgesIgnoringSafeArea忽略全部安全區域,便可讓黑色背景鋪滿整個螢幕。

遊戲列表檢視

開啟App,進入準備遊戲檢視,此時需要與Watch端聯動,在Watch端確認後,iOS端將進入至遊戲列表頁面。

遊戲列表頁面的互動邏輯是,點選遊戲卡片則進入到遊戲中,在遊戲列表頁左右滑動可切換遊戲。

我們先來完成單張遊戲卡片的設計。分析得知,單張遊戲卡片的內容包括3塊內容:遊戲專案、遊戲說明、遊戲封面。

我們建立一個新的檢視,程式碼如下所示:

``` // MARK: 遊戲項

struct gameRowView: View {

var gameName: String
var gameHelpText: String
var gameImage: String

var body: some View {
    VStack(alignment: .center, spacing: 60) {
        Text(gameName)
            .font(.system(size: 48))
            .bold()
            .foregroundColor(.white)

        VStack(alignment: .center, spacing: 10) {
            Text(gameHelpText)
                .font(.system(size: 17))
                .foregroundColor(.white)
            Image(gameImage)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(maxHeight:UIScreen.main.bounds.size.width - 20)
        }
    }
}

} ```

上述程式碼中,我們建立了一個新的結構體gameRowView遊戲項檢視。

之所以建立一個新的結構體,而沒有和上面prepareView準備遊戲檢視一樣直接定義View檢視,是因為我們需要將gameRowView遊戲項檢視作為“母版”,然後按照單個gameRowView遊戲項檢視構建多個一樣樣式的遊戲卡片。

在gameRowView遊戲項檢視中,我們聲明瞭3個String型別的變數:gameName遊戲名稱、gameHelpText遊戲說明、gameImage遊戲封面。

然後在其Body中建立樣式,由於遊戲名稱的Text和其餘兩塊在頁面上還是有些距離,這裡使用了2個VStack垂直佈局容器,將遊戲說明和遊戲封面放在一個容器中,它們直接的間距為10,而再用一個VStack垂直佈局容器再把遊戲標題包裹在一起,間距為60。

這裡額外再補充一個知識點。

就是遊戲封面使用frame設定其大小的問題,由於我們匯入的遊戲封面圖片可能存在大小不一致的問題,因此如果需要讓這個遊戲卡片看起來元素位置保持一致,由於使用了VStack垂直佈局容器,因此,可以設定圖片maxHeight高度為螢幕width寬度,再減去20留點邊距。

這樣做,無論圖片尺寸是多少,每個遊戲卡片展示的遊戲名稱、遊戲說明、遊戲封面的位置就保持一致了。

我們匯入兩張SVG格式的遊戲圖片作為素材使用,如下圖所示:

遊戲卡片是左右滑動切換的互動,這時我們就可以在ContentView檢視中再建立一個View檢視構建它,如下程式碼所示:

// 遊戲列表 func gameListView() -> some View { TabView { gameRowView(gameName: "投籃", gameHelpText: "手舉球開始遊戲", gameImage: "basketball") gameRowView(gameName: "打棒球", gameHelpText: "雙手揮動開始遊戲", gameImage: "baseball") } .tabViewStyle(PageTabViewStyle()) }

上述程式碼中,我們建立了一個遊戲列表檢視gameListView。

然後使用TabView滾動檢視容器包裹了2個gameRowView遊戲項檢視,遊戲項檢視中我們給宣告的變數賦值以顯示內容。最後設定TabView滾動檢視的樣式,為PageTabViewStyle分頁滾動型別,如此便實現了橫向切換遊戲卡片的互動。

本章程式碼

為方便學習,本章完整程式碼如下所示:

``` import SwiftUI

struct ContentView: View { var body: some View { ZStack { Color(.black).edgesIgnoringSafeArea(.all)

        gameListView()
    }
}

// 準備遊戲
func prepareView() -> some View {
    VStack(alignment: .center, spacing: 80) {
        Spacer()
        Text("請確定你已啟用Apple Watch上的空氣投籃App")
            .font(.system(size: 17))
            .foregroundColor(.white)
            .lineLimit(2)
            .lineSpacing(15)
            .multilineTextAlignment(.center)

        Image("watch_application")
            .resizable()
            .aspectRatio(contentMode: .fit)
        Spacer()
        Spacer()
    }.frame(maxWidth: UIScreen.main.bounds.size.width / 2)
}

// 遊戲列表
func gameListView() -> some View {
    TabView {
        gameRowView(gameName: "投籃", gameHelpText: "手舉球開始遊戲", gameImage: "basketball")
        gameRowView(gameName: "打棒球", gameHelpText: "雙手揮動開始遊戲", gameImage: "baseball")
    }
    .tabViewStyle(PageTabViewStyle())
}

}

// MARK: 遊戲項

struct gameRowView: View { var gameName: String var gameHelpText: String var gameImage: String

var body: some View {
    VStack(alignment: .center, spacing: 60) {
        Text(gameName)
            .font(.system(size: 48))
            .bold()
            .foregroundColor(.white)

        VStack(alignment: .center, spacing: 10) {
            Text(gameHelpText)
                .font(.system(size: 17))
                .foregroundColor(.white)
            Image(gameImage)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(maxHeight: UIScreen.main.bounds.size.width - 20)
        }
    }
}

}

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

本章小結

首先恭喜你,完成了本章的介紹的所有內容!

空氣投籃iOS端的頁面目前我們只完成了前2張,接下來,我們將繼續完成其餘的頁面及其互動,以及後面也會切換到Watch端,完成空氣投籃Watch端的相關頁面設計。

總的來說,空氣投籃App的頁面及其互動並不複雜。

這個專案很小,卻實實在在戳中了很多使用者的內心。彷彿某些個人“很傻”的小習慣被大眾所認知、所接受,這種滿足感剛好刺激到了某個痛點,很小,但很痛。

這可能就是一款好的產品的象徵,也應該是每個提供產品服務的企業所追求的。

望共勉之~

版權宣告

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

「其他文章」