SwiftUI 2.0 课程笔记 Chapter 4

课程链接:https://www.bilibili.com/video/BV1q64y1d7x5?p=4

课程项目仓库:https://github.com/cnatom/MemorizeSwiftUI

枚举enum

在enum中,我们可以使用另一个enum。enum内的变量,前面的参数可以不指定标签,如下方的drink标签

enum FastFoodMenuItem{
    case hamburger(numberOfPatties: Int)
    case fries(size: FryOrderSize)
    case drink(String, ounces: Int)
    case cookie
    
}
enum FryOrderSize{
    case large
    case small
}

enum变量定义

let menuItem: FastFoodMenuItem = FastFoodMenuItem.hamburger(numberOfPatties: 2)
var otherItem: FastFoodMenuItem = .cookie // 相当于 FastFoodMenuItem.cookie

根据enum类型,执行不同的语句

var menuItem = FastFoodMenuItem.hamburger(numberOfPatties: 2)
switch menuItem {
    case FastFoodMenuItem.hamburger : print("burger")
    case FastFoodMenuItem.fries: print("freis")
    case FastFoodMenuItem.drink: print("drink")
    case FastFoodMenuItem.cookie : print("cookie")
}

或许,编译器自己知道menuItem枚举变量的类型,所以我们还可以写的更简单一点

var menuItem: FastFoodMenuItem = FastFoodMenuItem.hamburger(numberOfPatties: 2)
switch menuItem {
    case .hamburger : print("burger")
    case .fries: print("freis")
    case .drink: print("drink")
    case .cookie : print("cookie")
}

Swift也支持default,如下代码,s既不是"goodbye"也不是"hello",所以就只能执行default中的语句

let s: String = "helloworld"
switch s{
    case "goodbye": ...
    case "hello": ...
    default: ...
}

case后面还可以加多条语句,并且不需要用break关键字

var menuItem: FastFoodMenuItem = FastFoodMenuItem.hamburger(numberOfPatties: 2)
switch menuItem {
    case .hamburger : print("burger")
    case .fries: 
  				print("freis")
  				print("yummy")
    case .drink: 
  				print("drink")
    case .cookie : print("cookie")
}

还可以获得枚举类型中某一项的值

let menuItem = FastFoodMenuItem.drink("Coke", Int,ounces: 32)
switch menuItem {
    case .hamburger(let numberOfPatties):print("\(numberOfPatties)")
    case .fries(let size):print("\(size)")
    case .drink(let brand, let ounces):print("\(brand) + \(ounces)")
    case .cookie:print("a cookie!")
}

enum中还可以添加函数

enum FastFoodMenuItem{
    case hamburger(numberOfPatties: Int)
    case fries(size: FryOrderSize)
    case drink(String, Int,ounces: Int)
    case cookie
    func isDrink(number: Int) -> Bool{
        switch self {
        case .hamburger(let pattyCount): return pattyCount == number
        case .fries, .cookie : return true
        case .drink(_, let ounces): return ounces == 16
        }
    }
}

可选类型Optional

Swift 的可选(Optional)类型,用于处理值缺失的情况。其本质是一个枚举类型

enum Optional<T>{
		case none
  	case some(T)
}

// 以下各组语句等价
var hello: String?
var hello: Optional<String> = .none

var hello: String? = "hello"
var hello: Optional<String> = .some("hello")

var hello: String? = nil
var hello: Optional<String> = .none

变量后加一个叹号(hello!),意为强制解包,这种方法通常不可取。

enum Optional<T>{
		case none
  	case some(T)
}
let hello: String? = ...

// 强制解包,与下方switch基本等同
print(hello!) //如果变量为空,则抛出错误

switch hello{
  	case .none: // 抛出错误
  	case .some(let data): print(data)
}

一般使用条件语句来判断变量是否为空。

let hello: String? = ...

// 常用的写法(等价于下方的switch)
if let safehello = hello{
  	print(safehello)
} else {
  	// do something else
}

switch hello{
  	case .none: {
      	// do something else
    }
  	case .some(let data): print(data)
}

??运算符可以为变量设置"可选默认值"

let x: String? =  ...
let y = x ?? "foo"  // 如果x为空,则y="foo"

//上述语句与下方等同
switch x{
  	case .none: y = "foo"
  	case .some(let data): y = data
}

使用单个?可以实现对多个变量进行判空并取值(Optional chaining)

let x: String? = ...
let y = x?.foo()?.bar?.z  // y等于从左到右第一个不为空的变量所对应的值
//如果所有值都为空,则y也为空


更新后的MVVM实例

Model
//
//  MemoryGame.swift
//  Memorize
//
//  Created by atom on 2022/5/21.
//
import Foundation

//Model
//where CardContend: Equatable 使得CardContent之间可以使用==
struct MemoryGame<CardContend> where CardContend: Equatable{
    
