Devlog
article thumbnail

위 글은 유튜브 정대리님의 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: {})

        }
    }
}

profile

Devlog

@덩이

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그