无法在 SwiftUI 中创建简单的可选择标签视图

Posted

技术标签:

【中文标题】无法在 SwiftUI 中创建简单的可选择标签视图【英文标题】:Can't create a simple selectable tag view in SwiftUI 【发布时间】:2021-12-04 03:25:25 【问题描述】:

我对 SwfitUI 不太熟悉。

我找到了this helper 来实现这个标签视图:

但是让它可选择是痛苦的,我所做的一切,我都遇到了很多错误......

我怎样才能让这个东西工作!?

这是我的完整课程:

import SwiftUI

struct TagViewItem: Hashable 

var title: String
var isSelected: Bool

static func == (lhs: TagViewItem, rhs: TagViewItem) -> Bool 
    return lhs.isSelected == rhs.isSelected


func hash(into hasher: inout Hasher) 
    hasher.combine(title)
    hasher.combine(isSelected)


struct TagView: View 
    @State var tags: [TagViewItem]
    @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(tags.indices)  index in
            item(for: tags[index].title, isSelected: &tags[index].isSelected)
                .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, isSelected: inout Bool) -> some View 
    Text(text)
        .foregroundColor(isSelected ? Colors.primaryBarBackground : Colors.textColor)
        .padding()
        .lineLimit(1)
        .background(isSelected ? Colors.primaryBlue : Colors.primaryBarBackground)
        .frame(height: 36)
        .cornerRadius(18)
        .overlay(Capsule().stroke(Colors.primaryBlue, lineWidth: 4))
        .onTapGesture 
            isSelected.toggle()
        


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
    


【问题讨论】:

【参考方案1】:

经过一番折腾,终于有了工作版:

struct TagView: View 
@State var tags: [TagViewItem]
@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(tags.indices)  index in
            item(for: tags[index].title, isSelected: tags[index].isSelected)
                .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 tags[index].title == self.tags.last!.title 
                        width = 0 //last item
                     else 
                        width -= d.width
                    
                    return result
                )
                .alignmentGuide(.top, computeValue: d in
                    let result = height
                    if tags[index].title == self.tags.last!.title 
                        height = 0 // last item
                    
                    return result
                ).onTapGesture 
                    tags[index].isSelected.toggle()
                
        
    .background(viewHeightReader($totalHeight))


private func item(for text: String, isSelected: Bool) -> some View 
    Text(text)
        .foregroundColor(isSelected ? Colors.primaryBarBackground : Colors.textColor)
        .padding()
        .lineLimit(1)
        .background(isSelected ? Colors.primaryBlue : Colors.primaryBarBackground)
        .frame(height: 36)
        .cornerRadius(18)
        .overlay(Capsule().stroke(Colors.primaryBlue, lineWidth: 1))


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
    


用法:

TagView(tags: [TagViewItem(title: "ff", isSelected: false), TagViewItem(title: "yyhuuuh", isSelected: false), TagViewItem(title: "kjhgdtfyughuihu", isSelected: true), TagViewItem(title: "nbyvyvuyv", isSelected: false)])

【讨论】:

以上是关于无法在 SwiftUI 中创建简单的可选择标签视图的主要内容,如果未能解决你的问题,请参考以下文章

如何在 swiftUI 中创建一个普通视图

ScrollView 或 List,如何制作特定的可滚动视图 (SwiftUI)

无法使用 SwiftUI 使选取器居中

如何在使用自定义用户代理的 SwiftUI 中创建 Safari 视图?

SwiftUI |在所有视图中可用的模型或类中创建错误消息

隐藏 SwiftUI DisclosureGroup 箭头并删除默认填充