    //private(set) 代表只读
    private(set) var cards: Array<Card>
    private var indexOfFaceUp: Int?  // 正面朝上的卡片索引
    
    init(numberOfPairsOfCards: Int,createCardContent: (Int) -> CardContend){
        cards = Array<Card>()
        for pairIndex in 0..<numberOfPairsOfCards{
            let content = createCardContent(pairIndex)
            cards.append(Card(content: content, id: pairIndex*2))
            cards.append(Card(content: content, id: pairIndex*2+1))
        }
    }
    
    // mutating使该函数能够改变struct的变量
    mutating func choose(_ card: Card){
        //获取选中卡片的Index,确保该卡片反面朝上(为了防止重复点击,导致该卡片不断翻转),确保该卡片没有被匹配
        if let chosenIndex = cards.firstIndex(where: {$0.id == card.id}),
            cards[chosenIndex].isFaceUp==false,
            cards[chosenIndex].isMatched==false
        {
            if let potentialMatchIndex = indexOfFaceUp {
                // 如果第二张翻开的卡片
                if cards[chosenIndex].content == cards[potentialMatchIndex].content{
                    // 和第一张翻开的卡片内容相同,则两者成功匹配
                    cards[chosenIndex].isMatched = true
                    cards[potentialMatchIndex].isMatched = true
                }
                // 匹配成功后,当前没有卡片朝上
                indexOfFaceUp = nil
            }else{
                // 如果是第一张被翻开的卡片,则之前翻到正面的卡片都要翻到反面
                for index in cards.indices {
                    cards[index].isFaceUp = false
                }
                indexOfFaceUp = chosenIndex
                
            }
            cards[chosenIndex].isFaceUp.toggle() // 将卡片翻转,toggle相当于取反 a = !a
        }
        
    }
// 获取card的第一个索引,该函数与内置函数cards.firstIndex(where:{})效果等同
//    func index(of card:Card)->Int?{
//        for index in 0..<cards.count{
//            if(cards[index].id==card.id){
//                return index
//            }
//        }
//        return nil  // 因为Int?,所以这里可以返回一个nil
//    }

    
    //MemoryGame<CardContent>.Card
    //Identifiable:使每一个Card独一无二,可以被Foreach唯一识别
    struct Card: Identifiable{
        var isFaceUp: Bool = false
        var isMatched: Bool = false
        var content : CardContend
        var id: Int
    }
}


ViewModel
import SwiftUI

//ViewModel
//ObservableObject 使得 @Published 修饰的变量改变时,会发送UI刷新公告
class EmojiMemoryGame: ObservableObject{
    //static类型变量的初始化顺序在普通var之前
    //因此static的类成员可以作为其他类成员的默认值使用
    //如 var a = EmojiMemoryGame.emojis[1]
    static var emojis = ["😀","🦴","🍎","🍇","🏀","🎽","🤣","🐶","🐱","🐭",
                         "🐹","🐰","🦊","🐵","🐢","🍎","🍋","🍉","🥩","🍳"]
    
    //创建一个Model
    static func createMemoryGame() -> MemoryGame<String> {
        return MemoryGame<String>(numberOfPairsOfCards: 3, createCardContent: {
            // 定义中:createCardContent: (Int) -> CardContend //CardContend是一个泛型
            // 因此,此处会自动识别类型,将
            // index in 识别为 (index: Int) -> String in
            index in
            return EmojiMemoryGame.emojis[index]
        } )
    }
    
    // @Published使得model每次改变时,都会发送UI刷新公告 objectWillChange.send()
    @Published private var model: MemoryGame<String> = createMemoryGame()
    
    
    var cards: Array<MemoryGame<String>.Card>{
        return model.cards
    }
    
    // MARK: - Intent(s)
    // 向Model发送从View接收到的指令
    func choose(_ card: MemoryGame<String>.Card){
        //objectWillChange.send()
        model.choose(card)
    }
}

View
import SwiftUI

struct ContentView: View {
    // @ObservedObject使View跟踪viewModel的变换并刷新UI
    @ObservedObject var viewModel: EmojiMemoryGame

    var body: some View {
        ScrollView {
            LazyVGrid(columns:[GridItem(.adaptive(minimum: 65))]){
                ForEach(viewModel.cards){ card in
                    MyCardView(card: card)
                        .aspectRatio(2/3,contentMode: .fit)
                        .onTapGesture {
                            // View向ViewModel发送改变Model的通知
                            viewModel.choose(card)
                        }
                }
            }
        }
        .foregroundColor(.blue)
        .padding()
        
    }
    
}
struct MyCardView: 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 if card.isMatched{
                // 成功匹配的卡片
                shape.opacity(0)
            }else{
                // 反面朝上
                shape.fill()
            }
        }
    }
}

// Xcode预览UI
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let game = EmojiMemoryGame()
        ContentView(viewModel: game).preferredColorScheme(.light)
        ContentView(viewModel: game).preferredColorScheme(.dark)
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值