web-dev-qa-db-fra.com

SwiftUI HStack avec enveloppe et hauteur dynamique

J'ai cette vue pour afficher les balises de texte sur plusieurs lignes que j'ai obtenues de SwiftUI HStack avec Wrap , mais lorsque je l'ajoute dans un VStack, les balises chevauchent toute autre vue que j'ai mise ci-dessous. Les balises s'affichent correctement, mais la hauteur de la vue elle-même n'est pas calculée à l'intérieur du VStack. Comment puis-je faire en sorte que cette vue utilise la hauteur de son contenu?

import SwiftUI

struct TestWrappedLayout: View {
    @State var platforms = ["Ninetendo", "XBox", "PlayStation", "PlayStation 2", "PlayStation 3", "PlayStation 4"]

    var body: some View {
        GeometryReader { geometry in
            self.generateContent(in: geometry)
        }
    }

    private func generateContent(in g: GeometryProxy) -> some View {
        var width = CGFloat.zero
        var height = CGFloat.zero

        return ZStack(alignment: .topLeading) {
            ForEach(self.platforms, id: \.self) { platform in
                self.item(for: platform)
                    .padding([.horizontal, .vertical], 4)
                    .alignmentGuide(.leading, computeValue: { d in
                        if (abs(width - d.width) > g.size.width)
                        {
                            width = 0
                            height -= d.height
                        }
                        let result = width
                        if platform == self.platforms.last! {
                            width = 0 //last item
                        } else {
                            width -= d.width
                        }
                        return result
                    })
                    .alignmentGuide(.top, computeValue: {d in
                        let result = height
                        if platform == self.platforms.last! {
                            height = 0 // last item
                        }
                        return result
                    })
            }
        }
    }

    func item(for text: String) -> some View {
        Text(text)
            .padding(.all, 5)
            .font(.body)
            .background(Color.blue)
            .foregroundColor(Color.white)
            .cornerRadius(5)
    }
}

struct TestWrappedLayout_Previews: PreviewProvider {
    static var previews: some View {
        TestWrappedLayout()
    }
}

Exemple de code:

struct ExampleTagsView: View {
    var body: some View {
        ScrollView {
            VStack(alignment: .leading) {
                Text("Platforms:")
                TestWrappedLayout()

                Text("Other Platforms:")
                TestWrappedLayout()
            }
        }
    }
}

struct ExampleTagsView_Previews: PreviewProvider {
    static var previews: some View {
        ExampleTagsView()
    }
}

Résultat: enter image description here

2
Lualdi Dylan

Ok, voici une variante un peu plus générique et améliorée (pour la solution initialement introduite dans SwiftUI HStack with Wrap )

Testé avec Xcode 11.4/iOS 13.4

Remarque: comme la hauteur de vue est calculée dynamiquement, le résultat fonctionne à l'exécution, pas en aperçu

enter image description here

struct TagCloudView: View {
    var tags: [String]

    @State private var totalHeight 
          = CGFloat.zero       // << variant for ScrollView/List
    //    = CGFloat.infinity   // << variant for VStack

    var body: some View {
        VStack {
            GeometryReader { geometry in
                self.generateContent(in: geometry)
            }
        }
        .frame(height: totalHeight)// << variant for ScrollView/List
        //.frame(maxHeight: totalHeight) // << variant for VStack
    }

    private func generateContent(in g: GeometryProxy) -> some View {
        var width = CGFloat.zero
        var height = CGFloat.zero

        return ZStack(alignment: .topLeading) {
            ForEach(self.tags, id: \.self) { tag in
                self.item(for: tag)
                    .padding([.horizontal, .vertical], 4)
                    .alignmentGuide(.leading, computeValue: { d in
                        if (abs(width - d.width) > g.size.width)
                        {
                            width = 0
                            height -= d.height
                        }
                        let result = width
                        if tag == self.tags.last! {
                            width = 0 //last item
                        } else {
                            width -= d.width
                        }
                        return result
                    })
                    .alignmentGuide(.top, computeValue: {d in
                        let result = height
                        if tag == self.tags.last! {
                            height = 0 // last item
                        }
                        return result
                    })
            }
        }.background(viewHeightReader($totalHeight))
    }

    private func item(for text: String) -> some View {
        Text(text)
            .padding(.all, 5)
            .font(.body)
            .background(Color.blue)
            .foregroundColor(Color.white)
            .cornerRadius(5)
    }

    private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
        return GeometryReader { geometry -> Color in
            let rect = geometry.frame(in: .local)
            DispatchQueue.main.async {
                binding.wrappedValue = rect.size.height
            }
            return .clear
        }
    }
}

struct TestTagCloudView : View {
    var body: some View {
        VStack {
            Text("Header").font(.largeTitle)
            TagCloudView(tags: ["Ninetendo", "XBox", "PlayStation", "PlayStation 2", "PlayStation 3", "PlayStation 4"])
            Text("Some other text")
            Divider()
            Text("Some other cloud")
            TagCloudView(tags: ["Apple", "Google", "Amazon", "Microsoft", "Oracle", "Facebook"])
        }
    }
}
5
Asperi

J'ai juste réussi à résoudre ce problème en déplaçant le GeometryReader jusqu'à ExampleTagsView et en utilisant platforms.first au lieu de last inside .alignmentGuide

Code complet:

import SwiftUI

struct ExampleTagsView: View {
    var body: some View {
        GeometryReader { geometry in
            ScrollView(.vertical) {
                VStack(alignment: .leading) {
                    Text("Platforms:")
                    TestWrappedLayout(geometry: geometry)

                    Text("Other Platforms:")
                    TestWrappedLayout(geometry: geometry)
                }
            }
        }
    }
}

struct ExampleTagsView_Previews: PreviewProvider {
    static var previews: some View {
        ExampleTagsView()
    }
}

struct TestWrappedLayout: View {
    @State var platforms = ["Ninetendo", "XBox", "PlayStation", "PlayStation 2", "PlayStation 3", "PlayStation 4", "PlayStation 5", "Ni", "Xct5Box", "PlayStatavtion", "PlvayStation 2", "PlayStatiadfon 3", "PlaySdatation 4", "PlaySdtation 5"]
    let geometry: GeometryProxy

    var body: some View {
        self.generateContent(in: geometry)
    }

    private func generateContent(in g: GeometryProxy) -> some View {
        var width = CGFloat.zero
        var height = CGFloat.zero

        return ZStack(alignment: .topLeading) {
            ForEach(self.platforms, id: \.self) { platform in
                self.item(for: platform)
                    .padding([.horizontal, .vertical], 4)
                    .alignmentGuide(.leading, computeValue: { d in
                        if (abs(width - d.width) > g.size.width)
                        {
                            width = 0
                            height -= d.height
                        }
                        let result = width
                        if platform == self.platforms.first! {
                            width = 0 //last item
                        } else {
                            width -= d.width
                        }
                        return result
                    })
                    .alignmentGuide(.top, computeValue: {d in
                        let result = height
                        if platform == self.platforms.first! {
                            height = 0 // last item
                        }
                        return result
                    })
            }
        }
    }

    func item(for text: String) -> some View {
        Text(text)
            .padding(.all, 5)
            .font(.body)
            .background(Color.blue)
            .foregroundColor(Color.white)
            .cornerRadius(5)
    }
}

Résultat: enter image description here

1
Lualdi Dylan