IOS开发基础 · SwiftUI · StanfordCS193p Lecture3-4

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

structclass
value typereference type
Copied when passed or assignedPassed around via pointers
Copy on writeAutomatically reference counted
Functional programmingObject-oriented programming
No inheritanceInheritance(single)
“Free” init initializes ALL vars“Free” init initializes NO vars
Mutability must be explicitly statedAlways 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
    }
}

Lecture 4 Assignment II

Memorize

Required Tasks

1.Get the Memorize game working as demonstrated in lectures 1 through 4. Type in all the code. Do not copy/paste from anywhere.
2.If you’re starting with your assignment 1 code, remove your theme-choosing buttons and (optionally) the title of your game.
3.Add the formal concept of a “Theme” to your Model. A Theme consists of a name for the theme, a set of emoji to use, a number of pairs of cards to show, and an appropriate color to use to draw the cards.
4.At least one Theme in your game should show fewer pairs of cards than the number of emoji available in that theme.
5.If the number of pairs of emoji to show in a Theme is fewer than the number of emojis that are available in that theme, then it should not just always use the first few emoji in the theme. It must use any of the emoji in the theme. In other words, do not have any “dead emoji” in your code that can never appear in a game.
6.Never allow more than one pair of cards in a game to have the same emoji on it.
7.If a Theme mistakenly specifies to show more pairs of cards than there are emoji available, then automatically reduce the count of cards to show to match the count of available emoji.
8.Support at least 6 different themes in your game.
9.A new theme should be able to be added to your game with a single line of code.
10.Add a “New Game” button to your UI (anywhere you think is best) which begins a brand new game.
11.A new game should use a randomly chosen theme and touching the New Game button should repeatedly keep choosing a new random theme.
12.The cards in a new game should all start face down.
13.The cards in a new game should be fully shuffled. This means that they are not in any predictable order, that they are selected from any of the emojis in the theme (i.e. Required Task 5), and also that the matching pairs are not all side-by-side like they were in lecture (though they can accidentally still appear side-by-side at random).
14.Show the theme’s name in your UI. You can do this in whatever way you think looks best.
15.Keep score in your game by penalizing 1 point for every previously seen card that is involved in a mismatch and giving 2 points for every match (whether or not the cards involved have been “previously seen”). See Hints below for a more detailed explanation. The score is allowed to be negative if the user is bad at Memorize.
16.Display the score in your UI. You can do this in whatever way you think looks best.

Hints

1.Economy is still (and is always) valuable in coding.
2. Your ViewModel’s connection to its Model can consist of more than a single var model. It can be any number of vars. The “Model” is a conceptual entity, not a single struct.
3.A Theme is a completely separate thing froma MemoryGame(even though both are part of your application’s Model). You should not need to modify a single line of code in MemoryGame.swift to support themes!
4.Since a Theme is now part of your Model, it must be UI-independent. Representing a color in a UI-independent way is surprisingly nuanced (not just in Swift, but in general). We recommend, therefore, that you represent a color in your Theme as a simple String which is the name of the color, e.g. “orange”. Then let your ViewModel do one of its most important jobs which is to “interpret” the Model for the View. It can provide the View access to the current theme’s color in a UI-dependent representation (like SwiftUI’s Color struct, for example).
5.You don’t have to support every named color in the world (a dozen or so is fine), but be sure to do something sensible if your Model contains a color (e.g. “fuchsia”) that the ViewModel does not know how to interpret.
6.We’ll learn a better (though still not perfect) way to represent a color in a UI independent fashion later in the quarter.
7.Required Task 6 means that, for example, a Halloween game should never have four 🎃 cards.
8.Required Task 7 means that, for example, if a theme’s emojis are [“👻”, “🎃”, “🕷”]
and the number of pairs to show in the theme is 47, you must automatically reduce that 47 to 3.
9.You might find Array’s randomElement() function useful in this assignment (though note that this function (understandably) returns an Optional, so be prepared for that!). This is just a Hint, not a Required Task.
10.There is no requirement to use an Optional in this assignment (though you are welcome to do so if you think it would be part of a good solution).
11.You’ll very likely want to keep the static func createMemoryGame() from lecture to create a new memory game. But that function needs a little bit more information to do its job now, so you will almost certainly have to add an argument to it.
12.On the other hand, you obviously won’t need the static let emojis from last week’s lecture anymore because emojis are now obtained from whatever the current Theme is.
13.It’s quite likely that you will need to add an init() to your ViewModel. That’s because you’ll probably have one var whose initialization depends on another var. You can resolve this kind of catch-22 in an init() because, in an init(), you can control the order in which vars get initialized (whereas, when you use property initializers to initialize vars, the order is undetermined, which is why property initializers are not allowed to reference other vars).
14.The code in your ViewModel’s init() might look very, very similar to the code involved with your new game mechanism since you obviously want to start a new game in both of these places. Don’t worry if you end up with some code duplication here (you probably don’t quite know enough Swift yet to factor this code out).
15.You might well have to shuffle two different Arrays in this assignment. This is just a Hint, not a Required Task.
16.An amazing thing about “in-line functions” (actually called “closures”) in Swift is that if you declare a local variable in the scope that contains a closure, the closure can use that variable! For example, if foo below is a function that takes a function () -> Void as an argument, then …

