美文网首页iOS-项目实战SwiftUI
使用Apple最新的Swift UI技术编写更好的app(4)

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

作者: _我和你一样 | 来源:发表于2020-09-22 17:05 被阅读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

    相关文章

      网友评论

        本文标题:使用Apple最新的Swift UI技术编写更好的app(4)

        本文链接:https://www.haomeiwen.com/subject/npnwyktx.html