SwfitUI에서 데이터 전달

준우·2023년 12월 8일
1

SwiftUI 이야기

목록 보기
2/5
post-thumbnail
post-custom-banner

현재 존재하는 SwiftUI 에서 데이터 전달하는 객체

  • State
  • Binding
  • ObservableObject
  • StateObject
  • ObservedObject
  • EnvironmentObject

분류 이미지


@State

  • SwiftUI 에서 Struct 내의 값을 변경할 수 있게 해줍니다.

  • 지속적으로 변형 가능한 변수를 만들 수 있게 해줍니다.

  • @State는 String, Int, Bool 과 같은 타입에만 사용되는 것을 권장합니다.

  • 일반적으로, @State 변수는 private로 선언되고, 다른 View와 공유되지 않습니다.

  • 다른 View와 값을 공유하고 싶다면, @Binding 또는 @ObservedObject를 사용하시면 됩니다.

  • 사용 예시

struct ContentView: View {
	@State private var number = 0
}

@Binding

  • @Binding은 부모 View의 @State 와 같은 값을 양방향으로 연결되게 해줍니다.

  • isPresented 는 showAddView 를 바인딩 시켜줘서 값을 변경해줍니다.

  • 사용 예시

// 부모 View
struct ContentView: View {
  // 변경 가능한 변수
  @State private var showAddView = false

  var body: some View {
    VStack {
      Button("Trigger") {
        showAddView = true
      }
    }
    .sheet(isPresented: $showAddView) {
      AddView(isPresented: self.$showAddView)
    }
  }
}

// 자식 View
struct AddView: View {
  // ContentView의 showAddView와 연결된 변수
  @Binding var isPresented: Bool

  var body: some View {
    Button("Dismiss") {
      // ContentView의 showAddView를 False로 변경합니다.
      self.isPresented = false
    }
  }
}

ObservableObject

  • Protocol으로 Combine 프레임워크의 일부입니다.

  • ObservableObject를 사용하기 위해서는, Protocol을 준수하고 @Published를 사용하면 됩니다.

  • @Published를 사용하면 변수의 값이 추가되거나, 삭제되는 것을 View가 감지할 수 있게 됩니다.

  • ObservableObject는 MVVM 아키텍쳐의 ViewModel에 적용하기 좋은 프로토콜입니다.

  • 사용 예시

class MyViewModel: ObservableObject {
  // ObservableObject 프로토콜을 준수하기 위해 @Published 변수 생성
  @Published var dataSource: MyModel
  
  init(dataSource: MyModel) {
    self.dataSource = dataSource
  }
}

@StateObject

  • @ObservedObject 와 비슷한 방식으로 작동합니다.

  • SwiftUI가 View 를 다시 랜더링 할 때, 실수로 취소되는 것을 방지할 수 있습니다.

  • @StateObject를 통해서 관찰되고 있는 객체는 화면 구조가 재생성되어도 해제되지 않습니다.(아래에서 다시 설명하겠습니다.)

  • 사용 예시

struct ContentView: View {
  @StateObject var user = User()
}

@ObservedObject

  • SwiftUI는 @ObservedObject 를 통해 View가 외부 객체를 감지하게 해줍니다.
  • 아래 코드의 User class 는 ObservableObject를 준수하고, @Published 변수를 갖고 있습니다.
  • @ObservedObject user 변수는 이러한 User class 객체를 담고 있습니다.
  • SwiftUI는 이러한 user 객체의 @Published 변수 값이 변경될 때 view를 refresh 합니다.
class User: ObservableObject {
  // ObservableObject 를 준수하기 위해 @Published 변수 생성
  @Published var name = "Hohyeon Moon"
}

struct ContentView: View {
  // ObservableObject 객체(= User) 를 관찰
  @ObservedObject var user = User()

  var body: some View {
    VStack {
      Text("Your name is \(user.name).")
    }
  }
}

@EnvironmentObject

  • @EnvironmentObject 는 보통 앱 전반에 걸쳐 공유되는 데이터에 사용됩니다. (예를 든다면, 다크모드, 앱 테마 컬러)
  • @EnvironmentObject 는 .environmentObject() 를 통해 값을 전달할 수 있습니다.
  • 전달하는 object는 ObservableObject 프로토콜을 준수해야 합니다.
  • Root View 를 제공하면, 어떠한 View에서도 사용이 가능합니다.
// MySettings.swift
class Settings: ObservableObject {
  @Published var version = 0
}

// SceneDelegate.swift
// EnvironmentObject 상위뷰에서 사용하는 방법 - 1 
var settings = UserSettings() 
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(settings))

// EnvironmentObject 상위뷰에서 사용하는 방법 - 2
@main
struct MyApp: App {
    let settings = AppSettings()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(settings) // 설정 데이터 전달
        }
    }
}

// MyViews.swift
struct ContentView: View {
  // EnvironmentObject 변수를 생성
  @EnvironmentObject var settings: UserSettings

  var body: some View {
    NavigationView {
      VStack {
        Button(action: {
          self.settings.version += 1
        }) {
          Text("Increase version")
        }

        NavigationLink(destination: DetailView()) {
          Text("Show Detail View")
        }
      }
    }
  }
}

// DetailView.swift - ContentView의 자식뷰
struct DetailView: View {
  // EnvironmentObject 변수를 생성
  @EnvironmentObject var settings: UserSettings

