SwiftUI 정리

6 분 소요

App 프로토콜을 준수하는 구조체를 선언하고 하나 이상의 scene을 추가합니다. WindowGroup과 같은 내장된 scene을 사용하거나 Scene 프로토콜을 준수하는 커스텀 scene을 사용할 수 있습니다. scene에는 앱 사용자 인터페이스를 정의하는 뷰 계층이 포함되어 있으며, 시스템에서 관리하는 생명 주기가 있습니다.

CommandMenu 인스턴스를 적용하여 매뉴 커맨드를 추가합니다. CommandGroup을 사용하여 시스템 제공 메뉴를 업데이트할 수 있습니다. 명령에 대한 키보드 단축키를 제공하면 iOS, iPadOS, tvOS에서 이 명령어 키를 사용할 수 있습니다.

WidgetKit과 함께 작동하는 SwiftUI를 사용하여 앱에 위젯을 추가할 수 있습니다. 위젯을 사용하면 앱에서 관련 콘텐츠를 빠르게 접근할 수 있습니다. Widget 프로토콜을 준수하는 구조체를 정의하고 위젯에 대한 뷰 계층을 선언합니다.

작업을 호출하여 시스템의 다른 부분과 상호작용합니다. 예를 들어 OpenURLAction 인스턴스로 URL을 열 수 있습니다.

App

앱의 구조와 동작을 나타내는 프로토콜입니다.

public protocol App {
    associatedtype Body : Scene
    @SceneBuilder var body: Self.Body { get }
    init()
}

App 프로토콜을 준수하는 구조체를 선언하여 앱을 만듭니다. 앱의 콘텐츠를 정의하는데 필요한 body 연산 프로퍼티를 구현합니다.

App 프로토콜을 준수하는 사용자 정의 타입에 대한 진입점을 제공하기 위해 @main 속성이 타입 선언 앞에 나옵니다. 프로토콜은 시스템이 앱을 시작하기 위해 호출하는 main() 메서드의 기본 구현을 제공합니다. 진입점은 앱의 모든 파일 중에서 단 하나의 진입점만을 가질 수 있습니다.

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            Text("Hello, world!")
        }
    }
}

Scene 프로토콜을 준수하는 인슽턴스에서 앱의 body를 구성합니다. 각 scene에는 뷰 계층 구조에서 루트 뷰를 가지고 있으며, 시스템에서 관리하는 생명 주기가 있습니다. SwiftUI에서는 문서 또는 설정을 보여주기 위해 일반적인 시나리오를 처리하기 위한 몇 가지 구체적인 scene 유형을 제공합니다. 또는 사용자 정의 scene을 만들 수 있습니다.

@main
struct Mail: App {
    var body: some Scene {
        WindowGroup {
            MailView()
        }
        Settings {
            SettingsView()
        }
    }
}

앱에서 상태를 선언하여 앱의 모든 scene에서 공유할 수 있습니다. 예를 들어 StateObject 속성을 사용하여 데이터 모델을 초기화한 다음, 뷰 입력에서 해당 모델을 ObservedObject로 또는 환경을 통해 EnvironmentObject로 앱의 scene에 제공할 수 있습니다.

@main
struct Mail: App {
    @StateObject private var model = MailModel()
    
    var body: some Scene {
        WindowGroup {
            MailViewer()
                .enviromentObject(model) // Passed through the environment.
        }
        Settings {
            SettingsView(model: model) // Passed as an observed object.
        }
    }
}

View

앱 사용자 인터페이스를 나타내고 뷰를 구성하는데 사용하는 수정자를 제공하는 프로토콜입니다.

public protocol View {
    associatedtype Body : View
    @ViewBuilder var body: Self.Body { get }
}

View 프로토콜을 채택하는 타입을 선언하여 커스텀 뷰를 만들 수 있습니다. body 연산 프로퍼티를 구현하여 커스텀 뷰에 대한 콘텐츠를 제공합니다.

struct MyView: View {
    var body: some View {
        Text("Hello, World!")
    }
}

위 예제와 같이 Text 인스턴스와 SwiftUI에서 제공하는 하나 이상의 기본적인 뷰와 커스텀 뷰를 뷰 계층 구조로 결합하여 뷰의 body를 결합합니다.

View 프로토콜은 앱 레이아웃에서 뷰를 구성하는데 사용하는 수정자를 제공합니다. 수정자는 뷰 구성에 설명된 대로 다른 뷰에서 호출한 뷰 인스턴스를 지정된 특성으로 래핑하여 작동합니다. 예를 들어 텍스트 뷰에 opacity(_:) 수정자를 추가하면 투명도가 추가된 새로운 뷰가 반환됩니다.

