MemorizeApp.swift
import SwiftUI
@main
struct MemorizeApp: App {
let game = EmojiMemoryGame()
var body: some Scene {
WindowGroup {
ContentView(viewModel: game)
}
}
}
//struct MemorizeApp: App {
// //用let,因为EmojiMemoryGame()是个class,这里是一个引用类型,game就是一个指针,let game意思是我不会改变这个指针,但他指向哪我是会改变的,游戏会变,但路径不变。
// //viewmodel EmojiMemoryGame可以通过指针更改
// //创建一个EmojiMemoryGame()开闭括号因为类获得了免费的init,EmojiMemoryGame是个class,调用它获得一个什么都不做的init,里面只有一个变量获取免费的init。
// //我们正在用那个免费的init来创建表情符号游戏
// //MARK:3,搞个viewmodel,为的是给下面的ContentView的变量viewModel赋值
// let game = EmojiMemoryGame()
// var body: some Scene {
// WindowGroup {
// //给ContentView的变量viewModel赋值
// //把名字为game的viewModel传给ContentView,viewModel的类型是EmojiMemoryGame,EmojiMemoryGame是个class,是引用类型的,传递指针
// ContentView(viewModel: game)
// }
// }
//}
ContentView.swift
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: EmojiMemoryGame
@State var emojiCount = 4
var body: some View {
VStack {
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)
Spacer()
}
.padding(.horizontal)
}
}
struct CardView: View {
let card: MemoryGame<String>.Card
var body: some View {
ZStack {
let shape = RoundedRectangle(cornerRadius: 20)
if card.isFaceUp {
shape.fill().foregroundColor(.white)
shape.strokeBorder(lineWidth: 3)
Text(card.content).font(.largeTitle)
} else {
shape.fill()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let game = EmojiMemoryGame()
ContentView(viewModel: game)
.preferredColorScheme(/*@START_MENU_TOKEN@*/.dark/*@END_MENU_TOKEN@*/)
ContentView(viewModel: game)
.preferredColorScheme(.light)
}
}
//struct ContentView: View {
// var emojis = ["🚙", "🚗", "⛱", "🎡", "🪝", "🗿", "🛖", "⏱", "☎️", "🎰","🚜","🛴", "✈️"]
// @State var emojiCount = 4
//
// var body: some View {
// VStack {
// ScrollView {
// LazyVGrid(columns: [GridItem(.adaptive(minimum: 65))]){
// ForEach(emojis[0..<emojiCount], id: \.self) { emoji in
// CardView(content: emoji).aspectRatio(2/3, contentMode: .fit)
// }
// }
// }
// .foregroundColor(.red)
// Spacer()
// HStack {
// remove
// Spacer()
// add
// }
// .font(.largeTitle)
// .padding(.horizontal)
// }
// .padding(.horizontal)
//
// }
// var remove: some View {
// Button(action:{
// if emojiCount > 1{
// emojiCount -= 1
// }
// }, label: {
// Image(systemName: "minus.circle")
// })
// }
// var add: some View {
// Button(action:{
// if emojiCount < emojis.count {
// emojiCount += 1
// }
// }, label: {
// Image(systemName: "plus.circle")
// })
// }
//}
//
//struct CardView: View {
// var content: String
// @State var isFaceUp: Bool = true
//
// var body: some View {
// ZStack {
// let shape = RoundedRectangle(cornerRadius: 20)
// if isFaceUp {
// shape.fill().foregroundColor(.white)
// shape.strokeBorder(lineWidth: 3)
// Text(content).font(.largeTitle)
//
// } else {
// shape.fill()
// }
// }
// .onTapGesture {
// isFaceUp = !isFaceUp
// }
// }
//}
//struct ContentView: View {
// //这是用viewModel浏览卡片
// //MARK:26,@ObservedObject意思是当viewModel有变化时,重建整个body
// //MARK: 1,把View和viewModel链接:ContentView必须能看到model,它才能绘制model的UI
// //MARK: 2,因为ContentView多了一个变量viewModel,需要把用到ContentView的地方把变量viewModel都赋值
// //MARK: 8,把var改成let
// @ObservedObject var viewModel: EmojiMemoryGame
// //var viewModel: EmojiMemoryGame, ContentView通过EmojiMemoryGame来显示,现在只用让var body工作来展示
// //var声明变量,body是变量的名字,some View是变量的类型.{}内的是个没有名字的函数,隐藏了return,返回一个Text
// //content 最后一个参数,可以从括号里删除,{}里的是ZStack的content参数
// //用ForEach创造数组中每个事物的CardView,content是我们的CardView,ForEach需要数组emojis,有content这个内容参数,在hstack中的视图组合器,ForEach本身不是视图组合器,是乐高制造商,他让一袋乐高装满了东西。提供为数组中的每个东西做点事情。
// //给函数{}加参数{参数名1, 参数名2, in};CardView(content: emoji)为每个CardView指定emoji;
// //id: \.self 用结构体本身做唯一标识符
//// var emojis = ["🚝","🏍","🚖","🚀","🚕","🚎","🚑","🚜","🦽","🚔","🛸","🚂","🚞","🚨"]
////
//// @State var emojiCount = 14
//
// //var body:给我个uI让我看看model里有什么
// var body: some View {
//
// ScrollView{
// //LazyVGrid会制作许多列,不能放数字,要放GridItem数组,GridItem可以控制列,GridItem(.fixed(200))第一个列有200宽度
// //LazyVGrid水平使用所有宽度,垂直尽可能小以便多放东西。LazyVGrid的Lazy意思是不滚动不加载
// //.adaptive(minimum: 65)最小宽度65
// LazyVGrid(columns: [GridItem(.adaptive(minimum: 65))]){
// //让ForEach认为string是可识别的,id: \.self
// //emoji是这个函数的参数,用in将它分开,从执行的代码返回CardView
//
// //MARK:5,因为有了viewmodel,所以视图要现实viewmodel中的东西
// //ForEach(emojis[0..<emojiCount], id: \.self){ emoji in
// //出错是因为cards不可识别?卡片数组必须Identifiable
//
// //如果viewModel中是private(set) var model,下面的var cards:注释掉没有,则ForEach(viewModel.model.cards)
// //MARK:6,把emoji in 改成card in
// ForEach(viewModel.cards){ card in
//
// //用ForEach遍历viewModel中的所有卡片,cards是viewmodel中的var cards: Array<MemoryGame<String>.Card>,model.cards是个数组。ForEach遍历出每一张卡片,要给它做个CardView视图展示
// //人们创建struct时会设置值
// //MARK:9,CardView(content: emoji)改为CardView(card: card)
// //CardView是单个卡片视图
// CardView(card: card)
// .aspectRatio(2/3, contentMode: .fit)
//
// //MARK: - intent(s)添加注释
// //intent都记录在ViewModel中,通过它来表达用户意图
// //MARK:12, .onTapGesture 给卡片添加翻面功能
// .onTapGesture {
// viewModel.choose(card)
// }
// }
// }
// }
//
// .foregroundColor(.red)
// //Spacer占有其他人不要的所有空间
//// Spacer()
//// HStack{
//// add
//// Spacer()
//// remove
//// }
//// .font(.largeTitle)
//// .padding(.horizontal)
//
//
// .padding(.horizontal)
//
// }
//
////加减卡片
//// var remove: some View {
//// Button {
//// if emojiCount > 1 {
//// emojiCount -= 1
//// }
//// } label: {
//// Image(systemName: "minus.circle")
//// }
//// }
//// var add: some View {
//// Button {
//// if emojiCount < emojis.count{
//// emojiCount += 1
//// }
//// } label: {
//// Image(systemName: "plus.circle")
////
//// }
//// }
//
//
//}
//
////CardView单个卡片视图,@State重建视图,指针不变;content是卡片的内容
////CardView正在显示这张卡片,为这张卡构建一个UI
//struct CardView: View {
// //MARK:7,在这个CardView创建card并且为他建立一个body,只传一张卡
// //MARK:7-1,强烈推荐:这里没有将整个model传给他,只传model的一部分,当你构建一个view时,只传人他完成工作所需的最低限度的model,CardView的工作是为我构建一个显示卡片的UI在model中,只把卡传给他。这里用let,因为卡片视图不会改变
// //@State在视图中非常罕见,所以你视图中不会有很多变量。
// let card: MemoryGame<String>.Card
// //struct中变量可以没有初始值
//// var content: String
//// @State var isFaceUp: Bool = true
//
// var body: some View {
// ZStack{
// let shape = RoundedRectangle(cornerRadius: 20)
// //MARK:10,if isFaceUp 改成if card.isFaceUp,Text(card.content)
// if card.isFaceUp {
// //stroke边缘 fill默认填充
// shape.fill().foregroundColor(/*@START_MENU_TOKEN@*/.white/*@END_MENU_TOKEN@*/)
// //strokeBorder笔划边框
// shape.strokeBorder(lineWidth: 3)
// Text(card.content).font(.largeTitle)
// } else {
// shape.fill()
// }
//
// }
////反转卡片
//// .onTapGesture {
//// isFaceUp = !isFaceUp
//// }
//
// }
//}
//
//struct ContentView_Previews: PreviewProvider {
// static var previews: some View {
// //MARK:4,给用到ContentView地方的变量赋值,game是个指针,创造一个游戏,把它传递给viewModel的明暗两个视图
// let game = EmojiMemoryGame()
// //ContentView()
// ContentView(viewModel: game)
// .preferredColorScheme(.dark)
// ContentView(viewModel: game)
// .preferredColorScheme(.light)
//
//
//
// }
//}
MemoryGame.swift
//Foundation是个数组,字符串,字典组成的基础包,不是按钮或视图api
//前变量后值或类型
import Foundation
struct MemoryGame<Content> {
private(set) var cards: Array<Card>
mutating func choose(_ card: Card){
let chosenIndex = index(of: card)
cards[chosenIndex].isFaceUp.toggle()
//print("chosenCard = \(chosenCard)")
print("\(cards)")
}
func index(of card: Card) -> Int {
for index in 0..<cards.count {
if cards[index].id == card.id {
return index
}
}
return 0
}
init(numberOfPairOfCards: Int,createCardContent:(Int) ->Content ) {
cards = Array<Card>()
for pairIndex in 0..<numberOfPairOfCards{
let content: Content = createCardContent(pairIndex)
cards.append(Card(content: content, id: pairIndex*2))
cards.append(Card(content: content, id: pairIndex*2+1))
}
}
struct Card: Identifiable {
var isFaceUp: Bool = true
var isMatched: Bool = false
var content: Content
var id: Int
}
}
//
//struct MemoryGame<CardContent> {
// private(set) var cards: Array<Card>
//
// //MARK:19 mutating放在func前面告诉全世界调用这个函数将改变整个struct,所以这个函数只能在MemoryGame作为变量var的地方调用
// //MARK:21,所以说这个函数可变,可以改变整个struct
// mutating func choose(_ card: Card) {
//
// //card.isFaceUp.toggle()//函数里的参数是常数,会报错
// //MARK:13,编写一个名为index(of: )的函数,这张卡片的索引的函数,在array中找到这张卡片
// let chosenIndex = index(of: card)
// //MARK:15,
//// var chosenCard = cards[chosenIndex]
//// chosenCard.isFaceUp.toggle()
// //MARK:18
// cards[chosenIndex].isFaceUp.toggle()
// //MARK:16,
// //print("chosenCard = \(chosenCard)")
// print("\(cards)")
// }
//
// //MARK:14, 实现上面的函数index(of: card),卡片的索引,这是该数组的索引
// func index(of card: Card) -> Int {
// // MARK:17,如果卡片索引的ID==卡片ID,返回int类型的index
// for index in 0..<cards.count {
// if cards[index].id == card.id {
// return index
// }
// }
// return 0 //bogus!
// }
//
// init(numberOfPairOfCards: Int, creatCardContent:(Int) -> CardContent) {
// cards = Array<Card>()
// for pairIndex in 0..<numberOfPairOfCards{
// let content: CardContent = creatCardContent(pairIndex)
// //MARK:11,给卡片组加ID参数
// cards.append(Card(content: content, id: pairIndex*2))
// cards.append(Card(content: content, id: pairIndex*2+1))
// }
// }
//
// //MARK:10-1,让卡片可识别Identifiable
// struct Card:Identifiable {
// var isFaceUp: Bool = false
// var isMatched: Bool = false
// var content: CardContent
// var id: Int
// }
//
//}
//----------------------------------------------------------
//struct MemoryGame<CardContent> {
// private(set) var cards: Array<Card>
//
// //mutating 这个函数可以改变整个struct
// mutating func choose(_ card: Card){
// //card.isFaceUp.toggle()
// //编写一个index(of:)的函数
// let chosenIndex = index(of: card)
// cards[chosenIndex].isFaceUp.toggle()
// print("\(cards)")
// }
//
// func index(of card: Card) -> Int {
// for index in 0..<cards.count {
// if cards[index].id == card.id {
// return index
// }
// }
// return 0
// }
//
// init(numberOfPairOfCards: Int, createCardContent:(Int) ->CardContent) {
// cards = Array<MemoryGame.Card>()
// for pairIndex in 0..<numberOfPairOfCards{
//
// let content: CardContent = createCardContent(pairIndex)
// cards.append(Card(content: content,id: pairIndex*2))
// cards.append(Card(content: content,id: pairIndex*2+1))
// }
// }
//
// struct Card: Identifiable {
// var isFaceUp: Bool = true
// var isMatched: Bool = false
// var content: CardContent
// var id: Int
// }
//}
//
//
//
////model是个简单的MemoryGame模型
////我们使用范型<CardContent> 必须向全世界宣布,放在<>里,
////当有人用我们的MemoryGame时,他们将不得不告诉我们这个范型是什么,不能一直don’t care,我们不关心,但谁用谁关心。我们做表情游戏时,它将成为一个String
//struct MemoryGame<CardContent> {
// //有卡片:放在数组里<卡片阵列>
// //Model保护其卡片免受ViewModel的影响,只看不动
// //这个cards:Array<Card>在使用前要先用下面的init进行初始化
// //var cards: Array<Card> 由Card类型组成的数组
// private(set) var cards: Array<Card>
//
// //有选择卡片
// func choose(_ card: Card) {
//
// }
//
// //init初始化未设置的变量,numberOfPairsOfCards初始化的时候你得知道有几对卡,一共有10对卡,创建第一对卡的内容,返回第一对卡的内容。
// init(numberOfPairsOfCards: Int, createCardContent: (Int) -> CardContent) {
// //创建空的<卡片阵列>,初始化了一个空的卡片数组,所以上面能用
// cards = Array<Card>()
// //add numberOfPairsOfCards x 2 cards to cards array,卡的对数乘以2就是有多少卡,(for 第几对 in 对数)
// for pairIndex in 0..<numberOfPairsOfCards {
// //制作一对卡,cards.append(Card(isFaceUp: false, isMatched: false, content: <#T##CardContent#>)),因为struct Card中有默认值,所以isFaceUp: false, isMatched: false不需要了
// //创建一个函数createCardContent,把pairIndex传给它,createCardContent这个函数从哪来?
// //let content: CardContent = createCardContent(pairIndex) //类型推断,干掉CardContent
// //let 卡的内容 = 创建卡的内容(第几对)
// //var content: CardContent = ... 创建一个变量 它是CardContent类型的。让createCardContent这个函数来创建卡片内容
// //createCardContent: (Int) -> CardContent 传入要创建第几对卡的数组,比如1,返回第一对卡的内容,把函数直接写到参数里。
// let content = createCardContent(pairIndex)
// //给每张卡设置内容。(content: CardContent),CardContent是个范型,不确定它是什么类型,(content: content)前面是参数,后面值。
// cards.append(Card(content: content))
// cards.append(Card(content: content))
// }
// }
//
// //MemoryGame.Card,卡片有哪些属性
// struct Card {
// var isFaceUp: Bool = false //是否正面朝上
// var isMatched: Bool = false //是否匹配
// var content: CardContent //卡片上有什么,String?可以做的更好,未来有任意图像而不是表情符号;CardContent范性--随便啥:don't care
// }
//}
EmojiMemoryGame.swift
// #ViewModel
//虽然ViewModel是我们UI的一部分,但它不是swiftUI视图,是用户界面的一部分
//ViewModel是个中介,在Model和View之间;它需要和Model建立连接,实际上ViewModel会重建自己的Model。有时候Model是数据库
//你玩游戏它就在那里,如果ViewModel消失了,然后游戏结束了。
//因为很多时候ViewModel创建自己的Model,我们会经常说ViewModel是程序中的truth。Model是app的truth,如果ViewModel在创建自己的模型,则本质上ViewModel是truth
//添加三个关键字让我们的UI变成完全反应式的UI,我们model的任何更改都会更新匹配UI
//ObservableObject,viewmodel向世界发布something changed,model变化了,view知道viewmodel改变时重绘,@ObservedObject,
import SwiftUI
class EmojiMemoryGame: ObservableObject {
static var emojis = ["🚝","🏍","🚖","🚀","🚕","🚎","🚑","🚜","🦽","🚔","🛸","🚂","🚞","🚨"]
static func makeMemoryGame() -> MemoryGame<String> {
MemoryGame<String>(numberOfPairOfCards: 4) { pairIndex in emojis[pairIndex]}
}
@Published private var model: MemoryGame<String> = makeMemoryGame()
var cards: Array<MemoryGame<String>.Card> {
return model.cards
}
//MARK: - Intent(s)
func choose(_ card: MemoryGame<String>.Card) {
model.choose(card)
}
}
//MARK:22,ObservableObject 向全世界发布“有所改变”
//class EmojiMemoryGame: ObservableObject {
// static var emojis = ["🚝","🏍","🚖","🚀","🚕","🚎","🚑","🚜","🦽","🚔","🛸","🚂","🚞","🚨"]
//
// static func makeMemoryGame() -> MemoryGame<String> {
// MemoryGame<String>(numberOfPairOfCards: 4 ){ pairIndex in emojis[pairIndex]}
// }
//
// //MARK:24,@Published 无论model发生任何变化,它都会执行objectWillChange.send()
// //倾向于让model完全私有,通过var和func将其暴露给view
// //MARK:20,如果这里为let model 则下面model.choose(card)就不能用,这就是swift的写时复制的方式,它强制事物的可变性
// @Published private var model: MemoryGame<String> = makeMemoryGame()
//
// var cards: Array<MemoryGame<String>.Card> {
// model.cards
// }
//
// //MARK:12-1,实现viewModel.choose(card)
// func choose(_ card: MemoryGame<String>.Card) {
// //MARK:23,向全世界发布objectWillChange 在model前面加上@Published
// //objectWillChange.send()
// //13,
// //MARK:25,choose是mutating func,所以swiftUI会自动执行objectWillChange.send()
// model.choose(card)
// }
//}
//-----------------------------------------------------------------
//ObservableObject向全世界宣布有东西改变了!
//class EmojiMemoryGame: ObservableObject {
// static var emojis = ["🚝","🏍","🚖","🚀","🚕","🚎","🚑","🚜","🦽","🚔","🛸","🚂","🚞","🚨"]
//
// static func makeMemoryGame() -> MemoryGame<String> {
// MemoryGame<String>(numberOfPairOfCards: 4) { pairIndex in emojis[pairIndex] }
// }
//
// //让model完全私密,通过var func暴露给view
// //在任何变量前加关键字@Published,无论model发生任何变化,它都会自动执行objectWillChange.send()
// @Published private var model: MemoryGame<String> = makeMemoryGame()
//
// //viewModel.cards
// var cards: Array<MemoryGame<String>.Card> {
// model.cards
// }
//
// // MARK: - Intent(s)
// //用户的意图,这个Intent是我们必须勾在UI上的东西,我们的UI会表达这个Intent
// //choose是个mutating func,swiftUI知道这改变了它,会自动执行objectWillChange.send()
// func choose(_ card: MemoryGame<String>.Card) {
// //向世界发布这个objectWillChange,model changed
// //objectWillChange.send()
// model.choose(card)
// }
//}
//
//class EmojiMemoryGame {
//
// let emojis = ["🚝","🏍","🚖","🚀","🚕","🚎","🚑","🚜","🦽","🚔","🛸","🚂","🚞","🚨"]
//
// private var model: MemoryGame<String> =
// MemoryGame<String>(numberOfPairsOfCards: 4) { pairIndex in
// "🚙" }
//
// var cards: Array<MemoryGame<String>.Card> {
// return model.cards
// }
//}
//createCardContent: (Int) -> CardContent,createCardContent: (Int) -> String 接收int返回string
//func makeCardContent(index: Int) -> String {
// return "🎳"
//}
//类可以继承
//class EmojiMemoryGame {
//
// //从视图中获取表情符号,static意味着基本上是全局的,emojis相对于EmojiMemoryGame是全局的
// static let emojis = ["🚝","🏍","🚖","🚀","🚕","🚎","🚑","🚜","🦽","🚔","🛸","🚂","🚞","🚨"]
//
// //创建记忆游戏 类型函数
// static func createMemoryGame() -> MemoryGame<String> {
// return MemoryGame<String>(numberOfPairsOfCards: 4) { pairIndex in
// emojis[pairIndex]
// }
// }
//
//
// private let model: MemoryGame<String> = createMemoryGame()
//
// //我们的模型是个结构体struct,所以我们要创建一个名为model的变量var,它是MemoryGame类型的
// //因为MemoryGame<CardContent>是范型的,所以我们要指明CardContent是什么类型,这是一个EmojiMemoryGame,所以它是string,表情符号是字符串
// //ViewModel是Model的看门人,ViewModel保护model免受不良行为的影响,谁有权访问ViewModel;我们的做法是制作私有model
// //private意味着只有ViewModel代码本身可以看到Model,它保护Model,阻止任何访问修改;private是访问控制的一部分。
// //有时候完全私有有点太严格了,例如在我们的记忆游戏中人们需要看到这些卡片,否则我们的视图将怎么绘制卡片?
// //所以还有另外一个private(set)私有,它告诉swift其他类和struct可以查看model,但无法改变它。它不能调用func choose,因为那会改变MemoryGame,最特别是他们无法读取Card并更改isFaceup、isMatched等。
// //private var model: MemoryGame<String>
// //class中所以的变量都要有值,MemoryGame<String>(numberOfPairsOfCards: 4)设置默认4张卡
//// private var model: MemoryGame<String> = MemoryGame<String>(numberOfPairsOfCards: 4) { pairIndex in
//// //pairIndex 第几对表情
//// //{ pairIndex in emojis[pairIndex] } = {99 in 表情数组[99]}
//// //不能使用带有属性初始化的实例成员,属性初始值设定项在“self”可用前运行。
//// //实例成员意味着在EmojiMemoryGame中的任何函数或变量,每当我建立一个EmojiMemoryGame,我会得到一个emojis和一个Model,不能像emojis使用在初始值设定项中,属性初始化器?property这个词只是指类或结构中的变量或常量属性,使用等号的属性。
//// //私有属性只在本文件内可用访问。
//// //model和emojis这两个属性初始化顺序是随机的,你在后面这个函数里emojis[pairIndex]使用它 它可能还没初始化。可以用init来初始化emojis
//// emojis[pairIndex]
////
//// }
//
//
// //1,model完全私有则必须有自己的cards,model.cards
// //2,views可以调用ViewModeel自己的var,拿到cards。
// //Model中的cards是个struct结构体,他们是一组卡片,我们传递struct时我们复制他们
// var cards: Array<MemoryGame<String>.Card> {
// return model.cards
// }
//}
网友评论