【问题标题】:How to make word in paragraph tappable w/ SwiftUI WITHOUT using .onOpenURL?如何在不使用 .onOpenURL 的情况下使用 SwiftUI 使段落中的单词可点击?
【发布时间】:2023-01-19 06:32:02
【问题描述】:

我需要在将通过 UIHostingController 在 UIKit 应用程序中使用的 SwiftUI 视图中的段落中创建一个可点击和带下划线的特定词。

我已经尝试过 .onOpenURL { } / 自定义 URL 方法和这个不管用在我的例子中,因为它会在我们应用程序的 UIKIT 端产生副作用,并且在传递 UIKit ViewController 中定义的函数时出现错误“发送到实例的无法识别的选择器......”

我找到了一个解决方法,通过将 onTapGesture 应用于包含文本的 HStack 来使整个段落可点击,但这并不理想......

有什么方法可以使用 Swift 的 AttributedString 使特定单词可点击或调用特定函数吗?

我也尝试过各种在 HStack 中制作 Text 的方法,但是如果将不同长度/单词组合传递给视图,结果永远不会产生统一的段落......

我也尝试将 .onTapGesture 添加到 HStack 中的特定文本视图,但出现错误...

错误**无法将“某些视图”类型的值转换为预期的参数类型“文本”

是否有不使用 .onOpenURL 的纯 SwiftUI 解决方案来使段落中的单个单词可点击?

是否可以将 AttributedString 与 .onTapGesture 结合使用来调用函数?

使用具有不同字符串长度的 HStack 组合的错误结果

**

private struct Paragraph: View {
    let actionLabel: String = "Action" // <- Underlined tappable word

    let actionLabelSecondary: String = "end of first sentence."

    let textBody: String = "Beginning of second sentence lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum"
    
    var body: some View {
        HStack {
            Text(actionLabel)
                .underline()
                .onTapGesture { // <- Cannot convert value of type 'some View' to expected argument type 'Text'
                    action()
                }
            + Text(" \(actionLabelSecondary) ")
            + Text(textBody)
        }
    }
    
    func action() {
        print("Word tapped")
    }
}

【问题讨论】:

    标签: swiftui


    【解决方案1】:

    最初我会选择 AttributedString 和 CustomURL,但您指出这对您的情况不起作用。

    所以这是另一种方法。我承认这感觉有点矫枉过正,但它确实有效。它基于自定义 FlowLayout(感谢 Majid),并使用它来将第一个标签显示为按钮,然后是其他文本标签的所有单个单词,因此它们可以根据视图大小自然换行。

    我没有测试与 UIKit 的集成,但它应该可以工作。就告诉我嘛。

    struct ContentView: View {
        let actionLabel: String = "Action" // <- Underlined tappable word
    
        let actionLabelSecondary: String = "end of first sentence."
    
        let textBody: String = "Beginning of second sentence lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum."
        
        var body: some View {
            FlowLayout {
                Button {
                    action()
                } label: {
                    Text(actionLabel + " ")
                        .underline()
                }
                
                ForEach(Array(zip(0..., actionLabelSecondary.components(separatedBy: " "))), id: .0) { word in
                    Text(word.1 + " ")
                }
                
                ForEach(Array(zip(0..., textBody.components(separatedBy: " "))), id: .0) { word in
                    Text(word.1 + " ")
                }
            }
            .padding()
        }
        
        func action() {
            print("Word tapped")
        }
    }
    
    
    struct FlowLayout: Layout {
        
        func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
            let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
            
            var totalHeight: CGFloat = 0
            var totalWidth: CGFloat = 0
            
            var lineWidth: CGFloat = 0
            var lineHeight: CGFloat = 0
            
              for size in sizes {
                if lineWidth + size.width > proposal.width ?? 0 {
                    totalHeight += lineHeight
                    lineWidth = size.width
                    lineHeight = size.height
                } else {
                    lineWidth += size.width
                    lineHeight = max(lineHeight, size.height)
                }
    
                totalWidth = max(totalWidth, lineWidth)
            }
    
            totalHeight += lineHeight
            
            return .init(width: totalWidth, height: totalHeight)
        }
        
        func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
                let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
                
                var lineX = bounds.minX
                var lineY = bounds.minY
                var lineHeight: CGFloat = 0
                
                for index in subviews.indices {
                    if lineX + sizes[index].width > (proposal.width ?? 0) {
                        lineY += lineHeight
                        lineHeight = 0
                        lineX = bounds.minX
                    }
                    
                    subviews[index].place(
                        at: .init(
                            x: lineX + sizes[index].width / 2,
                            y: lineY + sizes[index].height / 2
                        ),
                        anchor: .center,
                        proposal: ProposedViewSize(sizes[index])
                    )
                    
                    lineHeight = max(lineHeight, sizes[index].height)
                    lineX += sizes[index].width
                }
            }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-09-26
      • 1970-01-01
      • 2016-02-01
      • 1970-01-01
      • 2021-11-26
      • 1970-01-01
      • 2013-02-24
      • 2020-04-12
      • 2019-10-29
      相关资源
      最近更新 更多