-
Managing user interface stateSwiftUI/SwiftUI 애플 문서 한글화 2023. 6. 5. 12:18
Article
해당 문서는 https://developer.apple.com/documentation/swiftui/managing-user-interface-state 를 한글화한 문서입니다.
사용자 인터페이스 상태 관리
앱 내 뷰 계층에 뷰 별로 데이터를 캡슐화하여 뷰를 재 사용할 수 있도록 만들기
Overview
데이터를 뷰들 간 공유 할 수 있는 단일 소스로 이루어진 데이터가 필요한 경우 최소 공통 부모 뷰에 상태(State) 형태로 데이터를 저장합니다. 스위프트는 프로퍼티를 통해 데이터를 읽기 전용으로 제공하거나 바인딩(Binding)을 사용하여 상태에 대한 양방향 연결을 만들 수 있습니다. SwiftUI는 데이터의 변경 사항을 모니터링하고 필요에 따라 해당 데이터에 영향을 받은 뷰를 업데이트 합니다.
상태 변수의 수명 주기는 뷰의 수명 주기를 반영하므로 영구 저장소로 상태 속성을 사용하지 말아야 합니다. 대신 버튼의 하이라이트 상태, 필터 설정 또는 리스트에 현재 선택된 항목과 같이 사용자 인터페이스(UI)에 영향을 미치는 일시적인 상태 관리를 위해서 사용해야 합니다. 앱 개발 시 데이터 모델 개발 전에 UI의 프로토타입을 작성하는 동안 이러한 종류의 스토리지를 사용하는 게 편할 수 있습니다.
변경 가능한 값을 상태로 관리 하기
뷰는 변경이 필요한 데이터를 저장해야 할 경우 상태 프로퍼티 래퍼(@State)를 사용하여 변수를 선언합니다. 예를 들어 팟 캐스트 플레이어 뷰 에서 팟 캐스트 재생 여부를 확인하기 위해 isPlaying Boolean 값을 사용할 수 있습니다.
struct PlayerView: View { @State private var isPlaying: Bool = false var body: some View { // ... }
구조체의 속성을 상태로 표시하면 프레임워크는 기본 스토리지로 관리합니다. 뷰는 상태 속성의 이름으로 해당 변수의 wrappedValue 프로퍼티를 찾아 데이터를 읽거나 쓸 수 있습니다. 이 값이 변경되면 해당 상태 변수에 영향을 받는 뷰를 업데이트합니다. 예를 들어, PlayerView에 탭을 할 때마다, 저장된 값이 변경되고, 이는 저장된 값에 따라 재생 버튼의 이미지를 변경하도록 할 수 있습니다.
Button(action: { self.isPlaying.toggle() }) { Image(systemName: isPlaying ? "pause.circle" : "play.circle") }
상태 변수는 변경가능한 값이기 때문에 var로 선언되고 해당 값의 변경 사항이 해당 값을 갖는 뷰로 한정하기 위해 private을 사용해 상태 변수의 범위를 제한 할 수 있습니다. 이렇게 하면 변수를 선언한 뷰 계층 구조에 변수가 캡슐화된 상태로 유지됩니다.
변경 불가능한 값을 저장하기 위한 Swift 프로퍼티 선언
해당 값의 변경이 불필요한 경우 표준 Swift 속성을 선언합니다. 예를 들어, 팟캐스트에서 에피소드의 제목 및 프로그램 이름을 갖는 구조체를 Episode를 PlayerView에 추가할 수 있습니다.
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") } } } }
이 에피소드는 상수 값이지만, 해당 뷰의 상위 뷰에서는 상수일 필요는 없습니다. 단지 사용자가 상위 뷰에서 다른 에피소드를 선택하면 SwiftUI는 상태 값이 변경되었음을 감지하고 새로운 값으로 PlayerView를 다시 만들어 냅니다.
바인딩으로 상태 값에 대한 엑세스 공유
뷰가 자신 뷰와 상태 제어를 공유해야 하는 경우(텍스트 필드와 같이 입력값을 공유하고 싶을 때), 자식 뷰에 바인딩 프로퍼티 래퍼(@Binding)을 이용해 프로퍼티를 선언합니다. 바인딩은 기존 스토리지(@State로 선언한)에 대한 참조이며, 이를 통해 동일한 소스를 사용합니다. 예를 들어 팟캐스트 플레이어 뷰의 버튼을 PlayButton으로 리팩토링하는 경우(위에서는 버튼의 레벨에 있는 Image를 해당 뷰에서 사용) isPlaying 상태 변수를 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") } } }
위에서 상위 뷰에서 상태 변수를 직접 이용하여 상태 값이 변경되었다면 이제는 하위 뷰인 PlayButton에서 바인딩의 래핑된 값을 직접 참조하여 값을 읽고 쓸 수 있습니다. 하지만 상태 속성 값과 다르게 바인딩은 자체 저장소가 없고 단지 이미 있는 저장소에 대한 참조일 뿐으로 스토리지에 대한 양방향 연결을 제공합니다.
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를 요청하게 되고, 이는 해당 저장소에 대한 바인딩을 의미합니다. 유사하게 바인딩 값을 하위 다른 뷰에서 $ 접두어를 이용해 바인딩 할 수 있고, 이는 뷰 구조의 하위 객체에서는 임의의 횟수만큼 가능합니다.
예를 들어 Player의 상위 뷰에서 Episode 구조체를 상태 변수로 선언할 때 좋아요 버튼의 제어하기 위한 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) } } }
상태 전환 애니메이션
뷰의 상태가 변경되면 SwiftUI는 상태의 영향을 받는 뷰를 즉시 업데이트합니다. 시각적 전환을 원활하게 수행하려면 SwiftUI에 withAnimation(_:_:) 함수를 호출해 해당 함수의 body 안에서 래핑된 상태 값을 변경합니다. 예를 들어, isPlaying Boolean에 의해 제어되는 변경 사항을 애니메이션화 할 수 있습니다.
withAnimation(.easeInOut(duration: 1)) { self.isPlaying.toggle() }
withAnimation 함수의 후행 클로저 안에서 isPlaying을 변경하게 되면, swiftUI는 클로저 안에서 변경된 값에 따라 달라지는 모든 뷰 업데이트에 애니메이션을 적용합니다.
Image(systemName: isPlaying ? "pause.circle" : "play.circle") .scaleEffect(isPlaying ? 1 : 1.5)
위의 코드 처럼 SwiftUI는 사용자가 지정한 커브나 지속시간에 따라 Image에 대해 스케일 효과를 주어진 값 1 에서 1.5 로 전환하거나, 사용자가 지정하지 않은 경우 적절한 기본 값을 사용합니다. 하지만 동일한 Boolean 값이 시스템 이미지를 지정하더라도(isPlaying에 따라 pause.circle 또는 play.circle) 이미지 내용은 애니메이션에 영향을 받지 않습니다. 이는 SwiftUI는 pause.circle 과 play.circle 두 문자열 사이에서 의미있는 방식으로 애니메이션을 통해 전환할 수 없기 때문입니다.
상태 속성을 애니메이션에 추가하거나 위의 예와 같이 바인딩을 추가할 수 있습니다. 어쨋든, SwiftUI는 기본 저장 값이 변경될 때 발생하는 모든 뷰의 변경을 애니메이션으로 만듭니다. 예를 들어 PlayerView의 배경색을 변경할 수 있도록 아래와 같이 애니메이션을 추가할 수 있습니다.
VStack { Text(episode.title) Text(episode.showTitle) PlayButton(isPlaying: $isPlaying) } .background(isPlaying ? Color.green : Color.red) // Transitions with animation.
위의 withAnimation을 사용하는 경우 상태 값에 대한 모든 뷰에 애니메이션이 적용되는 데, 특정 뷰에만 애니메이션을 적용하고자 한다면 animation(_:value:) 한정자를 대신 사용합니다.
See Also
뷰 상태의 생성과 공유
struct State
SwiftUI에서 관리되는 값에 읽고 쓸 수 있는 프로퍼티 래펴 타입
struct Binding
신뢰할 수 있는 소스(@State 로 생성된 프로퍼티와 같은) 가 소유한 값을 읽고 쓸 수 있는 래퍼 타입 (해당 소스에 대한 참조로 이해하면 됩니다.)
'SwiftUI > SwiftUI 애플 문서 한글화' 카테고리의 다른 글
Maintaining the adaptable sizes of built-in views (0) 2023.06.05 Exploring the structure of a SwiftUI app (0) 2023.06.05 Migrating to new navigation types (0) 2023.06.05 UIApplicationDelegateAdaptor (0) 2023.06.05 Managing model data in your app (0) 2023.06.05