IOS开发
- Lecture3
- MVVM
- Varieties of Types
- struct & class
- don't care - generics
- Function
- Closures
- private(set)
- for
- 函数作为参数传给函数
- 初始化顺序
- Lecture4
- 修改代码
- View界面预览代码修改
- 构建View-ViewMode点击事件
- 让bool值反转
- internal external name
- print("\( )")
- struct 复制=
- struct本身不可更改
- 让ViewModel能够通知View更改
- MVVM下的成品代码
- enum
- like a struct
- associated data
- set the value
- check an enum's state
- methods and properties on an enum
- get all the cases
- switch
- Optional
- nil
- if let safely-gotten associated value
- ??
- Optional chaining
- 简化firstIndex
- if let and分隔符`,`
- isMatched的卡片处理
- 课程最终代码
Lecture3
MVVM
Model-View-ViewModel
Model : UI Independent, Data + logic, “The Truth”
View : Reflects the Model, Stateless, Declared
ViewModel: Binds View to Model, Interpreter, Gatekeeper
Varieties of Types
struct & class
Both can
1.store vars
2.computed vars inline function { }
3.let vars
4.functions
func multiply(operand: Int, by: Int) -> Int{
return operand * by
}
mutiply(oprand: 5, by: 6)
//two labels
// "_" indicates none external
func multiply(_ operand: Int, by otherOperand: Int) -> Int{
return operand * otherOperand
}
multiply(5, by: 6)
5.initializers
struct RoundedRactangle {
init(cornerRadius: CGFloat){
//initialize that rectangle with that cornerRadius
}
init(cornerSize: CGSize){
//initialize this rectangle with that cornerSize
}
}
Different
struct | class |
---|---|
value type | reference type |
Copied when passed or assigned | Passed around via pointers |
Copy on write | Automatically reference counted |
Functional programming | Object-oriented programming |
No inheritance | Inheritance(single) |
“Free” init initializes ALL vars | “Free” init initializes NO vars |
Mutability must be explicitly stated | Always mutable |
Everything you’ve seen so far is a struct (except View which is a protocol) | The ViewModel in MVVM is always a class(also, UIKit is class-based) |
don’t care - generics
Swift is a strongly-typed language
We use a “don’t care” type (we call this feature “generics”)
example: Array
struct Array<Element>{
...
func append(_ element: Element) {...}
}
//Element is a "don't care" type
use like this:
var a = Array<Int>()
a.append(5)
a.append(20)
Function
(Int, Int) -> Bool
(Double) -> Void
() -> Array<String>
() -> Void
var foo: (Double) -> Void
//foo's type: function that takes a Double, returns nothing
func doSomething(what: () -> Bool)
//what's type: function, takes nothing, returns Bool
var operation: (double) ->Double
func square(operand: Double) -> Double {
return operand * operand
}
operation = square
let result = operation(4)
//result will equal to 16
Closures
inline function
private(set)
other class and struct can look at the model, but can’t change it
for
for pairIndex in 0..<numberOfPairsOfCards {
}
函数作为参数传给函数
init(numberOfPairsOfCards: Int, createCardContent: (Int) -> CardContent) {
cards = Array<Card>()
// add numberOfPairsOfCards x 2 cards to cards array
for pairIndex in 0..<numberOfPairsOfCards {
let content: CardContent = createCardContent(pairIndex)
cards.append(Card(content: content))
cards.append(Card(content: content))
}
}
初始化顺序
这里都使用var xxx = xxx
,在真正运行的时候并不知道谁先运行,所以这里的使用emojis[Index]
将会产生问题
class EmojiMemoryGame{
var emojis = ["🛺","🚑","🚎","🚃","🚜","🛩️","🚀","🚁"]
private var model: MemoryGame<String> = MemoryGame<String>(numberOfPairsOfCards: 4) { pairIndex in
emojis[pairIndex]
}
}
修改为static类型
class EmojiMemoryGame{
static var emojis = ["🛺","🚑","🚎","🚃","🚜","🛩️","🚀","🚁"]
private var model: MemoryGame<String> = MemoryGame<String>(numberOfPairsOfCards: 4) { pairIndex in
emojis[pairIndex]
}
}
Lecture4
修改代码
// View
import SwiftUI
struct ContentView: View {
let viewModel: EmojiMemoryGame
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(.pink)
}
.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:10)
Text(card.content).font(.largeTitle)
}else{
shape.fill()
}
}
}
}
// ViewModel
// ViewModel制定了String类
import SwiftUI
class EmojiMemoryGame{
static let emojis = ["🛺","🚑","🚎","🚃","🚜","🛩️","🚀","🚁","⚽️","🏀","🏈","⚾️","🥎","🎾","🏉","🎱","🛼","🥊","🍎","🍇","🍐","🍌","🍋","🍊","🍉","🍓","🫐","🍒","🍈","🍑","🥦","🍍","🥥","🥝","🍆","🥑"]
static func createMemoryGame() -> MemoryGame<String> {
MemoryGame<String>(numberOfPairsOfCards: 3) { pairIndex in
emojis[pairIndex]
}
}
private var model: MemoryGame<String> = createMemoryGame()
var cards: Array<MemoryGame<String>.Card> {
return model.cards
}
//MARK: - Intent(s)
func choose(_ card:MemoryGame<String>.Card){
model.choose(card)
}
}
// Model
// Model中提供一个Card类,但不指定类型
import Foundation
struct MemoryGame<CardContent> {
private(set) var cards: Array<Card>
func choose(_ card: Card){
print("hello")
}
init(numberOfPairsOfCards: Int, createCardContent: (Int) -> CardContent) {
cards = Array<Card>()
// add numberOfPairsOfCards x 2 cards to cards array
for pairIndex in 0..<numberOfPairsOfCards {
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 = false
var isMatched: Bool = false
var content: CardContent
var id: Int
}
}
//main
//主程序创建一个ViewModel和一个View
import SwiftUI
@main
struct Memorize2App: App {
let game = EmojiMemoryGame()
var body: some Scene {
WindowGroup {
ContentView(viewModel: game)
}
}
}
View界面预览代码修改
因为ContentView的修改,需要提供ViewModel,故在预览界面也需要更改
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let game = EmojiMemoryGame()
ContentView(viewModel: game)
.preferredColorScheme(.light)
.previewDevice("iPhone 11 Pro")
ContentView(viewModel: game)
.previewInterfaceOrientation(.landscapeRight)
.preferredColorScheme(.light)
.previewDevice("iPhone 11 Pro")
ContentView(viewModel: game)
.preferredColorScheme(.dark)
.previewDevice("iPhone 11 Pro")
}
}
构建View-ViewMode点击事件
// View
.onTapGesture {
viewModel.choose(card)
}
// ViewModel
//MARK: - Intent(s)
func choose(_ card:MemoryGame<String>.Card){
model.choose(card)
}
func choose(_ card: Card){
print("hello")
}
让bool值反转
card.isFaceUp.toggle()
internal external name
let chosenIndex = index(of: card)
//first is external name
//second is internal name
//third is struct
func index(of card: Card) -> Int {
for index in 0..<cards.count {
if cards[index].id == card.id {
return index
}
}
return 0
}
print(“( )”)
print("chosenCard = \(chosenCard)")
struct Card: Identifiable{
var isFaceUp: Bool = true
var isMatched: Bool = false
var content: CardContent
var id: Int
}
print会将可能的一切转换为String打印出来,只要加上\( )
struct 复制=
struct在复制的时候是完整的复制,既复制后新的与原来的无关了就
struct本身不可更改
我们加上mutating
//this function can change this struct
mutating func choose(_ card: Card) {
let chosenIndex = index(of: card)
cards[chosenIndex].isFaceUp.toggle()
print("\(cards)")
}
此时数据已经发生改变了,但UI不会变化
让ViewModel能够通知View更改
类加上ObservableObject,同时在需要通知的地方加上objectWillChange.send()
import SwiftUI
class EmojiMemoryGame: ObservableObject{
static let emojis = ["🛺","🚑","🚎","🚃","🚜","🛩️","🚀","🚁","⚽️","🏀","🏈","⚾️","🥎","🎾","🏉","🥝","🍆","🥑"]
static func createMemoryGame() -> MemoryGame<String> {
MemoryGame<String>(numberOfPairsOfCards: 4) { pairIndex in
emojis[pairIndex]
}
}
private var model: MemoryGame<String> = createMemoryGame()
var cards: Array<MemoryGame<String>.Card> {
return model.cards
}
//MARK: - Intent(s)
func choose(_ card:MemoryGame<String>.Card){
objectWillChange.send()
model.choose(card)
}
}
或者是将,需要更改后就要通知的变量加上@Published
import SwiftUI
class EmojiMemoryGame: ObservableObject{
static let emojis = ["🛺","🚑","🚎","🚃","🚜","🛩️","🚀","🚁","⚽️","🏀","🏈","⚾️","🥎","🎾","🏉","🎱","🛼","🥊","🍎","🍇","🍐","🍌","🍋","🍊","🍉","🍓","🫐","🍒","🍈","🍑","🥦","🍍","🥥","🥝","🍆","🥑"]
static func createMemoryGame() -> MemoryGame<String> {
MemoryGame<String>(numberOfPairsOfCards: 4) { pairIndex in
emojis[pairIndex]
}
}
@Published private var model: MemoryGame<String> = createMemoryGame()
var cards: Array<MemoryGame<String>.Card> {
return model.cards
}
//MARK: - Intent(s)
func choose(_ card:MemoryGame<String>.Card){
model.choose(card)
}
}
此时让View观察着ViewModel,增添上@ObservedObject
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: EmojiMemoryGame
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(.pink)
}
.padding(.horizontal)
}
}
MVVM下的成品代码
//
// Memorize2App.swift
// Memorize2
//
// Created by zhj12399 on 2023/1/6.
//
import SwiftUI
@main
struct Memorize2App: App {
let game = EmojiMemoryGame()
var body: some Scene {
WindowGroup {
ContentView(viewModel: game)
}
}
}
//
// ContentView.swift
// Memorize2
//
// Created by zhj12399 on 2023/1/6.
// View
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: EmojiMemoryGame
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(.pink)
}
.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:10)
Text(card.content).font(.largeTitle)
}else{
shape.fill()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let game = EmojiMemoryGame()
ContentView(viewModel: game)
.preferredColorScheme(.light)
.previewDevice("iPhone 11 Pro")
ContentView(viewModel: game)
.previewInterfaceOrientation(.landscapeRight)
.preferredColorScheme(.light)
.previewDevice("iPhone 11 Pro")
ContentView(viewModel: game)
.preferredColorScheme(.dark)
.previewDevice("iPhone 11 Pro")
}
}
//
// EmojiMemoryGame.swift
// Memorize2
//
// Created by zhj12399 on 2023/1/6.
// ViewModel
import SwiftUI
class EmojiMemoryGame: ObservableObject{
static let emojis = ["🛺","🚑","🚎","🚃","🚜","🛩️","🚀","🚁","⚽️","🏀","🏈","⚾️","🥎","🎾","🏉","🎱","🛼","🥊","🍎","🍇","🍐","🍌","🍋","🍊","🍉","🍓","🫐","🍒","🍈","🍑","🥦","🍍","🥥","🥝","🍆","🥑"]
static func createMemoryGame() -> MemoryGame<String> {
MemoryGame<String>(numberOfPairsOfCards: 4) { pairIndex in
emojis[pairIndex]
}
}
@Published private var model: MemoryGame<String> = createMemoryGame()
var cards: Array<MemoryGame<String>.Card> {
return model.cards
}
//MARK: - Intent(s)
func choose(_ card:MemoryGame<String>.Card){
model.choose(card)
}
}
//
// MemoryGame.swift
// Memorize2
//
// Created by zhj12399 on 2023/1/6.
// Model
import Foundation
struct MemoryGame<CardContent> {
private(set) var cards: Array<Card>
mutating func choose(_ card: Card) {
let chosenIndex = index(of: card)
cards[chosenIndex].isFaceUp.toggle()
}
func index(of card: Card) -> Int {
for index in 0..<cards.count {
if cards[index].id == card.id {
return index
}
}
return 0
}
init(numberOfPairsOfCards: Int, createCardContent: (Int) -> CardContent) {
cards = Array<Card>()
// add numberOfPairsOfCards x 2 cards to cards array
for pairIndex in 0..<numberOfPairsOfCards {
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
}
}
enum
like a struct
a value type, it is copied as it is passed around
enum FastFoodMenuItem{
case hambuger //state
case fries
case drink
case cookie
}
associated data
each state can have its own “associated data”
enum FastFoodMenuItem{
case hambuger(numberOfPatties: Int)
case fries(size: FryOrderSize)
case drink(String, ounces: Int)
case cookie
}
// so FryOrderSize would be an enum too
enum FryOderSize{
case large
case small
}
set the value
let menuItem: FastFoodMenuItem = FastFoodMenuItem.hamburger(patties: 2)
var otherItem: FastFoodMenuItem = FastFoodMenuItem.cookie
//或者简写
let menuItem = FastFoodMenuItem.hamburger(patties: 2)
var otherItem: FastFoodMenuItem = .cookie
check an enum’s state
var menuItem = FastFoodMenuItem.hambuger(patties: 2)
switch menuItem{
case FastFoodMenuItem.hamburger: print("burger")
case FastFoodMenuItem.fries: print("fries")
case FastFoodMenuItem.drink: print("drink")
case FastFoodMenuItem.cookie: print("cookie")
}
// 简化为
switch menuItem{
case .hamburger: print("burger")
case .fries: print("fries")
case .drink: break
case .cookie: print("cookie")
default: print("other")
}
Associated data is accessed through a switch statement using this let syntax
var menuItem = FastFoodMenuItem.drink("Coke", ounces: 32)
switch menuItem {
case hamburger(let pattyCount): print("a burger with \(pattyCount) patties!")
case .fries(let size): print("a \(size) order of fries")
case .drink(let brand, let ounces): print("a \(ounces)oz \(brand)")
case .cookie: print("a cookie!")
}
methods and properties on an enum
an enum can have methods(and computed properties) but no stored properties
Notice: the use of _
if we don’t care about that piece of associated data
enum FastFoodMenuItem {
case hamburger(numberOfPatties: Int)
case fries(size: FryOrderSize)
case drink(String, ounces: Int)
case cookie
func isInludedInSpecialOrder(number: Int) -> Bool {
switch self {
case .hamburger(let pattyCount): return pattyCount == number
case .fries, .cookie: return true// a drink and cookie in every special order
case .drink(_, let ounces): return ounces == 16//&16oz drink of any kind
}
}
var calories: Int{ }
}
get all the cases
enum TeslaModel: CaseIterable {
case X
case S
case Three
case Y
}
//Now this enum will have a static var allCases that you can iterate over
for model in TeslaModel.allCases {
reportSaledNumbers(for: model)
}
func reportSalesNumbers(for model:TeslaModel) {
switch model {...}
}
switch
let s: String = "hello"
switch s {
case "goodbye": ...
case "hello": ...
default: ...
}
multiple lines allowed
var menuItem = FastFoodMenuItem.fries(sizes: FryOrderSize.large)
switch menuItem{
case .hamburger: print("burger")
case .fries:
print("yummy")
print("fries")
case .drink:
print("drink")
case .cookie: print("cookie")
}
Optional
It’s just an enum
enum Optional<T>{
case none
case some(T)
}
we have a value that can sometimes be “not set” or “unspecified” or “undertermined”
nil
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
You can then assign it the value nil (Optional.none)
Or you can assign it something of the type T
can access the associated value either by force(with !)
enum Optional<T> {
case none
case some(T)
}
let hello: String? = ...
print(hello!)
switch hello {
case .none: //raise an exception(crash)
case .some(let data): print(data)
}
if let safely-gotten associated value
if let safehello = hello {
print(safehello)
} else {
//do something else
}
// 就是下面这段程序的含义
switch hello {
case .none: { //do something else }
case .some(let data): print(data)
}
??
enum Optional<T> {
case none
case some(T)
}
let x: String? = ...
let y = x ?? "foo"
// 就是下面这段程序的含义
switch x {
case .none: y = "foo"
case .some(let data): y = data
}
Optional chaining
enum Optional<T> {
case none
case some(T)
}
let x: String? = ...
let y = x?.foo()?.bar?.z
switch x {
case .none: y = nil
case .some(let xval)):
switch xval.foo() {
case .none: y = nil
case .some(let xfooval):
switch xfooval.bar {
case .none: y = nil
case .some(let xfbval): y = xfbval.z
}
}
}
简化firstIndex
mutating func choose(_ card: Card) {
if let chosenIndex = cards.firstIndex(where: {$0.id == card.id}) {
cards[chosenIndex].isFaceUp.toggle()
}
}
if let and分隔符,
if let chosenIndex = cards.firstIndex(where: { $0.id == card.id}) , !cards[chosenIndex].isFaceUp{ //如果不是nil才执行
isMatched的卡片处理
课程最终代码
// Memorize2App.swift
// Memorize2
//
// Created by zhj12399 on 2023/1/6.
// Main
import SwiftUI
@main
struct Memorize2App: App {
let game = EmojiMemoryGame()
var body: some Scene {
WindowGroup {
ContentView(viewModel: game)
}
}
}
// ContentView.swift
// Memorize2
//
// Created by zhj12399 on 2023/1/6.
// View
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: EmojiMemoryGame
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(.pink)
}
.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:10)
Text(card.content).font(.largeTitle)
} else if card.isMatched{
shape.opacity(0) // 不透明度设置为0,相当于完全透明
} else {
shape.fill()
}
}
}
}
// EmojiMemoryGame.swift
// Memorize2
//
// Created by zhj12399 on 2023/1/6.
// ViewModel
import SwiftUI
class EmojiMemoryGame: ObservableObject{
static let emojis = ["🛺","🚑","🚎","🚃","🚜","🛩️","🚀","🚁","⚽️","🏀","🏈","⚾️","🥎","🎾","🏉","🎱","🛼","🥊"]
static func createMemoryGame() -> MemoryGame<String> {
MemoryGame<String>(numberOfPairsOfCards: 8) { pairIndex in
emojis[pairIndex]
}
}
@Published private var model: MemoryGame<String> = createMemoryGame()
var cards: Array<MemoryGame<String>.Card> {
return model.cards
}
//MARK: - Intent(s)
func choose(_ card:MemoryGame<String>.Card){
model.choose(card)
}
}
// MemoryGame.swift
// Memorize2
//
// Created by zhj12399 on 2023/1/6.
// Model
import Foundation
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
{ //如果不是nil才执行
if let potentialMatchIndex = indexOfTheOneAndOnlyFaceUpCard { //如果indexOfTheOneAndOnlyFaceUpCard为nil则去下面的else
if cards[chosenIndex].content == cards[potentialMatchIndex].content {
cards[chosenIndex].isMatched = true
cards[potentialMatchIndex].isMatched = true
}
indexOfTheOneAndOnlyFaceUpCard = nil
} else { //此处指之前已经选了两个卡值已经被设置为nil,或一张卡都没选
for index in cards.indices {
cards[index].isFaceUp = false
}
indexOfTheOneAndOnlyFaceUpCard = chosenIndex
}
//执行完逻辑后在把选的卡翻面
cards[chosenIndex].isFaceUp.toggle()
}
}
init(numberOfPairsOfCards: Int, createCardContent: (Int) -> CardContent) {
cards = Array<Card>()
// add numberOfPairsOfCards x 2 cards to cards array
for pairIndex in 0..<numberOfPairsOfCards {
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 = false
var isMatched: Bool = false
var content: CardContent
var id: Int
}
}