Почему пользовательское закрытие контейнера не допускает разные представления?

#swift #swiftui

#swift #swiftui

Вопрос:

Я работаю над пользовательским контейнером, но получаю сообщение об ошибке:

Не удалось выполнить диагностику для выражения; пожалуйста, отправьте отчет об ошибке

с помощью следующего кода (местоположение ошибки находится внизу кода ниже):

 struct ZoomCardView<Content: View>: View {
    let contentDefault: ((Binding<Bool>)) -> Content
    let contentZoomed: (Binding<Bool>) -> Content
    
    init(@ViewBuilder defaultContent: @escaping (Binding<Bool>) -> Content, @ViewBuilder zoomed: @escaping (Binding<Bool>) -> Content) {
        self.contentDefault = defaultContent
        self.contentZoomed = zoomed
    }
    
    @State private var isShowing: Bool = false
    
    var body: some View {
        HStack {
            if isShowing {
                self.contentZoomed($isShowing)
                    .transition(.scale(scale: 0, anchor: .center))
            } else {
                self.contentDefault($isShowing)
                    .transition(.scale(scale: 0, anchor: .center))
            }
        }
    }
}

struct DefaultView: View {
    var body: some View {
        HStack {
            Text("Default")
        }
    }
}

struct ZoomView: View {
    var body: some View {
        HStack {
            Text("Zoom")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {   <----------- *** Error
        ZoomCardView { isShowing in
            DefaultView()
        } zoomed: { isShowing in
            ZoomView()
        }
        .preferredColorScheme(.dark)
    }
}
 

Я заметил, что ContentView_Previews , если я передаю одно и то же представление, ошибка исчезает. Например:

 struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ZoomCardView { isShowing in
            ZoomView()
        } zoomed: { isShowing in
            ZoomView()
        }
        .preferredColorScheme(.dark)
    }
}
 

Почему я не могу передавать разные представления, когда ZoomCardView ожидает параметр типа <Content: View> ? Что я хотел бы сделать, так это в конечном итоге указать a View или любой из типов стека (т. Е. ZStack VStack Или HStack ), Предоставляя callsite окончательный контроль над тем, что должно быть показано. Заранее спасибо!

Ответ №1:

Вам нужно использовать два общих параметра, если вы хотите передать два разных представления.

Вместо:

 struct ZoomCardView<Content: View>: View
 

используйте:

 struct ZoomCardView<ContentDefault, ContentZoomed>: View where ContentDefault: View, ContentZoomed: View
 

Полный пример:

 struct ZoomCardView<ContentDefault, ContentZoomed>: View where ContentDefault: View, ContentZoomed: View {
    let contentDefault: ((Binding<Bool>)) -> ContentDefault
    let contentZoomed: (Binding<Bool>) -> ContentZoomed
    
    init(
        @ViewBuilder defaultContent: @escaping (Binding<Bool>) -> ContentDefault,
        @ViewBuilder zoomed: @escaping (Binding<Bool>) -> ContentZoomed
    ) {
        self.contentDefault = defaultContent
        self.contentZoomed = zoomed
    }
    
    @State private var isShowing: Bool = false
    
    var body: some View {
        HStack {
            if isShowing {
                self.contentZoomed($isShowing)
                    .transition(.scale(scale: 0, anchor: .center))
            } else {
                self.contentDefault($isShowing)
                    .transition(.scale(scale: 0, anchor: .center))
            }
        }
    }
}
 

Комментарии:

1. Спасибо @pawello2222! Отлично работает. TBH, я все еще не понимаю, зачем это нужно. Если представления имеют тип View , а ZoomCardView был определен как <Content: View>: View , почему компилятор проводит различие? Мне это очень любопытно. Еще раз спасибо!

2. @titusmagnus Если вы посмотрите на init , он ожидает два закрытия, оба с одинаковым типом возвращаемого значения (Content ). Таким образом, вы можете передавать только один и тот же тип для обоих замыканий. А DefaultView и ZoomView имеют разные типы. Создание второго универсального типа позволяет вам использовать два разных типа для этих замыканий.

3. Это имеет смысл, но я думаю, что меня смущает то, что, поскольку оба они являются a View и init требуют Content типа View , то, даже если оба представления имеют другой тип, они все равно имеют тип View . Вы видите, в чем заключается мое замешательство? Еще раз спасибо!

4. @titusmagnus, я тоже заметил это в вашем предыдущем вопросе. Content определяется как общий тип, который представляет какой-то вид представления — конкретный вид определяется тем, как вызывается ваш init (например, может быть Text , или VStack<Text> , что бы это ни было). Но вам нужны два разных представления — каждое из них является инициализацией другого типа, поэтому для представления этого вам нужны два общих типа

5. @titusmagnus Но вы не можете использовать View как ограничение, поскольку это протокол с associatedtype . Я предлагаю вам прочитать общие и непрозрачные типы