let greetings = [Hello,Howdy,Heya].shuffled()
foo {
print(greetings) // this is legal! greetings is usable here!
}

This might come in handy for Required Task 5.
17.Make sure you think carefully about where all of your code lives (i.e. is it in the View, or in the ViewModel, or in the Model?). This assignment is mostly about MVVM, so this is very important to get right.
18.We’re not making this a Required Task (yet), but try to put the keyword private or private(set) on any variables where you think it would be appropriate.
19.A card has “already been seen” only if it has, at some point, been face up and then is turned back face down. So tracking “seen” cards is probably something you’ll want to do when you turn a card that is face up to be face down.
20.If you flipped over a 🐧 + 👻 , then flipped over a ✏ + 🏀 , then flipped over two 👻 s, your score would be 2 because you’d have scored a match (and no penalty would be incurred for the flips involving 🐧 , ✏ or 🏀 because they have not (yet) been involved in a mismatch, nor was the 👻 ever involved in a mismatch). If you then flipped over the 🐧 again + 🐼 , then flipped 🏀 + 🐧 once more, your score would drop 3 full points down to -1 overall because that 🐧 card had already been seen (on the very first flip) and subsequently was involved in two separate mismatches (scoring -1 for each mismatch) and the 🏀 was mismatched after already having been seen (-1). If you then flip 🐧 + the other 🐧 that you finally found, you’d get 2 points for a match and be back up to 1 total point.
21.The “already been seen” concept is about specific cards that have already been seen, not emoji that have been seen.

My Code

