위 글은 유튜브 정대리님의 SwiftUI fundamental Tutorial 강좌를 보고 작성한 정리글로
자세한 내용은 유튜브를 통해 확인하시길 권장합니다.
이번 강의는 점 어려웠다.... 정리한거 두고두고 챙겨 봐야할 듯
Redux란?
ReacJS로, 리액트로 작업을 만들 때 단방향 흐름을 통하는 자바스크립트 프론트앤드 프레임워크를 사용할 때 쓰는 패턴
React+Flux가
스토어: 앱 전체의 상태를 가지고 있는 녀석
액션: 리듀서에게 알리는 상태 변경에 대한 액션 (어떤 행위를 할 것인지?)
리듀서: 액션으로 새로운 앱 상태를 변경하는 메소드들을 제공하는 것들로 현재 앱 상태를 받거나 상태를 변경하기 위해 액션을 보내는 녀석
UI 설계하기
DiceView.swift
import Foundation
import SwiftUI
struct DiceView: View{
var body: some View{
VStack{
Text("⚀")
.font(.system(size: 300, weight: .bold, design: .monospaced))
Button(action: {
print("주사위 굴러가유")
}, label: {
Text("랜덤 주사위 굴리기")
.fontWeight(.bold)
}).buttonStyle(MyButtonStyle())
}
}
}
MyButtonStyle.swift
import Foundation
import SwiftUI
struct MyButtonStyle: ButtonStyle{
func makeBody(configuration: Configuration) -> some View{
configuration
.label
.font(.system(size: 20))
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(20)
}
}
struct MyButtonStyle_Previews: PreviewProvider{
static var previews: some View{
Button(action: {
}, label: {
Text("Button")
.fontWeight(.heavy)
}).buttonStyle(MyButtonStyle())
}
}
AppState.swift 생성
앱의 상태, 즉 데이터를 의미
import Foundation
struct AppState{
var currentDice: String = ""
}
AppAction.swift 생성
import Foundation
enum AppAction: String{
// 주사위를 굴리는 액션: 현재 주사위 상태를 변경하는 액션
case rollTheDice
}
Reducer.swift 생성
import Foundation
// 클로저: 제네릭 타입
// typealias란? 별칭 짓기: 오른쪽 식을 왼쪽으로 바꾼다.
typealias Reducer<State, Action> = (inout State, Action) -> Void
// state라는 애가 매개변수로 들어올 때 변경하는 키워드: inout
// (inout State, Action) -> Void 해당 클로저 자체를 별칭으로 리듀서로 칭함, State와 Action을 가지고 있음
//필터링하는 메소드
func appReducer(_ state: inout AppState, _ action: AppAction) -> Void{
//들어오는 액션에 따라 분기처리 (= 필터링)
switch action {
case .rollTheDice:
// 앱의 상태를 변경하기
state.currentDice = ["⚀", "⚁", "⚂", "⚃", "⚄", "⚅"].randomElement() ?? "⚀"
// 값이 비었다면 ⚀
}
}
AppStore.swift 생성
// 모든 상태를 갖고 있는 파일
import Foundation
//리듀서랑 비슷한 형태
typealias AppStore = Store<AppState, AppAction>
// OvservableObject 앱 상태를 가지고 있는 옵저블 오버젝트 스토어
// 상속을 받지 못하는 클래스 final class
final class Store<State, Action>: ObservableObject{
// 옵저블 오버젝트이기 때문에 바인딩 가능: 값이 변경되면 알 수 있게끔...
//콤바인에서 사용하는 published, 프로퍼티가 스토어가 가지고 있는 무엇인가가 변경이 되었을 때 받을 수 있도록...
//외부에서 값이 변경되지 않도록 private(set) (스토어 내부에선 값 변경 가능)
//외부에서 읽을 수만 있도록 private(set)설정
@Published private(set) var state: State
//리듀서(클로서)를 갖고 있어야 함
private let reducer: Reducer<State, Action>
//스토어에 대한 생성자 생성
//리듀서가 클로저 형태이므로 이스케이핑 해줘야 함
//제네릭 형태의 State
// 즉 Store가 가지고 있는 제네릭 State
//생성자 메소드
//escaping으로 빠져나가기 위해 @escaping 추가
init(state: State, reducer: @escaping Reducer<State, Action>) {
self.state = state
self.reducer = reducer
}
//디스패치를 통해 액션 행하기
func dispatch(action: Action){
// inout 매개변수를 넣을 때는 &표시
//리듀서 클로저를 실행해서 액션 처리(필터링)
reducer(&self.state, action)
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
// 스토어 인스턴스 생성
let store = AppStore(state: AppState.init(currentDice: "⚀"), reducer: appReducer(_:_:))
var body: some View {
// 서브뷰에 옵저블 오버젝트 연결
DiceView().environmentObject(store)
}
}
DiceView.swift
import Foundation
import SwiftUI
struct DiceView: View{
//외부에서 environmentobject()로 연결됨
@EnvironmentObject var store : AppStore
// 주사위 굴리기 액션 실행
func rollTheDice(){
print(#fileID, #function, #line)
self.store.dispatch(action: .rollTheDice)
}
var body: some View{
VStack{
Text(self.store.state.currentDice)
.font(.system(size: 300, weight: .bold, design: .monospaced))
Button(action: {
self.rollTheDice()
}, label: {
Text("랜덤 주사위 굴리기")
.fontWeight(.bold)
}).buttonStyle(MyButtonStyle())
}
}
}
AppState.swift
import Foundation
struct AppState{
var currentDice: String = ""{
didSet{
print("currentDice: \(currentDice)", #fileID, #function)
}
}
}
애니메이션 효과주기
import Foundation
import SwiftUI
struct DiceView: View{
//외부에서 environmentobject()로 연결됨
@EnvironmentObject var store : AppStore
@State private var shouldRoll = false
@State private var pressed = false
var diceRollAnimation: Animation{
Animation.spring()
}
// 주사위 굴리기 액션 실행
func rollTheDice(){
print(#fileID, #function, #line)
self.shouldRoll.toggle()
self.store.dispatch(action: .rollTheDice)
}
var body: some View{
VStack{
Text(self.store.state.currentDice)
.font(.system(size: 300, weight: .bold, design: .monospaced))
.rotationEffect(.degrees(shouldRoll ? 360 : 0))
.animation(diceRollAnimation)
Button(action: {
self.rollTheDice()
}, label: {
Text("랜덤 주사위 굴리기")
.fontWeight(.bold)
}).buttonStyle(MyButtonStyle())
.scaleEffect(self.pressed ? 0.8 : 1.0)
.onLongPressGesture(minimumDuration: .infinity, maximumDistance: .infinity, pressing:{ pressing in
withAnimation(.easeInOut(duration: 0.2),{ self.pressed = pressing
})
}, perform: {})
}
}
}
'강의 > etc' 카테고리의 다른 글
[Spring/JPA] 강의소개 및 JPA 소개 (1) | 2022.03.30 |
---|---|
[JS] 2022 30분 요약 강좌(1) (0) | 2022.03.15 |
[SwiftUI fundamental Tutorial] LazyVGrid (0) | 2021.10.07 |
[SwiftUI fundamental Tutorial] Menu (0) | 2021.10.07 |
[SwiftUI fundamental Tutorial] Deeplink (0) | 2021.10.06 |