Передача нескольких вложенных представлений в представление в SwiftUI

#swiftui

#свифтуи

Вопрос:

Я создал пакет Swift, который создает несколько просмотров таблиц страниц из массива передаваемого ему содержимого:

 import SwiftUI

public struct WhatsNewView<Content: View>: View {
    
    let content: [Content]
    
    public init(content: [Content]){
        self.content = content
    }
        
    public var body: some View {
        TabView {
            ForEach(0..<content.count, id: .self) { pageNum in
                WhatsNewPage(content: content[pageNum], pageNum: pageNum   1, totalPages: content.count)
            }
        }
        .background(Color.white)
        .ignoresSafeArea()
        .tabViewStyle(PageTabViewStyle())
        .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
    }
}
 

Когда я вызываю его, я хочу передать любое простое или сложное представление для заполнения каждой страницы. Прямо сейчас я могу передать массив простых текстовых представлений следующим образом:

 import SwiftUI
import WhatsNew

@main
struct mFood_Vendor: App {
    @State var showWhatsNew = false
    let whatsNew = WhatsNew()

    var page1 = Text("Hello World")

    var page2 = Text("Goodbye World")

    var body: some Scene {
        WindowGroup {
            ContentView()
            .fullScreenCover(isPresented: $showWhatsNew, content: {
                let content = [page1, page2]
                WhatsNewView(content: content)
            })
            .onAppear(perform: {
                whatsNew.checkForUpdate(showWhatsNew: $showWhatsNew)
            })

        }
    }
}
 

Я хочу, чтобы page1 и page2 были любым контентом, который человек хочет видеть на страницах «Что нового». Но если я изменю эти переменные на что-то другое, например, текст и изображение, я получаю сообщение об ошибке «Не удалось выполнить диагностику выражения».

В идеале я хотел бы иметь возможность передавать что-то вроде:

 struct page1: View {
    var body: some View {
      VStack {
        Text("something")
        Image("plus")
       }
     }
}
 

Любая помощь будет признательна. Спасибо!

Ответ №1:

Вы можете использовать AnyView , чтобы получить то, что вы хотите. В этом случае ваш код станет:

 public struct WhatsNewView: View {

    let content: [AnyView]

    public init(content: [AnyView]){
        self.content = content
    }

    public var body: some View {
        TabView {
            ForEach(0..<content.count, id: .self) { pageNum in
                WhatsNewPage(content: content[pageNum], pageNum: pageNum   1, totalPages: content.count)
            }
        }
        .background(Color.white)
        .ignoresSafeArea()
        .tabViewStyle(PageTabViewStyle())
        .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
    }
}
 

И, в качестве примера использования:

 struct ContentView: View {
    var body: some View {
        let view1 = AnyView(Text("Hello World"))
        let view2 = AnyView(Image(systemName: "star.fill"))
        return WhatsNewView(content: [view1, view2])
    }
}
 

РЕДАКТИРОВАТЬ: я только что узнал, что TabView это может быть построено из TupleView . Это означает, что, в зависимости от ваших потребностей, вы можете написать что-то вроде этого (что было бы здорово, потому что это не заставляет пользователей вашего пакета Swift переносить все представления внутрь AnyView ):

 import SwiftUI

public struct WhatsNewView<Content: View>: View {
    private let content: Content

    public init(@ViewBuilder contentProvider: () -> Content){
        content = contentProvider()
    }

    public var body: some View {
        TabView {
            content
        }
        .background(Color.white)
        .ignoresSafeArea()
        .tabViewStyle(PageTabViewStyle())
        .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
    }
}
 

Вы можете использовать его следующим образом:

 struct ContentView: View {
    var body: some View {
        WhatsNewView {
            Text("Hello World")
            Image(systemName: "star.fill")
            Color.red
            VStack {
                Text("Text1")
                Text("Text2")
                Text("Text3")
            }
            Button("Tap Me") {
                print("Tapped")
            }
            Group {
                Text("Group1")
                Text("Group2")
            }
        }
    }
}
 

Результатом является:

введите описание изображения здесь

Ответ №2:

Оказывается, что если я оставлю WhatsNewView в покое, я могу просто сделать следующее:

 struct mFood_Vendor: App {
    @State var showWhatsNew = false
    let whatsNew = WhatsNew()

    var page1: some View {
        VStack (alignment: .leading){
            Text("Here is a list of new features:")
            Text("Hello World")
            Image(systemName: "plus")
        }
    }

    var page2: some View {
        Text("Goodbye World")
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
            .fullScreenCover(isPresented: $showWhatsNew, content: {
                let content = [AnyView(page1), AnyView(page2)]
                WhatsNewView (content: content)
            })
            .onAppear(perform: {
                whatsNew.checkForUpdate(showWhatsNew: $showWhatsNew)
            })

        }
    }
}
 

По сути, просто оберните каждую страницу содержимого в AnyView.

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

1. Взгляните на мое РЕДАКТИРОВАНИЕ, я думаю, оно может быть вам полезно.

2. Мило! Мне действительно нравится это решение! Спасибо.