//  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{
            Text("Theme: " + EmojiMemoryGame.ThemeName).font(.largeTitle)
            
            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(EmojiMemoryGame.ThemeColor)
            
            HStack{
                VStack{
                    Text("Scores:").foregroundColor(.red)
                    Text(EmojiMemoryGame.scores.formatted())
                }
                Spacer()
                Button{
                    viewModel.changeTheme()
                } label:{
                    Text("Change Theme")
                }
            }
        }
        .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()
            }
        }
    }
}
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 var cardsNumMin = 5
    static var ThemeColor = Color.white
    static var ThemeName = "None"
    static var ThemeNum = 0
    static var Themes = [
        Theme(name: "Vehicles", emojis: ["🛺","🚑","🚎","🚃","🚜","🛩️","🚀","🚁"], color: .gray),
        Theme(name: "Balls", emojis: ["⚽️","🏀","🏈","⚾️","🥎","🎾","🏉","🎱"], color: .blue),
        Theme(name: "Friuts", emojis: ["🍎","🍇","🍐","🍌","🍋","🍊","🍉","🍓","🫐","🍒","🍈","🍑"], color: .orange),
        Theme(name: "Vegetables", emojis: ["🥦","🍍","🥬","🥒","🍆","🥑","🌶️","🥕"], color: .green),
        Theme(name: "Faces", emojis: ["😂","😀"], color: .yellow)]
    static var scores = 0
    
    static func createMemoryGame() -> MemoryGame<String> {
        ThemeNum = Int.random(in: 0..<Themes.count)
        Themes[ThemeNum].emojis.shuffle()
        ThemeColor = Themes[ThemeNum].color
        ThemeName = Themes[ThemeNum].name
        let themeCount =  chooseCardsNum(Themes[ThemeNum].emojis.count)
        
        return MemoryGame<String>(numberOfPairsOfCards: themeCount){ pairIndex in
            Themes[ThemeNum].emojis[pairIndex]
        }
    }
    
    @Published private var model: MemoryGame<String> = createMemoryGame()
    
    var cards: Array<MemoryGame<String>.Card> {
        return model.cards
    }
    
    static func chooseCardsNum(_ emojisMaxNum: Int)-> Int{
        if emojisMaxNum > 3 {
            return Int.random(in: 3..<emojisMaxNum)
        } else {
            return emojisMaxNum
        }
    }
    
    //MARK: - Intent(s)
    func choose(_ card:MemoryGame<String>.Card){
        model.choose(card)
    }
    
    func changeTheme() {
        var newThemeNum = Int.random(in: 0..<EmojiMemoryGame.Themes.count)
        while(newThemeNum == EmojiMemoryGame.ThemeNum) { //解决change之后有可能会随机到上一次一样的问题
            newThemeNum = Int.random(in: 0..<EmojiMemoryGame.Themes.count)
        }
        EmojiMemoryGame.Themes[newThemeNum].emojis.shuffle()
        EmojiMemoryGame.ThemeColor = EmojiMemoryGame.Themes[newThemeNum].color
        EmojiMemoryGame.ThemeName = EmojiMemoryGame.Themes[newThemeNum].name
        EmojiMemoryGame.ThemeNum = newThemeNum
        let newThemeCount =  EmojiMemoryGame.chooseCardsNum(EmojiMemoryGame.Themes[newThemeNum].emojis.count)
        
        model.changeTheme(numberOfPairsOfCards: newThemeCount){ pairIndex in
            EmojiMemoryGame.Themes[newThemeNum].emojis[pairIndex]
        }
        EmojiMemoryGame.scores = 0
    }
}
//  MemoryGame.swift
//  Memorize2
//
//  Created by zhj12399 on 2023/1/6.
//  Model

import Foundation
import SwiftUI

struct MemoryGame<CardContent> where CardContent: Equatable{
    private(set) var cards: Array<Card>
    
    private var indexOfTheOneAndOnlyFaceUpCard: Int?
    
    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))
        }
        cards.shuffle()
    }
    
    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
                    EmojiMemoryGame.scores += 2
                }
                else { 
                    if cards[chosenIndex].isBeenSeen == true {
                        EmojiMemoryGame.scores -= 1
                    } else {
                        cards[chosenIndex].isBeenSeen = true
                    }
                    
                    if cards[potentialMatchIndex].isBeenSeen == true {
                        EmojiMemoryGame.scores -= 1
                    } else {
                        cards[potentialMatchIndex].isBeenSeen = true
                    }
                }
                indexOfTheOneAndOnlyFaceUpCard = nil
            } else { //此处指之前已经选了两个卡值已经被设置为nil,或一张卡都没选
                for index in cards.indices {
                    cards[index].isFaceUp = false
                }
                indexOfTheOneAndOnlyFaceUpCard = chosenIndex
            }
            //执行完逻辑后在把选的卡翻面
            cards[chosenIndex].isFaceUp.toggle()
        }
    }
    
    mutating func changeTheme(numberOfPairsOfCards: Int, createCardContent: (Int) -> CardContent) {
        cards = Array<Card>()
        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))
        }
        cards.shuffle()
    }
    
    struct Card: Identifiable{
        var isFaceUp: Bool = false
        var isMatched: Bool = false
        var content: CardContent
        var id: Int
        var isBeenSeen: Bool = false
    }
}


struct Theme{
    var name: String
    var emojis: Array<String>
    var color: Color
}

在这里插入图片描述

Extra Credit

We try to make Extra Credit be opportunities to expand on what you’ve learned this week. Attempting at least some of these each week is highly recommended to get the most out of this course.
If you choose to tackle an Extra Credit item, mark it in your code with comments so your grader can find it.
1.When your code creates a Theme, allow it to default to use all the emoji available in the theme if the code that creates the Theme doesn’t want to explicitly specify how many pairs to use. This will require adding an init or two to your Theme struct.
2.Allow the creation of some Themes where the number of pairs of cards to show is not a specific number but is, instead, a random number. We’re not saying that every Theme now shows a random number of cards, just that some Themes can now be created to show a random number of cards (while others still are created to show a specific, pre-determined number of cards).
3.Supportagradientasthe“color”foratheme.Hint:fill()cantakeaGradientasits argument rather than a Color. This is a “learning to look things up in the documentation” exercise.
4.Modify the scoring system to give more points for choosing cards more quickly. For example, maybe you get max(10 - (number of seconds since last card was chosen), 1) x (the number of points you would have otherwise earned or been penalized with). (This is just an example, be creative!). You will definitely want to familiarize yourself with the Date struct.

