@State
SwiftUI는 @State로 선언된 property를 관리합니다. @State는 @properyWrapper로, SwiftUI로 하여금 이 프로퍼티가 하고싶은 행동을 정의하는 타입이라고 이해 시킵니다!
따라서 @State로 선언된 변수의 값이 변경되는 경우, View를 무효화(invalidate)하고 var body: some View를 recompute 하도록 합니다.
@State 인스턴스는 값 자체가 아닌 값을 읽고 쓰는 수단이며, 이 State의 기본 값에 접근하기 위해서 변수의 이름을 사용합니다. 변수의 이름은 wrappedValue를 반환합니다.
이는 정확하게 대응되지는 않지만, C언어에서 포인터를 통해 변수의 값을 참조하는 방식과 유사하다고 볼 수 있습니다.
또한 이 @State 변수는 View 혹은 View에서 호출된 메서드에서만 접근해야합니다.
즉, 사용자가 @State 변수에 직접적으로 접근하지 못하도록 쓰레드에서 변경하는 것이 안전합니다. (thread safe)
@State 변수를 View 내부의 다른 View에 전달하고자 한다면, $를 이용하여 전달합니다. 예를 들어, @State Private var isFaceUp: Bool = false를 CardView에 전달하고자 한다면, CardView(isFaceUp: $isFaceUp)의 형태로 전달해야합니다. (그리고 이를 @Binding으로 받습니다)
@State는 본인이 들어가 있는 해당 뷰에 속한 것이기 때문에 private 프로퍼티로 선언되어야 한다.
상태 프로퍼티를 선언하면 레이아웃에 있는 뷰와 바인딩을 할 수 있다.바인딩이 되어 있는 뷰에서 @State의 값이 변경을 시도하면 값이 자동으로 업데이트된다.
@State의 바인딩은 프로퍼티 이름 앞에 '$'를 붙이면 된다.
@State 프로퍼티에 변화가 생길 때마다 뷰 계층구조는 SwiftUI에 의해 다시 렌더링된다.
위에 코드처럼 값에 영향을 끼치면 $바인딩을 하지만 참조만 하면 값에 영향을 미치지 않기 때문에 $바인딩을 하지 않아도 된다.
하지만 다음과 같이 @State 프로퍼티가 선언된 View에 하위 뷰에서 값에 접근해야할 경우 @Binding 프로퍼티를사용하여 접근 가능하다
상태 프로퍼티는 뷰의 상태를 저장하고 변경시 즉각 뷰에 적용시키는 방법을 제공하지만 해당 뷰에서만 사용할 수 있다는 제한이 있다.
즉 @Binding (상태 바인딩)이 구현되어 있지 않은 뷰는 접근할 수 없다. 또한 상태 프로퍼티는 일시적인 것이라 뷰를 부모 뷰가 사라지면 그 상태도 사라진다.
Observable 객체
이를 해결하기 위해서
영구적인 데이터를 표현하기 위한 Observable 객체를 사용한다
Observable 객체는 일반적으로 시간에 따라 변경되는 하나 이상의 데이터 값을 모으고 관리하는 역할을 담당한다.또한 Observable 객체는 타이머나 알림과 같은 이벤트를 처리하기 위해 사용될 수도 있다.
Observable 객체는 게시된 프로퍼티 published property로서 데이터 값을 publish한다.이후 Observer 객체는 게시자를 구독 subscribe하여 게시된 프로퍼티가 변경될 때마다 업데이트를 받는다.
Observable 객체의 게시된 프로퍼티를 구현하는 방법은 선언할 때 @Published 프로퍼티 래퍼를 사용하는 것이다.
이 래퍼(@Published)는 래퍼 프로퍼티 값이 변경될 때마다 모든 구독자에게 업데이트를 알리게 된다.
구독자는 observable 객체를 구독하기 위해 @ObservedObject 프로퍼티 래퍼를 사용한다.구독하게 되면 그 뷰 및 모든 하위 뷰가 상태 프로퍼티처럼 같은 방식으로 게시된 프로퍼티에 접근할 수 있게 된다.
해당 변수가 변경되면 자동으로 objectWillChange.send()를 호출한다고 한다.
Published 프로퍼티의 데이터가 변경되면 새로운 상태를 반영하기 위해서 자동으로 뷰 레이아웃을 다시 렌더링 한다.
Environment 객체
구독 객체는 특정 상태가 앱 내의 몇몇 SwiftUI 뷰에 의해 사용되어야 할 경우 가장 적합하다.
위에 코드처럼 Observed객체를 인자로 SecondView에 전달한다.
이러한 방법은 여러 상황에 사용될 수 있지만 앱 내의 여러 뷰가 동일한 구독 객체에 접근해야하는 경우에는 복잡해질 수 있다.
이러한 상황일 경우 Environment 객체를 사용하는 것이다.
Environment 객체는 Observable 객체와 같은 방식으로 선언된다. 즉 반드시 ObservableObject 프로토콜을 따라야한다.
가장 중요한 차이점은 뷰에서 뷰로 전달할 필요 없이 모든 뷰가 접근할 수 있다는 것이다.
Environment 객체를 구독해야 하는 객체는 @ObservedObject 래퍼 대신 @EnvironmentObject 프로퍼티 래퍼를 이용해 해당 객체를 참조하면 된다.
아래 내가 만든 앱중 Environment 객체를 사용한 것이다.
class LoginModel: ObservableObject {
@Published var isLoggedIn = false
}
@main
struct MySQLAppApp: App {
@AppStorage("userId") var userid = User(user_id: "", user_password: "").encode()!
@AppStorage("login") var login: Bool = false
@StateObject var friends = Friends()
@StateObject var loginStateModel = LoginModel()
@StateObject var chats = Chats()
let model = LoginModel()
var body: some Scene {
WindowGroup {
NavigationView {
ApplicationSwitcher()
}
.environmentObject(loginStateModel)
.environmentObject(chats)
.environmentObject(friends)
}
}
}
@StateObject로 데이터의 값이 변경되어도 뷰를 파괴하고 다시 랜더링하는 일이 없게끔 만들고 App Scene에서 .environmentObject에 ObservableObject 객체를 추가한다. -> 안하면 에러
다음으로는 아무 뷰에서 해당 environmentObject를 사용하면 된다.
import SwiftUI
struct SettingView: View {
@AppStorage("userId") var userid = User(user_id: "", user_password: "").encode()!
@EnvironmentObject var model: LoginModel
@Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
Form {
Section(content: {
NavigationLink {
AccountSettingView()
} label: {
Text("계정정보 변경")
}
Button("로그아웃") {
userid = User().encode()!
print(User.decode(userData: userid)?.user_id ?? "비어있는데요")
Task {
await model.logOut()
}
}
}, header: {
Text("설정")
})
}
.navigationTitle("설정")
}
}
}
'swiftUI' 카테고리의 다른 글
swiftUI - @ObservedObject, @StateObject (0) | 2023.09.03 |
---|---|
swiftUI - layoutPriority (0) | 2023.09.01 |
swiftUI - Code Snippets (0) | 2023.09.01 |
swiftUI - DragGesture( ) (0) | 2023.08.20 |
swiftUI - AVFoundation (0) | 2023.08.20 |