SwiftUI의 사용자 지정 컨테이너

17285 단어 swiftuiswiftios

컨테이너



인수로 전달된 다른 뷰를 렌더링할 수 있는 뷰에 대해 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)
    }
}


여기서 강조해야 할 몇 가지 중요한 사항:
  • 이 컨테이너는 이제 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의 사용자 지정 컨테이너에 대한 빠른 소개였습니다. 시간이 지남에 따라 나타날 이 패턴에 대한 다른 많은 흥미로운 사용 사례가 있다고 확신하지만 레이아웃 및 데이터 가져오기 사용 사례는 매우 흥미롭고 유용한 것입니다.

    좋은 웹페이지 즐겨찾기