2021-09-18

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.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.. 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...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.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 {
    private(set) var cards: Array
    
    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..Content ) {
        cards = Array()
        for pairIndex in 0.. {
//    private(set) var cards: Array
//
//    //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.. CardContent) {
//        cards = Array()
//        for pairIndex in 0.. {
//    private(set) var cards: Array
//
//    //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..CardContent) {
//        cards = Array()
//        for pairIndex in 0.. 必须向全世界宣布,放在<>里,
////当有人用我们的MemoryGame时,他们将不得不告诉我们这个范型是什么,不能一直don’t care,我们不关心,但谁用谁关心。我们做表情游戏时,它将成为一个String
//struct MemoryGame {
//    //有卡片:放在数组里<卡片阵列>
//    //Model保护其卡片免受ViewModel的影响,只看不动
//    //这个cards:Array在使用前要先用下面的init进行初始化
//    //var cards: Array 由Card类型组成的数组
//    private(set) var cards: Array
//
//    //有选择卡片
//    func choose(_ card: Card) {
//
//    }
//
//    //init初始化未设置的变量,numberOfPairsOfCards初始化的时候你得知道有几对卡,一共有10对卡,创建第一对卡的内容,返回第一对卡的内容。
//    init(numberOfPairsOfCards: Int, createCardContent: (Int) -> CardContent) {
//        //创建空的<卡片阵列>,初始化了一个空的卡片数组,所以上面能用
//        cards = Array()
//        //add numberOfPairsOfCards x 2 cards to cards array,卡的对数乘以2就是有多少卡,(for 第几对 in 对数)
//        for pairIndex in 0..)),因为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 {
        MemoryGame(numberOfPairOfCards: 4) { pairIndex in emojis[pairIndex]}
    }
    @Published private var model: MemoryGame = makeMemoryGame()
    
    var cards: Array.Card> {
        return model.cards
    }
    
    //MARK: - Intent(s)
    
    func choose(_ card: MemoryGame.Card) {
        model.choose(card)
    }
}

//MARK:22,ObservableObject 向全世界发布“有所改变”
//class EmojiMemoryGame: ObservableObject {
//    static var emojis = ["","","","","","","","","","","","","",""]
//
//    static func makeMemoryGame() -> MemoryGame {
//        MemoryGame(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 = makeMemoryGame()
//
//    var cards: Array.Card> {
//        model.cards
//    }
//
//    //MARK:12-1,实现viewModel.choose(card)
//    func choose(_ card: MemoryGame.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 {
//        MemoryGame(numberOfPairOfCards: 4) { pairIndex in emojis[pairIndex] }
//    }
//    
//    //让model完全私密,通过var func暴露给view
//    //在任何变量前加关键字@Published,无论model发生任何变化,它都会自动执行objectWillChange.send()
//    @Published private var model: MemoryGame = makeMemoryGame()
//
//    //viewModel.cards
//    var cards: Array.Card> {
//        model.cards
//    }
//    
//    // MARK: - Intent(s)
//    //用户的意图,这个Intent是我们必须勾在UI上的东西,我们的UI会表达这个Intent
//    //choose是个mutating func,swiftUI知道这改变了它,会自动执行objectWillChange.send()
//    func choose(_ card: MemoryGame.Card) {
//        //向世界发布这个objectWillChange,model changed
//        //objectWillChange.send()
//        model.choose(card)
//    }
//}

//
//class EmojiMemoryGame {
//
//    let emojis = ["","","","","","","","","","","","","",""]
//
//    private var model: MemoryGame =
//        MemoryGame(numberOfPairsOfCards: 4) { pairIndex in
//            "" }
//
//    var cards: Array.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 {
//       return MemoryGame(numberOfPairsOfCards: 4)  { pairIndex in
//        emojis[pairIndex]
//       }
//    }
//
//
//     private let model: MemoryGame = createMemoryGame()
//
//    //我们的模型是个结构体struct,所以我们要创建一个名为model的变量var,它是MemoryGame类型的
//    //因为MemoryGame是范型的,所以我们要指明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
//    //class中所以的变量都要有值,MemoryGame(numberOfPairsOfCards: 4)设置默认4张卡

////    private var model: MemoryGame = MemoryGame(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.Card> {
//        return model.cards
//    }
//}

你可能感兴趣的:(2021-09-18)