続・スタンフォード式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の動画もアップされました。これらも振り返りを投稿できればと思っているので、是非ともスキとフォローをお願いします!