SwiftUI Data Flow

  • SwiftUI는 UI를 생성하는 완전히 새로운 방식인데다, 관련 문서가 상대적으로 적어서 많은 사람들이 애를 먹고 있다.
  • 이번 글에서는 SwiftUI에서의 전체적인 데이터 흐름을 알아봐서 SwiftUI로 앱을 만들때 도움을 주었으면 한다.
  • SwiftUI Data flow에 대한 애플의 공식 영상도 강력 추천한다.
  • 애플의 공식 문서 또한 보는 것을 적극 추천한다.
  • 또, 해당 글은 미디엄의 SwiftUI Data Flow 글을 참고했다.

SwiftUI 라이프사이클

  • SwiftUI에는 View의 상태를 나타내는 함수가 아래와 같이 단 두가지 밖에 없다.
  • 대신 다양한 Property와 Data flow로 여러 상태에 대응할 수 있다.
.onAppear {
    print("ContentView appeared!")
}

.onDisappear {
    print("ContentView disappeared!")
}

ObservableObject

  • ObservableObject은 Combine 프레임워크의 일부로, 프로토콜이다.
  • 이것을 사용하기 위해서는, 프로토콜을 추가하고 @Published를 사용하면 된다.
  • ViewModel 혹은 Model에서 사용하기 좋은 프로토콜이다.
final class MoviesSelectedMenuStore: ObservableObject {
    @Published var menu: MoviesMenu
    
    init(selectedMenu: MoviesMenu) {
        self.menu = selectedMenu
    }
}

@ObservedObject

  • 이 Property wrapper는 ObservableObject를 사용하는 ViewModel 클래스를 변환하기 위해 사용된다.
struct MoviesHome : View {
    @ObservedObject private var selectedMenu = MoviesSelectedMenuStore(selectedMenu: .popular)
    
    var body: some View {
        NavigationView {
            Group {
                if selectedMenu.menu == .genres {
                    GenresList()
                } else {
                    MoviesHomeList(menu: $selectedMenu.menu)
                }
            }
      }
    }
}

@State

  • @State는 SwiftUI의 대표적인 property wrapper이다.
  • SwiftUI의 view는 struct이고 값 타입이며, SwiftUI는 view를 언제는 재생성할 수 있다.
  • 그래서 view내의 property들은 변하지 않고, 언제든 재생성된다.
  • 즉, 지속적인 값을 갖고 있는 변형 가능한 변수를 만들기 위해서는 @State를 사용해야 한다.
  • 다음 코드에서 볼 수 있듯이, @State property를 $를 통해 binding 할 수 있게 된다.
struct TabbarView: View {
    @State var selectedTab = Tab.movies
    
    enum Tab: Int {
        case movies, discover
    }
    
    func tabbarItem(text: String, image: String) -> some View {
        VStack {
            Image(systemName: image)
                .imageScale(.large)
            Text(text)
        }
    }
    
    var body: some View {
        TabView(selection: $selectedTab) {
            MoviesHome().tabItem{
                self.tabbarItem(text: "Movies", image: "film")
            }.tag(Tab.movies)
            
            DiscoverView().tabItem{
                self.tabbarItem(text: "Discover", image: "square.stack")
            }.tag(Tab.discover)
        }
    }
}

@Binding

  • @Binding property는 양방향으로 값을 관리할 수 있게 해준다.
  • 다음 코드와 같이 사용될 수 있다.
struct NotificationBadge : View {
    @Binding var isShown: Bool
    
    var body: some View {
        Text("Noti")
            .scaleEffect(isShown ? 1: 0.5)
            .opacity(isShown ? 1 : 0)
    }
}