Text("Hello, World!")
    .opacity(0.5)

기본 수정자는 뷰 관리를 위해 많은 옵션을 제공합니다.

ViewModifier

ViewModifier는 뷰에 다른 값을 지정하여 새로운 버전의 뷰를 생성하는 수정자 프로토콜입니다.

ViewModifier를 채택하여 만든 수정자 타입은 어떤 뷰에도 적용할 수 있습니다.

State and Data Flow

앱 모델 내에서 데이터의 흐름과 변동을 제어하고 반응합니다.

SwiftUI는 사용자 인터페이스 디자인에 대한 선언적 접근 방식을 제공합니다. 뷰 계층 구조를 구성할 때, 뷰에 대한 데이터 종속성도 표시합니다. 외부에서 발생한 이벤트나 사용자의 행동에 의해 데이터가 변경되면 SwiftUI는 인터페이스에 영향을 받는 부분을 자동으로 업데이트합니다. 결과적으로 SwiftUI는 전통적으로 뷰 컨트롤러가 수행한 대부분의 작업을 자동으로 수행합니다.

20210712-State-and-Data-Flow

SwiftUI는 앱의 데이터와 사용자 인터페이스를 연결하기 위해 state 변수와 바인딩과 같은 도구를 제공합니다. 이러한 도구는 부분적으로 작성하는 글루 로직(글루 코드)의 양을 줄임으로써 앱의 모든 데이터 조각에 대한 단일 정보 소스를 유지하는 데 도움이 됩니다. 수행해야 하는 작업에 가장 적합한 도구를 선택하세요.

  • State 프로퍼티를 사용하여 뷰 내에 일시적인 UI 상태를 로컬로 관리합니다.
  • ObservedObject 프로퍼티 래퍼를 사용하여 ObservableObject 프로토콜을 준수하는 외부 참조 모델 데이터에 연결합니다. EnvironmentObject 프로퍼티 래퍼를 사용하여 environment에 저장된 observable object에 접근합니다. StateObject를 사용하여 뷰에서 observable object를 인스턴스화 합니다.
  • Binding 프로퍼티 래퍼를 사용하여 state 또는 observable object와 같은 정보 소스에 대한 참조를 공유합니다.
  • Environment에 저장하여 앱 전체에 값 데이터를 배포합니다.
  • PreferenceKey를 사용하여 하위 뷰에서 뷰 계층 구조를 통해 데이터를 전달합니다.
  • FetchRequest를 사용하여 Core Data에 저장된 영구 데이터를 관리합니다.

SwiftUI는 State 및 바인딩과 같은 많은 데이터 관리 유형을 Swift 프로퍼티 래퍼로 구현합니다. 프로퍼티 선언에 래퍼 이름이 있는 속성을 추가하여 프로퍼티 래퍼를 적용합니다.

@State private var isVisible = true // Declares isVisiable as a state variable.

프로퍼티는 래퍼에 의해 지정된 동작을 얻습니다. SwiftUI의 state 및 데이터 흐름 프로퍼티 래퍼는 데이터의 변경을 감시하고 필요에 따라 영향을 받는 뷰를 자동으로 업데이트합니다. 코드에서 프로퍼티에 직접 참조할 때 위의 예에서 isVisible state 속성에 래핑된 값에 접근합니다.

if isVisible == true {
    Text("Hello") // Only rendered when isVisible is true.
}

또는 프로퍼티 이름 앞에 달러 기호($)를 붙여서 프로퍼티 래퍼의 예상 값에 접근할 수 있습니다. SwiftUI state 및 데이터 흐름 프로퍼티 래퍼는 항상 래핑된 값에 대한 양방향 연결인 바인딩을 프로젝션하여 다른 뷰에서 단일 소스에 접근하고 변경할 수 있도록 합니다.

@State

State 프로퍼티는 뷰 레이아웃의 현재 상태를 저장하기 위해서 사용합니다. State 프로퍼티는 주로 String, Int와 같은 원시 타입을 저장하기 위해 사용하지만, 다른 타입을 정의해서 사용할 수도 있습니다. State 프로퍼티의 값으로 설정된 뷰는 프로퍼티 값이 변경되면 뷰도 자동으로 업데이트합니다. State 프로퍼티는 자신의 뷰에서 사용하는 프로퍼티이므로 주로 private 으로 선언합니다.