My Extra Credit Code

每次配对成功时获得的score为max(上一次配对时间, 1)
每次配对失败时扣除3分

//  MemoryGame.swift
//  Memorize2
//
//  Created by zhj12399 on 2023/1/6.
//  Model
import Foundation
import SwiftUI

struct MemoryGame<CardContent> where CardContent: Equatable{
    private(set) var cards: Array<Card>
    
    private var indexOfTheOneAndOnlyFaceUpCard: Int?
    
    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))
        }
        cards.shuffle()
    }
    
    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

                    EmojiMemoryGame.scores += max(10 - abs(Int(EmojiMemoryGame.timeval.timeIntervalSinceNow)), 1)
                    EmojiMemoryGame.matchedNum += 1
                    
                    if EmojiMemoryGame.matchedNum == EmojiMemoryGame.Themes[EmojiMemoryGame.ThemeNum].emojisNum { //匹配成功
                        cards[chosenIndex].isFaceUp.toggle()
                        cards[indexOfTheOneAndOnlyFaceUpCard!].isFaceUp.toggle()
                    }
                    EmojiMemoryGame.timeval = Date()
                }
                else { 
                    if cards[chosenIndex].isBeenSeen == true {
                        EmojiMemoryGame.scores -= 3
                    } else {
                        cards[chosenIndex].isBeenSeen = true
                    }
                    
                    if cards[potentialMatchIndex].isBeenSeen == true {
                        EmojiMemoryGame.scores -= 3
                    } else {
                        cards[potentialMatchIndex].isBeenSeen = true
                    }
                }
                
                indexOfTheOneAndOnlyFaceUpCard = nil
            } else { //此处指之前已经选了两个卡值已经被设置为nil,或一张卡都没选
                for index in cards.indices {
                    cards[index].isFaceUp = false
                }
                indexOfTheOneAndOnlyFaceUpCard = chosenIndex
            }
            //执行完逻辑后在把选的卡翻面
            cards[chosenIndex].isFaceUp.toggle()
        }
    }
    
    mutating func changeTheme(numberOfPairsOfCards: Int, createCardContent: (Int) -> CardContent) {
        cards = Array<Card>()
        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))
        }
        cards.shuffle()
        indexOfTheOneAndOnlyFaceUpCard = nil
    }
    
    struct Card: Identifiable{
        var isFaceUp: Bool = false
        var isMatched: Bool = false
        var content: CardContent
        var id: Int
        var isBeenSeen: Bool = false
    }
}

struct Theme{
    var name: String
    var emojis: Array<String>
    var color: Color
    var emojisNum: Int = 0
    
    init(name: String, emojis: Array<String>, color: Color) {
        self.name = name
        self.emojis = emojis
        self.color = color
    }
    
    mutating func newEmojiNum() {
        if emojis.count < 3 {
            emojisNum = emojis.count
        } else {
            emojisNum = Int.random(in: 3..<emojis.count)
        }
    }
}
//  EmojiMemoryGame.swift
//  Memorize2
//
//  Created by zhj12399 on 2023/1/6.
//  ViewModel
import SwiftUI

class EmojiMemoryGame: ObservableObject{
    static var cardsNumMin = 5
    static var ThemeColor = Color.white
    static var ThemeName = "None"
    static var ThemeNum = 0
    static var Themes = [
        Theme(name: "Vehicles", emojis: ["🛺","🚑","🚎","🚃","🚜","🛩️","🚀","🚁"], color: .gray),
        Theme(name: "Balls", emojis: ["⚽️","🏀","🏈","⚾️","🥎","🎾","🏉","🎱"], color: .blue),
        Theme(name: "Friuts", emojis: ["🍎","🍇","🍐","🍌","🍋","🍊","🍉","🍓","🫐","🍒","🍈","🍑"], color: .orange),
        Theme(name: "Vegetables", emojis: ["🥦","🍍","🥬","🥒","🍆","🥑","🌶️","🥕"], color: .green),
        Theme(name: "Faces", emojis: ["😂","😀"], color: .yellow),
        Theme(name: "None", emojis: ["A"], color: .cyan)]
    
