SwiftUI의 사용자 지정 컨테이너
컨테이너
인수로 전달된 다른 뷰를 렌더링할 수 있는 뷰에 대해 SwiftUI의 컨테이너를 호출합니다. 컨테이너는 SwiftUI의 중심입니다. SwiftUI 학습을 시작하자마자 VStack
, HStack
, List
등을 사용합니다.
컨테이너의 기본 아이디어는 다음과 같습니다.
struct Container<Content: View>: View {
private let builder: () -> Content
init(@ViewBuilder _ builder: @escaping () -> Content) {
self.builder = builder
}
var body: some View {
builder()
}
}
특히 이 컨테이너는 아무것도 하지 않습니다. 내 말은, 이것은:
struct ContentView: View {
var body: some View {
Text("Some text")
.padding()
}
}
다음과 정확히 동일합니다.
struct ContentView: View {
var body: some View {
Container {
Text("Some text")
.padding()
}
}
}
둘 다 동일한 레이아웃을 생성합니다.
그러나 이것은 몇 가지 흥미로운 가능성의 기반입니다.
@ViewBuilder
그래서 당신은 생각하고 있을지도 모르지만 그 @ViewBuilder
속성 래퍼가 필요한 이유는 무엇입니까?
음, @ViewBuilder
속성 래퍼가 없는 경우 다음과 같은 일부 컨테이너에 있습니다.
struct Container<Content: View>: View {
private let builder: () -> Content
init(_ builder: @escaping () -> Content) {
self.builder = builder
}
var body: some View {
builder()
}
}
단 한 가지만 모든 것이 잘 작동합니다View
. Container
의 클로저 인수가 단일 뷰를 반환해야 하기 때문에 이와 같은 것을 렌더링하려고 하면 문제가 발생합니다.
struct ContentView: View {
var body: some View {
Container {
Text("Some text")
.padding()
Text("Some text")
.padding()
Text("Some text")
.padding()
}
}
}
처음에 표시된 것처럼 클로저에 @ViewBuilder
를 추가하면 이 문제가 해결됩니다.
사용 사례
형세
커스텀 컨테이너의 두 가지 용도에 대해 언급하겠습니다. 첫 번째는 레이아웃입니다. 뷰를 컨테이너로 보내고 컨테이너가 필요에 따라 정렬하도록 할 수 있습니다.
예를 들어:
struct TopArrangementContainer<Content: View>: View {
private let builder: () -> Content
init(@ViewBuilder _ builder: @escaping () -> Content) {
self.builder = builder
}
var body: some View {
VStack {
builder()
Spacer()
}
}
}
struct ContentView: View {
var body: some View {
TopArrangementContainer {
Text("Some text")
.padding()
Text("Some text")
.padding()
Text("Some text")
.padding()
}
}
}
이렇게 하면 다음 보기가 생성됩니다.
물론 적용할 수 있는 더 많은 흥미로운 레이아웃이 있지만 그 중 하나를 보여드리고 싶었습니다.
데이터 가져오기
Render Props는 React(https://reactjs.org/docs/render-props.html)의 개념입니다. React에서는 기본적으로 컨테이너에 클로저를 보낼 수 있습니다. 이 클로저는 일부 매개변수를 사용하고 이를 기반으로 보기를 빌드합니다.
이것은 많은 상황에서 유용할 수 있습니다. GeometryReader
는 이 디자인 패턴의 아주 좋은 예입니다.
데이터 가져오기를 위해 비슷한 작업을 수행해 보겠습니다. JSONPlaceholder에서 사용자를 가져오고 싶다고 상상해 보십시오: https://jsonplaceholder.typicode.com/users
그래서 뷰 모델로 사용할 모델과 ObservableObject
를 정의합니다.
struct User: Codable, Identifiable {
let id: Int
let name: String?
}
final class UsersViewModel: ObservableObject {
@Published var users: [User] = []
private var cancellables = Set<AnyCancellable>()
init() {
fetch()
}
private func fetch() {
URLSession.shared
.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/users")!)
.map(\.data)
.decode(type: [User].self, decoder: JSONDecoder())
.replaceError(with: [])
.assign(to: \.users, on: self)
.store(in: &cancellables)
}
}
꽤 직설적 인. User
구조체는 API에서 가져온 데이터를 보유할 모델이고 UsersViewModel
는 사용자를 가져와서 @Published
변수에 저장하는 클래스입니다. UsersViewModel
에 대한 중요한 점은 예를 들어 @StateObject
에서 Container
로 사용할 수 있다는 것입니다. 해보자:
struct UsersProvider<Content: View>: View {
@StateObject private var viewModel = UsersViewModel()
private let builder: ([User]) -> Content
init(@ViewBuilder _ builder: @escaping ([User]) -> Content) {
self.builder = builder
}
var body: some View {
builder(viewModel.users)
}
}
여기서 강조해야 할 몇 가지 중요한 사항:
struct Container<Content: View>: View {
private let builder: () -> Content
init(@ViewBuilder _ builder: @escaping () -> Content) {
self.builder = builder
}
var body: some View {
builder()
}
}
struct ContentView: View {
var body: some View {
Text("Some text")
.padding()
}
}
struct ContentView: View {
var body: some View {
Container {
Text("Some text")
.padding()
}
}
}
그래서 당신은 생각하고 있을지도 모르지만 그
@ViewBuilder
속성 래퍼가 필요한 이유는 무엇입니까?음,
@ViewBuilder
속성 래퍼가 없는 경우 다음과 같은 일부 컨테이너에 있습니다.struct Container<Content: View>: View {
private let builder: () -> Content
init(_ builder: @escaping () -> Content) {
self.builder = builder
}
var body: some View {
builder()
}
}
단 한 가지만 모든 것이 잘 작동합니다
View
. Container
의 클로저 인수가 단일 뷰를 반환해야 하기 때문에 이와 같은 것을 렌더링하려고 하면 문제가 발생합니다.struct ContentView: View {
var body: some View {
Container {
Text("Some text")
.padding()
Text("Some text")
.padding()
Text("Some text")
.padding()
}
}
}
처음에 표시된 것처럼 클로저에
@ViewBuilder
를 추가하면 이 문제가 해결됩니다.사용 사례
형세
커스텀 컨테이너의 두 가지 용도에 대해 언급하겠습니다. 첫 번째는 레이아웃입니다. 뷰를 컨테이너로 보내고 컨테이너가 필요에 따라 정렬하도록 할 수 있습니다.
예를 들어:
struct TopArrangementContainer<Content: View>: View {
private let builder: () -> Content
init(@ViewBuilder _ builder: @escaping () -> Content) {
self.builder = builder
}
var body: some View {
VStack {
builder()
Spacer()
}
}
}
struct ContentView: View {
var body: some View {
TopArrangementContainer {
Text("Some text")
.padding()
Text("Some text")
.padding()
Text("Some text")
.padding()
}
}
}
이렇게 하면 다음 보기가 생성됩니다.
물론 적용할 수 있는 더 많은 흥미로운 레이아웃이 있지만 그 중 하나를 보여드리고 싶었습니다.
데이터 가져오기
Render Props는 React(https://reactjs.org/docs/render-props.html)의 개념입니다. React에서는 기본적으로 컨테이너에 클로저를 보낼 수 있습니다. 이 클로저는 일부 매개변수를 사용하고 이를 기반으로 보기를 빌드합니다.
이것은 많은 상황에서 유용할 수 있습니다. GeometryReader
는 이 디자인 패턴의 아주 좋은 예입니다.
데이터 가져오기를 위해 비슷한 작업을 수행해 보겠습니다. JSONPlaceholder에서 사용자를 가져오고 싶다고 상상해 보십시오: https://jsonplaceholder.typicode.com/users
그래서 뷰 모델로 사용할 모델과 ObservableObject
를 정의합니다.
struct User: Codable, Identifiable {
let id: Int
let name: String?
}
final class UsersViewModel: ObservableObject {
@Published var users: [User] = []
private var cancellables = Set<AnyCancellable>()
init() {
fetch()
}
private func fetch() {
URLSession.shared
.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/users")!)
.map(\.data)
.decode(type: [User].self, decoder: JSONDecoder())
.replaceError(with: [])
.assign(to: \.users, on: self)
.store(in: &cancellables)
}
}
꽤 직설적 인. User
구조체는 API에서 가져온 데이터를 보유할 모델이고 UsersViewModel
는 사용자를 가져와서 @Published
변수에 저장하는 클래스입니다. UsersViewModel
에 대한 중요한 점은 예를 들어 @StateObject
에서 Container
로 사용할 수 있다는 것입니다. 해보자:
struct UsersProvider<Content: View>: View {
@StateObject private var viewModel = UsersViewModel()
private let builder: ([User]) -> Content
init(@ViewBuilder _ builder: @escaping ([User]) -> Content) {
self.builder = builder
}
var body: some View {
builder(viewModel.users)
}
}
여기서 강조해야 할 몇 가지 중요한 사항:
struct TopArrangementContainer<Content: View>: View {
private let builder: () -> Content
init(@ViewBuilder _ builder: @escaping () -> Content) {
self.builder = builder
}
var body: some View {
VStack {
builder()
Spacer()
}
}
}
struct ContentView: View {
var body: some View {
TopArrangementContainer {
Text("Some text")
.padding()
Text("Some text")
.padding()
Text("Some text")
.padding()
}
}
}
struct User: Codable, Identifiable {
let id: Int
let name: String?
}
final class UsersViewModel: ObservableObject {
@Published var users: [User] = []
private var cancellables = Set<AnyCancellable>()
init() {
fetch()
}
private func fetch() {
URLSession.shared
.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/users")!)
.map(\.data)
.decode(type: [User].self, decoder: JSONDecoder())
.replaceError(with: [])
.assign(to: \.users, on: self)
.store(in: &cancellables)
}
}
struct UsersProvider<Content: View>: View {
@StateObject private var viewModel = UsersViewModel()
private let builder: ([User]) -> Content
init(@ViewBuilder _ builder: @escaping ([User]) -> Content) {
self.builder = builder
}
var body: some View {
builder(viewModel.users)
}
}
UsersProvider
라고 합니다. 이 이름이 컨테이너가 하는 일을 좀 더 잘 설명하기 때문입니다. UsersViewModel
를 @StateObject
로 사용하고 있습니다. builder
클로저는 이제 [User]
배열을 매개변수로 사용합니다. 따라서 UsersViewModel
의 사용자가 변경될 때마다 상위View
에서 렌더링이 트리거됩니다. ContentView
에서 다음과 같이 리팩터링할 수 있습니다.struct ContentView: View {
var body: some View {
UsersProvider { users in
List(users) { user in
Text(user.name ?? "")
.padding()
}
}
}
}
따라서 API에서 얻은 사용자를 렌더링하는 방법을 결정할 수 있습니다.
요약
이것은 SwiftUI의 사용자 지정 컨테이너에 대한 빠른 소개였습니다. 시간이 지남에 따라 나타날 이 패턴에 대한 다른 많은 흥미로운 사용 사례가 있다고 확신하지만 레이아웃 및 데이터 가져오기 사용 사례는 매우 흥미롭고 유용한 것입니다.
Reference
이 문제에 관하여(SwiftUI의 사용자 지정 컨테이너), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/fmo91/data-containers-in-swiftui-e35
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
Reference
이 문제에 관하여(SwiftUI의 사용자 지정 컨테이너), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/fmo91/data-containers-in-swiftui-e35텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)