Managing user interface state

Groot·2023년 12월 10일
0

TIL

목록 보기
147/148
post-thumbnail

TIL

🌱 난 오늘 무엇을 공부했을까?

SwiftUI - 공식문서 보기

Managing user interface state

  • 앱의 뷰 계층 구조 내에서 뷰별 데이터를 캡슐화해서 뷰를 재사용할 수 있도록 만든다.

Overview

  • 뷰 간에 공유되는 단일 정보 소스를 설정하기 위해 데이터가 필요한 뷰의 최소단위 공통적인 조상에 데이터를 상태로 저장한다.
  • Swift 프로퍼티를 통해 데이터를 읽기 전용으로 제공하거나, 바인딩을 사용해 상태에 대한 양방향 연결을 만든다.
  • SwiftUI는 데이터 변경을 확인하고, 필요에 따라 영향을 받는 모든 뷰를 업데이트 한다.
    image
  • state 변수는 뷰의 라이프 사이클을 반영하기 때문에 영구적인 저장을 위해서 state 프로퍼티를 사용하지는 마라.
  • 버튼 강조, 필터 설정 또는 현재 선택된 목록 항목과 같이 사용자의 인터페이스에만 영향을 미치는 임시적인 상태를 관리하는데 사용해라.
  • 앱의 데이터 모델을 변경할 준비가 되기 전 프로토타입을 만드는 동안 이런 종류의 저장소 관리가 편리하다는 것을 알게 될 수도 있다.

Manage mutable values as state

  • 뷰가 수정될 수 있는 데이터를 저장해야 하는 경우 State property wrapper를 사용해서 변수를 선언해라.
  • 예를들면, PlayerView 내부에서 isPlaying이란 Boolean을 생성해서 팟캐스트가 실행되는 시점을 추적할 수 있다.
    struct PlayerView: View {
        @State private var isPlaying: Bool = false

        var body: some View {
            // ...
        }
    }
  • 프로퍼티를 state로 표시하면 프레임워크가 기본 저장소를 관리하도록 지시한다.
  • 뷰는 프로퍼티 이름을 사용해 wrapped Value 프로퍼티에 있는 데이터를 읽고 쓴다.
  • 값이 변경되는 시점에 SwiftUI는 뷰의 영향을 받는 부분을 업데이트함.
  • 예를들면, 탭하면 저장된 값을 전환하고 저장된 값에 따라 다른 이미지를 표시하는 버튼을 PlayerView에 추가할 수 있다.
    Button(action: {
        self.isPlaying.toggle()
    }) {
        Image(systemName: isPlaying ? "pause.circle" : "play.circle")
    }
  • state 변수를 비공개로 선언하여 범위를 제한하면 변수를 선언하는 뷰 계층 구조에 변수가 캡슐화된 상태로 유지된다.

Declare Swift properties to store immutable values

  • 뷰가 수정하지 않는 데이터를 뷰에 제공하려면 기본 Swift 프로퍼티로 선언해라.
  • 예를 들면, 에피소드 제목과 프로그램 이름에 대한 문자열을 포함하는 입력 구조를 갖도록 팟캐스트 플레이어를 확장할 수 있다.
    struct PlayerView: View {
        let episode: Episode // The queued episode.
        @State private var isPlaying: Bool = false

        var body: some View {
            VStack {
                // Display information about the episode.
                Text(episode.title)
                Text(episode.showTitle)
                
                Button(action: {
                    self.isPlaying.toggle()
                }) {
                    Image(systemName: isPlaying ? "pause.circle" : "play.circle")
                }
            }
        }
    }
  • 에피소드 프로퍼티의 값은 PlayerView에 대해 상수이지만 이 보기의 상위 뷰에서는 상수일 필요는 없다.
  • 사용자가 상위 에피소드에서 다른 에피소드를 선택하면 SwiftUI는 상태 변경을 감지하고 새로운 입력으로 PlayerView를 다시 생성한다.