    static var scores = 0
    static var matchedNum = 0
    static var timeval = Date()
    static var timeLeft = ""
    
    static func createMemoryGame() -> MemoryGame<String> {
        ThemeNum = Int.random(in: 0..<Themes.count)
        Themes[ThemeNum].emojis.shuffle()
        ThemeColor = Themes[ThemeNum].color
        ThemeName = Themes[ThemeNum].name
        Themes[ThemeNum].newEmojiNum()
        
        timeval = Date()
        
        return MemoryGame<String>(numberOfPairsOfCards: Themes[ThemeNum].emojisNum){ pairIndex in
            Themes[ThemeNum].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)
    }
    
    func changeTheme() {
        var newThemeNum = Int.random(in: 0..<EmojiMemoryGame.Themes.count)
        while(newThemeNum == EmojiMemoryGame.ThemeNum) { //解决change之后有可能会随机到上一次一样的问题
            newThemeNum = Int.random(in: 0..<EmojiMemoryGame.Themes.count)
        }
        EmojiMemoryGame.Themes[newThemeNum].emojis.shuffle()
        EmojiMemoryGame.ThemeColor = EmojiMemoryGame.Themes[newThemeNum].color
        EmojiMemoryGame.ThemeName = EmojiMemoryGame.Themes[newThemeNum].name
        EmojiMemoryGame.ThemeNum = newThemeNum
        EmojiMemoryGame.Themes[EmojiMemoryGame.ThemeNum].newEmojiNum()
        
        model.changeTheme(numberOfPairsOfCards: EmojiMemoryGame.Themes[EmojiMemoryGame.ThemeNum].emojisNum){ pairIndex in
            EmojiMemoryGame.Themes[newThemeNum].emojis[pairIndex]
        }
        EmojiMemoryGame.scores = 0
        EmojiMemoryGame.timeval = Date()
        EmojiMemoryGame.matchedNum = 0
    }
}
//
//  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{
            Text("Theme: " + EmojiMemoryGame.ThemeName).font(.largeTitle)
            
            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(EmojiMemoryGame.ThemeColor)
            
            HStack{
                VStack{
                    Text("Scores:").foregroundColor(.red)
                    Text(EmojiMemoryGame.scores.formatted())
                }
                Spacer()
                Button{
                    viewModel.changeTheme()
                } label:{
                    Text("Change Theme")
                }
            }
        }
        .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()
            }
        }
    }
}
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")
    }
}

Lecture 3-4 课程资源

Lecture 3-4 Reading
Lecture 3-4 Assignment
Lecture 3-4 Homework

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
【课程特点】1、231节大容量课程:包含了SwiftUI的大部分知识点,详细讲解SwiftUI的方方面面;2、15个超级精彩的实例:包含美食、理财、健身、教育、电子商务等各行业的App实例;3、创新的教学模式:手把手教您SwiftUI用户界面开发技术,一看就懂,一学就会;4、贴心的操作提示:让您的眼睛始终处于操作的焦点位置,不用再满屏找光标;5、语言简洁精练:瞄准问题的核心所在,减少对思维的干扰,并节省您宝贵的时间;6、视频短小精悍:即方便于您的学习和记忆,也方便日后对功能的检索;7、齐全的学习资料:提供所有课程的源码,在Xcode 11 + iOS 13环境下测试通过; 更好的应用,更少的代码!SwiftUI是苹果主推的下一代用户界面搭建技术,具有声明式语法、实时生成界面预览等特性,可以为苹果手机、苹果平板、苹果电脑、苹果电视、苹果手表五个平台搭建统一的用户界面。SwiftUI是一种创新、简单的iOS开发中的界面布局方案,可以通过Swift语言的强大功能,在所有的Apple平台上快速构建用户界面。 仅使用一组工具和API为任何Apple设备构建用户界面。SwiftUI具有易于阅读和自然编写的声明式Swift语法,可与新的Xcode设计工具无缝协作,使您的代码和设计**同步。自动支持动态类型、暗黑模式、本地化和可访问性,意味着您的**行SwiftUI代码已经是您编写过的非常强大的UI代码了。 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhj12399

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值