课程链接: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)
}
}