Share access to state with bindings

  • 뷰가 하위 뷰와 상태 제어를 공유해야 하는 경우 Binding property wrapper를 사용하여 하위 뷰에서 속성을 선언한다
  • 바인딩은 기존 스토리지에 대한 참조를 나타내며 기본 데이터에 대한 단일 정보 소스를 보존한다.
  • 예를 들어, 팟캐스트 플레이어 뷰의 버튼을 PlayButton이라는 하위 뷰로 리팩터링하는 경우 isPlaying 속성에 대한 바인딩을 제공할 수 있다.
    struct PlayButton: View {
        @Binding var isPlaying: Bool

        var body: some View {
            Button(action: {
                self.isPlaying.toggle()
            }) {
                Image(systemName: isPlaying ? "pause.circle" : "play.circle")
            }
        }
    }
  • 위에 표시된 바와 같이, State와 마찬가지로 프로퍼티를 직접 참조하여 바인딩의 래핑된 값을 읽고 쓴다.
  • 그러나 State 속성과 달리 Binding에는 자체 저장소가 없다.
  • 대신, 다른 곳에 저장된 State 속성을 참조하고 해당 저장소에 대한 양방향 연결을 제공한다.
  • PlayButton을 인스턴스화할 때 달러 기호($) 접두사를 붙여 상위 뷰에 선언된 해당 상태 변수에 대한 바인딩을 제공
    struct PlayerView: View {
        var episode: Episode
        @State private var isPlaying: Bool = false

        var body: some View {
            VStack {
                Text(episode.title)
                Text(episode.showTitle)
                PlayButton(isPlaying: $isPlaying) // Pass a binding.
            }
        }
    }
  • $ 접두사는 상태에 대해 기본 저장소에 대한 바인딩인 projectedValue에 대한 래핑된 속성을 요청한다.

  • 마찬가지로 $ 접두사를 사용하여 바인딩에서 바인딩을 가져올 수 있으므로 임의의 수의 뷰 계층 구조 수준을 통해 바인딩을 전달할 수 있다.

  • 상태 변수 내에서 범위가 지정된 값에 대한 바인딩을 가져올 수도 있다.

  • 예를들어, 플레이어의 상위 뷰에서 에피소드를 상태 변수로 선언하고 에피소드 구조에 토글로 제어하려는 isFavorite 부울도 포함되어 있는 경우, $episode.isFavorite를 참조하여 에피소드의 즐겨찾기 상태에 대한 바인딩을 얻을 수 있다.

    struct Podcaster: View {
        @State private var episode = Episode(title: "Some Episode",
                                             showTitle: "Great Show",
                                             isFavorite: false)
        var body: some View {
            VStack {
                Toggle("Favorite", isOn: $episode.isFavorite) // Bind to the Boolean.
                PlayerView(episode: episode)
            }
        }
    }

Animate state transitions

  • 뷰 상태가 변경되면 SwiftUI는 영향을 받는 뷰를 즉시 업데이트한다.
  • 시각적 전환을 부드럽게 하려면, withAnimation(::) 함수 호출에서 이를 트리거하는 상태 변경을 래핑하여 SwiftUI에 애니메이션을 적용하도록 지시할 수 있습니다.
  • 예를 들어, isPlaying Boolean에 의해 제어되는 변경 사항을 애니메이션화할 수 있다.
    withAnimation(.easeInOut(duration: 1)) {
        self.isPlaying.toggle()
    }
  • 애니메이션 함수의 후행 클로저 내에서 isPlaying을 변경하면 버튼 이미지의 크기 조정 효과처럼 래핑된 값에 의존하는 모든 것을 SwiftUI에 애니메이션화하도록 지시할 수 있다.
    Image(systemName: isPlaying ? "pause.circle" : "play.circle")
        .scaleEffect(isPlaying ? 1 : 1.5)
  • SwiftUI는 지정된 곡선과 지속 시간을 사용하거나 아무것도 제공하지 않는 경우 합리적인 기본값을 사용하여 주어진 값 1과 1.5 사이에서 시간에 따라 크기 조정 효과 입력을 전환한다.

  • 반면에 동일한 Boolean이 표시할 시스템 이미지를 지정하더라도 이미지 콘텐츠는 애니메이션의 영향을 받지 않습니다.

  • 이는 SwiftUI가 두 문자열 Pause.circle과 play.circle 사이에서 의미 있는 방식으로 점진적으로 전환할 수 없기 때문다.

  • state 프로퍼티에 애니메이션을 추가하거나 위의 예와 같이 binding에 애니메이션을 추가할 수 있다.

  • 어느 쪽이든, SwiftUI는 기본 저장된 값이 변경될 때 발생하는 모든 뷰 변경에 애니메이션을 적용

  • 예를 들어, PlayerView(애니메이션 블록 위치 위의 뷰 계층 구조 수준)에 배경색을 추가하면 SwiftUI도 이에 애니메이션을 적용한다.

    VStack {
        Text(episode.title)
        Text(episode.showTitle)
        PlayButton(isPlaying: $isPlaying)
    }
    .background(isPlaying ? Color.green : Color.red) // Transitions with animation.
  • 상태 변경으로 인해 트리거된 모든 뷰가 아닌 특정 뷰에 애니메이션을 적용하려면 대신 animation(_:value:) view modifier를 사용

역시 공식문서 따라가기가 제일 맘편하다.. 역시 근본..

profile
I Am Groot

0개의 댓글