iOS-项目实战SwiftUI

使用Apple最新的Swift UI技术编写更好的app(4)

2020-09-22  本文已影响0人  _我和你一样

实现grid布局,实现翻牌规则

之前我们卡片都在一行,在某个方向上浪费了很多空间。我们希望卡片分布在多行也就是网格状布局。在目前的siwft中,并没有这样的布局,需要我们自己实现。

我们创建一个 Grid 结构体,来实现网格布局。

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 以及根据item生成对应的视图。并计算他们的大小摆放他们的位置。这是我们这个结构体要实现的。

viewForItem是一个函数类型的,在初始化方法中需要添加关键字 @escaping 来表明这是一个逃逸闭包。这个关键字让程序知道 这个初始化很快会执行完,但函数会延迟调用,不会随着init函数一起调用,而是在将来某些时候触发调用。这里要使用@escaping告诉计算机。

我们可以通过GeometryReader来获取分配的空间大小,然后使用GridLayout来计算每个卡片的大小和位置。

使用ForEach 函数时,需要参数是有唯一标示的,而我们的Item我们并不关心是什么内容,但是函数要求Item需要是遵循了Identifiable协议的,因此我们要限制一下我们的结构体,要求Grid结构体中用到的Item是遵循了Identifiable协议的。在这里我们又一次使用了泛型的概念。同样的,viewForItem函数要求接受一个item,返回一个view,ItemView要求遵循了View协议。

struct Grid<Item,ItemView>: View where Item:Identifiable, ItemView:View {}

在我们的视图中,将原来的HStack换成我们自己写的网格布局,并设置卡片之间的间距。

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
}
image.png

接下来我们要完善一下游戏规则。

启动时所有卡片都是反面

点开第一个卡片时,翻开卡片

点开第二个卡片时,和第一个卡片进行对比

点开第三张卡片时,合上其他卡片

我们处理一下我们选择卡片的逻辑代码:

我们需要定义一个变量来跟踪正面朝上的那个卡片的位置索引var indexOfTheOneAndOnlyFaceupCard:Int?刚开始没有正面朝上,所以这是个可选值。

我们要判断选择的卡片是否是正面朝上以及是否已经匹配。

   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
        }
    }

然后我们运行后发现,被匹配之后的卡片虽然正面朝下,但是已经不能点击了。这样的界面行为很不好。我们希望已经匹配成功的卡片不再显示。我们在视图代码中,修改显示卡片背面的逻辑,只有没有匹配的卡片才进行绘制。

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)}
                }
            }

上面处理了界面。下面我们来优化下选择卡片之后的代码。

我们把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
        }
        }
    }

代码中还做了其他的优化,今天的效果图如下:

dayfour.gif

个人主页有GitHub地址主页,查看源码请点击:https://github.com/MyColourfulLife/MySwiftUI

上一篇下一篇

猜你喜欢

热点阅读