見出し画像

続・スタンフォード式iOSアプリ開発講座をやってみた【後編②】

ページのタイトルがややこしくなってきましたが、前回の続きです。レクチャー4をハンズオンしていきます。

レクチャー4のテーマ

まず、MVVMアーキテクチャを連動させるために、ビューモデルをObservableObjectプロトコルに適合させます。そしてビューにはObservedObject属性のプロパティを導入します。さらに、列挙型、オプショナルを使って、ゲームロジックをより具体的に実装していきます。

ビュー側のコード

このアプリでは、ContentView型とCardView型がMVVMアーキテクチャにおけるビューの概念を果たします。

下の構造体は、画面全体のUIを示すビューです。

import SwiftUI

struct ContentView: View {
   @ObservedObject var viewModel: EmojiMemoryGame
   
   var body: some View {
       ScrollView {
           LazyVGrid(columns: [GridItem(.adaptive(minimum: 65))] ) {
               ForEach(viewModel.cards) { card in
                   CardView(card: card)
                       .aspectRatio(2/3, contentMode: .fit)
                       .onTapGesture {
                           viewModel.choose(card)
                       }
               }
           }
       }
       .foregroundColor(.red)
       .padding(.horizontal)
   }
}

下の構造体は、個々のカードを示すUIのビューです。

struct CardView: View {
   let card: MemoryGame<String>.Card
   
   var body: some View {
       ZStack {
           let shape = RoundedRectangle(cornerRadius: 20.0)
           if card.isFaceUp {
               shape.fill().foregroundColor(.white)
               shape.strokeBorder(lineWidth: 3.0)
               Text(card.content).font(.largeTitle)
           } else if card.isMatched {
               shape.opacity(0.0)
           } else {
               shape.fill()
           }
       }
   }
}


ビューモデル側のコード

このアプリでは、EmojiMemoryGameクラスがビューモデルの概念を果たします。

class EmojiMemoryGame: ObservableObject {
   static let emojis = ["🚗", "🚌", "🏎", "🚓", "🚕", "🚒", "🚛", "🚜",
                        "🚍", "🚡", "🚟", "🚃", "🚞", "🚝", "🚄", "🚅",
                        "✈️", "🛰", "🛩", "🚀", "🛸", "⛵️", "🛶", "🚤"]
   
   static func createMemoryGame() -> MemoryGame<String> {
       return MemoryGame<String>(numberOfPairsOfCards: 4) { pairIndex in 
           EmojiMemoryGame.emojis[pairIndex] 
       }
   }
   
   @Published private var model: MemoryGame<String> = createMemoryGame()
   
   var cards: Array<MemoryGame<String>.Card> {
       model.cards
   }
   
   func choose(_ card: MemoryGame<String>.Card) {
       model.choose(card)
   }
}





モデル側のコード

このアプリでは、MemoryGame構造体がモデルの概念を果たします。

struct MemoryGame<CardContent> where CardContent: Equatable {
   private(set) var cards: Array<Card>
   private var indexOfTheOneAndOnlyFaceUpCard: Int?

   mutating func choose(_ card: Card) {
       if let chosenIndex = cards.firstIndex(where: { $0.id == card.id }), !cards[chosenIndex].isFaceUp, !cards[chosenIndex].isMatched  {
           if let potentialMatchIndex = indexOfTheOneAndOnlyFaceUpCard {
               if cards[chosenIndex].content == cards[potentialMatchIndex].content {
                   cards[chosenIndex].isMatched = true
                   cards[potentialMatchIndex].isMatched = true
               }
               indexOfTheOneAndOnlyFaceUpCard = nil
           } else {
               for index in cards.indices {
                   cards[index].isFaceUp = false
               }
               indexOfTheOneAndOnlyFaceUpCard = chosenIndex
           }
           cards[chosenIndex].isFaceUp.toggle()
       }
   }
   
   init(numberOfPairsOfCards: Int, createCardContet: (Int) -> CardContent) {
       self.cards = Array<Card>()
       // add numberOfPairsOfCards x 2 cards to array
       for pairIndex in 0..<numberOfPairsOfCards {
           let content = createCardContet(pairIndex)
           cards.append(Card(content: content, id: pairIndex*2))
           cards.append(Card(content: content, id: pairIndex*2+1))
       }
   }
   
   struct Card: Identifiable {
       var isFaceUp  = false
       var isMatched = false
       let content: CardContent
       var id: Int
   }
}



アプリのインスタンス

MemorizeApp.swiftファイルには、プロジェクトのエントリーポイントとなるMemorizeApp構造体が定義されています。この構造体のインスタンスがアプリ自身です。

@main
struct MemorizeApp: App {
   let game = EmojiMemoryGame()
   
   var body: some Scene {
       WindowGroup {
           ContentView(viewModel: game)
       }
   }
}

ここでは、ビューモデルのインスタンスを作成して、ビューに渡しています。

まだまだ続く

レクチャー4を実践している間に、レクチャー5とレクチャー6の動画もアップされました。これらも振り返りを投稿できればと思っているので、是非ともスキとフォローをお願いします!

いいなと思ったら応援しよう!