實現(xiàn)grid布局窒升,實現(xiàn)翻牌規(guī)則
之前我們卡片都在一行功炮,在某個方向上浪費了很多空間。我們希望卡片分布在多行也就是網(wǎng)格狀布局肋演。在目前的siwft中抑诸,并沒有這樣的布局,需要我們自己實現(xiàn)爹殊。
我們創(chuàng)建一個 Grid 結(jié)構(gòu)體蜕乡,來實現(xiàn)網(wǎng)格布局。
struct Grid<Item,ItemView>: View where Item:Identifiable, ItemView:View {
var items:[Item]
var viewForItem: (Item) -> ItemView
init(items:[Item], viewForItem:@escaping (Item)->ItemView) {
self.items = items
self.viewForItem = viewForItem
}
var body: some View {
GeometryReader { gemory in
body(for: GridLayout(itemCount: items.count, in: gemory.size))
}
}
func body(for layout:GridLayout) -> some View {
ForEach(items) { item in
let index = items.firstIndex(matching:item)!
viewForItem(item)
.frame(width:layout.itemSize.width,height:layout.itemSize.height)
.position(layout.location(ofItemAt: index))
}
}
}
我們需要知道 items 以及根據(jù)item生成對應(yīng)的視圖梗夸。并計算他們的大小擺放他們的位置层玲。這是我們這個結(jié)構(gòu)體要實現(xiàn)的。
viewForItem
是一個函數(shù)類型的反症,在初始化方法中需要添加關(guān)鍵字 @escaping 來表明這是一個逃逸閉包辛块。這個關(guān)鍵字讓程序知道 這個初始化很快會執(zhí)行完,但函數(shù)會延遲調(diào)用铅碍,不會隨著init函數(shù)一起調(diào)用润绵,而是在將來某些時候觸發(fā)調(diào)用。這里要使用@escaping告訴計算機胞谈。
我們可以通過GeometryReader來獲取分配的空間大小尘盼,然后使用GridLayout來計算每個卡片的大小和位置。
使用ForEach 函數(shù)時烦绳,需要參數(shù)是有唯一標示的卿捎,而我們的Item我們并不關(guān)心是什么內(nèi)容,但是函數(shù)要求Item需要是遵循了Identifiable協(xié)議的径密,因此我們要限制一下我們的結(jié)構(gòu)體午阵,要求Grid結(jié)構(gòu)體中用到的Item是遵循了Identifiable協(xié)議的。在這里我們又一次使用了泛型的概念享扔。同樣的趟庄,viewForItem函數(shù)要求接受一個item括细,返回一個view,ItemView要求遵循了View協(xié)議戚啥。
struct Grid<Item,ItemView>: View where Item:Identifiable, ItemView:View {}
在我們的視圖中奋单,將原來的HStack換成我們自己寫的網(wǎng)格布局,并設(shè)置卡片之間的間距猫十。
struct EmojiMemoryGameView: View {
@ObservedObject var viewModel:EmojiMemoryGame
var body: some View {
Grid(items: viewModel.cards) { card in
GridView(card: card).onTapGesture {
viewModel.shoose(card: card)
}.padding(cardPadding)
}
.padding()
.foregroundColor(.orange)
}
let cardPadding:CGFloat = 5
}
接下來我們要完善一下游戲規(guī)則览濒。
啟動時所有卡片都是反面
點開第一個卡片時,翻開卡片
點開第二個卡片時拖云,和第一個卡片進行對比
點開第三張卡片時贷笛,合上其他卡片
我們處理一下我們選擇卡片的邏輯代碼:
我們需要定義一個變量來跟蹤正面朝上的那個卡片的位置索引var indexOfTheOneAndOnlyFaceupCard:Int?
剛開始沒有正面朝上,所以這是個可選值宙项。
我們要判斷選擇的卡片是否是正面朝上以及是否已經(jīng)匹配乏苦。
mutating func choose(card:Card) {
print("card choosen:\(card)")
if let choosenIndex = cards.firstIndex(matching: card), !cards[choosenIndex].isFaceUp, !cards[choosenIndex].isMactched {
if let potentialMactchIndex = indexOfTheOneAndOnlyFaceupCard {
if cards[choosenIndex].content == cards[potentialMactchIndex].content {
// matched
cards[choosenIndex].isMactched = true
cards[potentialMactchIndex].isMactched = true
}
indexOfTheOneAndOnlyFaceupCard = nil
}else {
for index in cards.indices {
cards[index].isFaceUp = false
}
indexOfTheOneAndOnlyFaceupCard = choosenIndex
}
cards[choosenIndex].isFaceUp = true
}
}
然后我們運行后發(fā)現(xiàn),被匹配之后的卡片雖然正面朝下尤筐,但是已經(jīng)不能點擊了汇荐。這樣的界面行為很不好。我們希望已經(jīng)匹配成功的卡片不再顯示盆繁。我們在視圖代碼中掀淘,修改顯示卡片背面的邏輯,只有沒有匹配的卡片才進行繪制油昂。
ZStack {
if card.isFaceUp {
RoundedRectangle(cornerRadius: conerRadius).fill(Color.white)
RoundedRectangle(cornerRadius: conerRadius).stroke(lineWidth: edgeLineWidth)
Text(card.content)
}else {
if !card.isMactched {RoundedRectangle(cornerRadius: conerRadius).fill(Color.orange)}
}
}
上面處理了界面革娄。下面我們來優(yōu)化下選擇卡片之后的代碼。
我們把indexOfTheOneAndOnlyFaceupCard賦值之后邏輯代碼寫在一起冕碟,將indexOfTheOneAndOnlyFaceupCard作為計算屬性拦惋,在set方法和get方法中處理邏輯
var indexOfTheOneAndOnlyFaceupCard:Int? {
get {
cards.indices.filter{cards[$0].isFaceUp}.only
}
set {
for index in cards.indices {
cards[index].isFaceUp = index == newValue
}
}
}
這樣的話 choose中的代碼就可以更簡單了
mutating func choose(card:Card) {
if let choosenIndex = cards.firstIndex(matching: card), !cards[choosenIndex].isFaceUp, !cards[choosenIndex].isMactched {
if let potentialMactchIndex = indexOfTheOneAndOnlyFaceupCard {
if cards[choosenIndex].content == cards[potentialMactchIndex].content {
// matched
cards[choosenIndex].isMactched = true
cards[potentialMactchIndex].isMactched = true
}
cards[choosenIndex].isFaceUp = true
}else {
indexOfTheOneAndOnlyFaceupCard = choosenIndex
}
}
}
代碼中還做了其他的優(yōu)化,今天的效果圖如下:
個人主頁有GitHub地址主頁安寺,查看源碼請點擊:https://github.com/MyColourfulLife/MySwiftUI