increase 버튼을 클릭하면 number 프로퍼티의 값이 증가하고 증가한 값이 뷰에 새로 그려지게 됩니다. 마찬가지로 decrease 버튼을 클릭하면 number 프로퍼티의 값이 감소하고 감소한 값이 뷰에 새로 그려지게 됩니다.

struct ContentView: View {
    // @State 프로퍼티 래퍼로 선언한 number
    @State private var number = 0
    
    var body: some View {
        VStack {
            Text("\(number)")
            Divider()
            Button("increase", action: increaseNumber)
            Divider()
            Button("decrease", action: decreaseNumber)
        }
    }
    
    // number 증가
    private func increaseNumber() {
        number += 1
    }
    
    // number 감소
    private func decreaseNumber() {
        number -= 1
    }
}

@Binding

Binding 프로퍼티는 다른 뷰의 State 프로퍼티를 연결할 때 사용합니다. 바인딩을 하기 위해서는 $ 기호를 사용하여 State 프로퍼티와 연결합니다. 하나의 State 프로퍼티를 공유하기 위해 사용합니다.

struct ContentView: View {
    // @State 프로퍼티 래퍼로 선언한 number
    @State private var number = 0
    
    var body: some View {
        VStack {
            Text("\(number)")
            Divider()
            Button("increase", action: increaseNumber)
            Divider()
            Button("decrease", action: decreaseNumber)
            Divider()
            SecondView(number: $number)
        }
    }
    
    private func increaseNumber() {
        number += 1
    }
    
    private func decreaseNumber() {
        number -= 1
    }
}

struct SecondView: View {
    @Binding var number: Int
    
    var body: some View {
        Text("\(number)")
    }
}

@ObservedObject

ObservedObject 프로퍼티는 주로 뷰모델 형태로 사용합니다. ObservableObject 프로토콜을 채택한 타입에 대해서 선언할 수 있으며, @Published 프로퍼티의 변화를 추적하며, 뷰에 반영합니다. ObservedObject는 뷰의 라이프사이클에 의존하여 뷰가 새로 그려질 경우 새로 생성될 수 있습니다. 따라서 데이터가 유실될 수 있습니다.

struct ContentView: View {
    // @ObservedObject 프로퍼티 래퍼로 선언한 counter
    @ObservedObject private var counter = Counter()
    
    var body: some View {
        VStack {
            Text("\(counter.count)")
            Divider()
            Button("increase", action: increaseNumber)
            Divider()
            Button("decrease", action: decreaseNumber)
        }
    }
    
    private func increaseNumber() {
        counter.increaseCount()
    }
    
    private func decreaseNumber() {
        counter.decreaseCount()
    }
}

final class Counter: ObservableObject {
    // count 프로퍼티의 변화 감지
    @Published private(set) var count = 0
    
    func increaseCount() {
        count += 1
    }
    
    func decreaseCount() {
        count -= 1
    }
}

@EnvironmentObject

EnvironmentObject 프로퍼티는 선언된 뷰와 하위 뷰 전반에 공유되는 데이터 프로퍼티입니다. 상위 뷰에서 정의한 EnvironmentObject 프로퍼티를 공유할 때 사용합니다.

@main
struct SwiftUITutorialApp: App {
    private var user = User("zdo")
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(user)
        }
    }
}

final class User: ObservableObject {
    @Published var name: String
    
    init(_ name: String) {
        self.name = name
    }
}

struct ContentView: View {
    @EnvironmentObject var user: User
    
    var body: some View {
        VStack {
            Text(user.name)
        }
    }
}

@StateObject

StateObject 프로퍼티는 ObservedObject 프로퍼티와 비슷하게 동작합니다. 차이점이라면 뷰가 새로 그려지더라도 데이터가 사라지지 않습니다.

struct ContentView: View {
    // @StateObject 프로퍼티 래퍼로 선언한 counter
    @StateObject private var counter = Counter()
    
    var body: some View {
        VStack {
            Text("\(counter.count)")
            Divider()
            Button("increase", action: increaseNumber)
            Divider()
            Button("decrease", action: decreaseNumber)
        }
    }
    
    private func increaseNumber() {
        counter.increaseCount()
    }
    
    private func decreaseNumber() {
        counter.decreaseCount()
    }
}

final class Counter: ObservableObject {
    // count 프로퍼티의 변화 감지
    @Published private(set) var count = 0
    
    func increaseCount() {
        count += 1
    }
    
    func decreaseCount() {
        count -= 1
    }
}

참고 URL


Apple SwiftUI

Wiki Glue code

카테고리: ,

업데이트:

댓글남기기