#4–1, SwiftUI 從零開始:大老二

Syuan
彼得潘的 Swift iOS / Flutter App 開發教室
8 min readNov 17, 2022

--

啊怎麼就變成SwiftUI了?

剛開始只是因為我在拉介面的時候發現SwiftUI不用下Autolayout好像很棒,所以就想用著做做看順便學,做了才發現事情不是憨人想的那麼簡單…

結果瞎搞好久現在也要收尾了,防止日後失憶就先記下來。

這次要做的題目是大老二,一個玩家對三個電腦,單機遊玩,大致完成的樣子如下。

當然不是一開始就會變成這樣,不過千里之行始於足下,首先從介面佈局開始吧。

馬上就會看到預設已經建好了一個 struct 叫做 ContentView,且這個 ContentView 遵守 View 這個 Protocol ,所以才可以在畫面上顯示。

View 這個 Protocol 會要求一個 Body 屬性,他會描述 view 在畫面上會是什麼樣子。現在可以把卡牌實際放進畫面顯示看看,52張卡牌圖檔記得先拉入Assets並命名好。

Assets裡面的卡牌圖片檔名輸入到 Image(“Spade Ace”)

原本的VStack是垂直佈局,將內涵的元件由上而下垂直排列,不過這裡可以先不動。resizable()是縮放圖片使其能配置到當前空間。

aspectRatio(2/3, contentMode: .fit)寬高比,解決圖片縮放後能維持原比例不會變形,這裡我給上邊寬與高是2比3。

HStack

那當然不可能只擺一張牌在畫面上,所以可以用 HStack 來實現。

HStack的佈局中H指的就是 horizontal 水平佈局,所以我在裡面加入了第二張卡牌的描述,馬上就可以看到卡牌呈現水平排列。

ForEach

這裡就會當然就會想,大老二一共有四個玩家各持13張牌在台面上,那總不能image重複52次,所以我可以用 ForEach來實現多張卡牌的匯入。

先把卡牌名稱放進陣列,在 let card 的位置。

接著把陣列匯入ForEach,且 ForEach 需要一個每個元素獨特的id來做區分,而 self 就是指 array 中每個元素自己就是id,所以才會寫成\.self。其中的{}括號內則放入剛剛規範好的卡牌格式。

Extract Subview

下個階段因為要對佈局做更仔細的配置,所以可以先把卡牌格式另外做成 subView,精簡程式碼。按著command後滑鼠點選Image。

之後把名稱修改一下,確認subView有匯入ForEach就可以了。

Model 的建立

上面我們把卡牌名稱直接放在陣列裡面,但撲克牌畢竟有52張,一次在陣列擺52個元素實在不是好做法,且實際上我們還需要用卡牌來做很多事,舉凡像是卡牌間的大小比較:如花色大小比較、數字比較等,這些都是要去額外定義的,沒有辦法只用個陣列就解決。所以我需要另外建立一個 model 來處理這些東西。

卡牌 model 的建立原理可以參考先前建立過的範例:

這裡先用比較簡易的內容來測試,另外建立一個例如Game.swift 的檔案,然後建立下面內容。

enum Rank {
case Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King, Ace, Two
}

enum Suit {
case Club, Diamond, Heart, Spade
}


struct Card: Identifiable {
var rank: Rank
var suit: Suit
var filename: String {
return "\(suit) \(rank)"
}
var id = UUID()
}

var testCards = [
Card(rank: .Ace, suit: .Spade),
Card(rank: .King, suit: .Spade),
Card(rank: .Queen, suit: .Spade),
Card(rank: .Jack, suit: .Spade),
Card(rank: .Ten, suit: .Spade),
]

如前面所述因為 ForEach需要元素 id,所以這邊要在 Card 構成加上 Identifiable,這可以讓我們做出來的每張牌都有獨立的 UUID,這樣就可以在 ForEach 使用了。

另一個要建立的 model 是玩家,也就是包含我們自己在內的4個玩家。

typealias Stack = [Card]

struct Player {
var cards = Stack()
var playerIsMe = false
}

var testPlayers = [
Player(),
Player(),
Player(),
Player(playerIsMe: true),
]

玩家的手上的手牌原本是 [Card] ,但這裡我給他下了個 typealias 取名Stack,因為之後我們還需要大量對玩家的手牌引用,所以有別名比較好處理。而另一個 playerIsMe 預設 false 則是為了以後切換玩家出牌順序處理。

現在可以把 testCard 加到13張來測試,暫且把Stack() 改成testCard。

LazyVGrid

我們剛剛建立好玩家後,每個玩家都有13張牌,且剛剛加到13張後可以發現牌面太小顯然不好看,這裡使用 LazyVGrid 取代 HStack 來處理卡牌的佈局。

GridItem 會自動計算 column 的數量,先在 adaptive size 設定 column 的寬度為100,spacing間距設為-76,這樣就可以讓每個 column 做到牌面重疊的效果。

而四個玩家有四組手牌,可以再利用 ForEach 來重複處理這個 LazyVGrid。同樣的 Player 這個 Struct 也要 Identifiable 才可以處理。最後再用 VStack (Vertical) 把垂直排列元件,讓四組手牌垂直排列。

現在我們可以顯示四家手牌了。

下回來把出牌區域做出來。

--

--