  var body: some View {
    Text("Version: \(settings.version)")
  }
}

WWDC 2023 이후 변경된 점

Xcode 15 와 Swift 5.9 이후 SwftUI 의 데이터를 사용하는 방식이 변경되었습니다.

  • Observable 프로토콜을 준수하는 대신, @Observable Macro 를 표시하면 됩니다.
  • @Published 를 사용하지 않아도 됩니다.
// BEFORE
// ObservableObject 프로토콜을 사용한 방식
class Library: ObservableObject {
    @Published var books: [Book] = [Book(), Book(), Book()]
}

// AFTER
// Observable Macro를 사용한 방식
@Observable class Library {
    var books: [Book] = [Book(), Book(), Book()]
}
  • 이전에는 @State, @StateObject, @ObservedObject, @EnvironmentObject 와 같은 프로퍼티들을 사용했습니다.
  • 이제는 값, 참조 타입 모두 @State 프로퍼티 래퍼만 사용하면 됩니다.
// BEFORE
@main
struct BookReaderApp: App {
  // @StateObject 사용
    @StateObject private var library = Library()

    var body: some Scene {
        WindowGroup {
            LibraryView()
                .environmentObject(library)
        }
    }
}

// AFTER
@main
struct BookReaderApp: App {
  // @StateObject -> @State를 사용하면 됩니다.(적응하는 데, 조금 걸릴수도?)
    @State private var library = Library()

    var body: some Scene {
        WindowGroup {
            LibraryView()
                .environment(library)
        }
    }
}
  • @EnvironmentObject -> @Environment 로 바꼈습니다.
// BEFORE
struct LibraryView: View {
  // 기존에는 @EnvironmentObject를 사용했습니다.
    @EnvironmentObject var library: Library

    var body: some View {
        List(library.books) { book in
            BookView(book: book)
        }
    }
}

// AFTER
struct LibraryView: View {
  // @Environment(Model) 로 바뀌었습니다.
    @Environment(Library.self) private var library
    
    var body: some View {
        List(library.books) { book in
            BookView(book: book)
        }
    }
}
  • @ObserverdObject는 사용하지 않게 되었습니다.
// BEFORE
struct BookView: View {
  // 기존에는 Observable 프로토콜을 채택한 class 를 사용하기 위해서는 @ObservedObject를 사용해야 했습니다.
    @ObservedObject var book: Book
    
    var body: some View {
        Text(book.title)
    }
}

// AFTER
struct BookView: View {
  // 이제는 그냥 변수처럼 사용하면 됩니다.
    var book: Book
    
    var body: some View {
        Text(book.title)
    }
}
  • Observable 을 준수하는 오브젝트에 대해서 Binding 이 필요한 경우에는 @Bindable 을 사용하게 바뀌었습니다.
// BEFORE
struct BookEditView: View {
  // 기존
    @ObservedObject var book: Book
    
    var body: some View {
        TextField("Title", text: $book.title)
    }
}

// AFTER
struct BookEditView: View {
  // Binding 이 필요한 경우 @Bindable 을 사용하면 됩니다.
  // Bindable 을 사용함으로써 Book 객체에 대하여 CRUD가 가능해집니다.
    @Bindable var book: Book
    
    var body: some View {
      // 구조체 내부 값에 접근하기 위해서는 $을 붙여야 합니다.
        TextField("Title", text: $book.title)
    }
}

SwiftUI Life Cycle

  • SwiftUI에는 View 의 상태를 나타내는 함수
  • Life Cycle에는 다음 두 가지만 존재합니다.
    • .onAppear
    • .onDisappear
.onAppear {
  // 뷰가 나타날 때
    print("View appeared")
}

.onDisappear {
  // 뷰가 사라질 때
    print("View disappeared")
}

@StateObject 와 @ObservedObject 의 차이점?

  • @StateObject 와 @ObservedObject 는 관찰 중인 객체의 변경에 반응해서 화면을 업데이트 할 수 있게 해줍니다.

@ObservedObject 란?

  • Observable 프로토콜을 따른다면, @ObservedObject 프로퍼티로 연결되어 데이터가 변경되었을 때, 화면을 다시 그리게 됩니다.
  • @Published 가 변화했다는 신호를 보내서 @Observable 프로토콜 객체를 변화시킵니다.

@StateObject 란?

  • @ObservedObject 와 비슷하게 작동합니다.

@StateObject 가 @ObservedObject 와 다른 점?

  • @StateObject 를 통해서 관찰되고 있는 객체는 화면 구조가 재생성되어도 파괴되지 않습니다.
  • 예를 들자면, 기존에 있던 숫자 변수가 View 가 새로 생성되어도 숫자 변수는 초기화 되지 않습니다. (하지만, @ObservedObject는 초기화 됩니다. --> 큰 차이점)

@StateObject 는 언제 써야할까?

  • SwiftUI 가 화면을 만들거나 다시 그릴 수 있는 가능성이 있는 경우에는 내부에 @ObservedObject 를 사용하는 것은 안전하지 않습니다.
  • 뷰를 재사용해도 항상 같은 결과를 얻을 경우

@StateObject : ObservedObject 를 생성하는 화면에서도 일관된 결과를 원할 경우 사용

@ObservedObject : 의존 관계로 주입할 때 사용

post-custom-banner

0개의 댓글