일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- 스위프트
- colorofdays
- 스터디
- 조건문
- flutter #state # stateful #stateless
- stanford
- UserDefault
- 오늘의 색상
- WidgetTree
- collectionView
- UIKit
- GIT
- ImageSlider
- SwiftUI
- CS193p
- process
- 코딩테스트
- IOS
- flutter
- 알고리즘
- xml
- Swift
- 백준
- Masil
- 새싹후기
- MVVM
- 프로젝트회고
- 청년취업사관학교후기
- xcode
- 프로그래머스
- Today
- Total
개발을 시작하는 이야기
[SwiftUI] Lecture 4: Memorize Game Logic 본문
강의 보기 : Youtube :: Stanford
이번 강의에서 주된 내용은 Enum과 Optional에 대한 이야기
Enum
값타입으로 관련 데이터를 가지고 있을수 있다.
enum의 상태 체크는 switch를 이용한다.
break는 아무 일도 하고 싶지 않을 때 사용한다.
default는 기본값에 해당하는, 케이스가 없는 경우 사용한다. if문의 else 와 같은 느낌
switch에서 열거형을 case로 구분할때, 튜플에서 label을 추가해서 해당 값에 접근할 수 있다.
저장프로퍼티는 가질수 없고, 함수 사용은 가능하다.
CaseIterable를 이용해서 모든 타입에 접근할 수 있다.
enum A: CaseIterable {
case a
case b
case c
}
for e in A.allCases {
print(e)
}
Optional
열거형으로 none, some으로 이루어진다. some은 제네릭 타입
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
옵셔널 값을 해제하는 방법
- 값의 뒤에 '!'를 붙여 강제 해제
- 다른 변수에 할당, if let , guard let
- '??'를 사용하여 nil일때의 기본 값을 지정
이번 강의에서는 지난번의 MVVM에 맞춰서 View를 정리해주고, 로직을 정리했다.
View
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: EmojiMemoryGame
var body: some View {
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(/*@START_MENU_TOKEN@*/.red/*@END_MENU_TOKEN@*/)
.padding(.horizontal)
}
}
struct CardView: View {
let card: MemoryGame<String>.Card
var body: some View {
ZStack {
let shape = RoundedRectangle(cornerRadius: 20.0)
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()
}
}
}
}
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> {
model.cards
}
//MARK: - Intent(s)
func choose(_ card: MemoryGame<String>.Card) {
model.choose(card)
}
}
Model
import Foundation
struct MemoryGame<CardContent> where CardContent: Equatable {
private(set) var cards: Array<Card>
private var indexOfTheOneAndIOnlyFaceUpCatd: Int?
mutating func choose(_ card: Card) {
if let chosenIndex = cards.firstIndex(where: { $0.id == card.id }),
!cards[chosenIndex].isFaceUp,
!cards[chosenIndex].isMatched
{
if let potentialMatchIndex = indexOfTheOneAndIOnlyFaceUpCatd {
if cards[chosenIndex].content == cards[potentialMatchIndex].content {
cards[chosenIndex].isMatched = true
cards[potentialMatchIndex].isMatched = true
}
indexOfTheOneAndIOnlyFaceUpCatd = nil
} else {
for index in cards.indices {
cards[index].isFaceUp = false
}
indexOfTheOneAndIOnlyFaceUpCatd = chosenIndex
}
cards[chosenIndex].isFaceUp.toggle()
}
print("\(cards)")
}
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
}
}
mutating
swift에서 클래스는 래퍼런스 타입이고, 구조체와 열거형은 값 타입이다.
값 타입의 속성은 기본적으로 인스턴스 메서드 내에서 수정을 할 수 없는데,
값 타입의 속성을 수정하기 위해서 인스턴스 메서드에서 mutating 키워드를 사용한다.
함수 내부에서 설정한 chosenIndex를 변경하기 위해서 mutating을 사용한다.
@Published 와 @ObservedObject
ObservedObject는 필수구현을 필요로 하지 않는 프로토콜로, Combine에 속한 기능이다. 클래스에서만 사용이 가능하고 ObservedObject를 준수한 클래스는 objectWillChange라는 프로퍼티를 사용할 수 있는데, 이는 objectWillChange.send()를 이용하기 위함이다. 이 함수는 변경된 사항이 있다는것을 알려주는 역할을 한다.
변수가 적으면 send() 함수를 통해 간단히 해결할 수 있겠지만, 변수의 양도 많고 수정되는 부분이 많아 복잡하다면 관리하기 힘들기 때문에 이를 대신해주는 기능이 @Published 속성 래퍼가 해준다. 해당 변수가 변경되면 자동으로 objectWillChange.send()를 호출해준다.
where
where절은 크게 두가지 용도로 사용하게 된다.
1. 패턴과 결합하여 조건을 추가
2. 타입에 대한 제약 추가
let tuples: [(Int, Int)] = [(1, 2), (1, -1), (1, 0), (0, 2)]
// 값 바인딩, 와일드카드 패턴
for tuple in tuples {
switch tuple {
case let (x, y) where x == y: print("x == y")
case let (x, y) where x == -y: print("x == -y")
case let (x, y) where x > y: print("x > y")
case (1, _): print("x == 1")
case (_, 2): print("y == 2")
default: print("\(tuple.0), \(tuple.1)")
}
}
/*
x == 1
x == -y
x > y
y == 2
*/
//타입과 결합한 활용
let anyValue: Any = "ABC"
switch anyValue {
case let value where value is Int: print("value is Int")
case let value where value is String: print("value is String")
case let value where value is Double: print("value is Double")
default: print("Unknown type")
}
// value is String
Equatable
값이 같은지 비교할 수 있는 형식으로, 프로토콜을 준수하는 유형은 등호 연산자 (==) 또는 같지 않음 연산자(!=)를 사용해 동등성을 비교할 수 있다.
if let @ ,
if let으로 선언한 변수를 조건문에서 바로 활용하기 위해서는 &&이 아닌, ','로 구분을 해주면 사용이 가능하다.
if let chosenIndex = cards.firstIndex(where: { $0.id == card.id }),
!cards[chosenIndex].isFaceUp,
!cards[chosenIndex].isMatched
{
살짝의 찍먹으로 맛봤지만 SwiftUI에서 MVVM은 필수라고 하는 데에는 다 이유가 있구나 싶다.
과정이 진행될수록 점점 복잡해지고, 모르는 것들도 중간에 종종 나오면서 공부가 많이 되는것 같다.
참고 자료: mutating, Published, ObservedObject, Equatable
'개발 이야기 > Swift' 카테고리의 다른 글
접근제어는 왜 사용해야 할까? (0) | 2022.04.15 |
---|---|
[SwiftUI] Lecture 5: Properties Layout @ViewBuilder (0) | 2022.04.14 |
[SwiftUI] Lecture 3: MVVM and the Swift type system (0) | 2022.04.12 |
[SwiftUI] Lecture 2: Learning more about SwiftUI (0) | 2022.04.10 |
[SwiftUI] Lecture 1: Getting started with SwiftUI (0) | 2022.04.08 |