You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI:如何布局带点击手势的多Text视图并实现自动换行?

解决方案

要实现带独立手势的Text视图按段落流布局(内联自动换行),你需要自定义布局来处理每个Text的内联排列逻辑,让每个文本片段既能独立响应手势,又能像普通段落一样自动换行。

自定义内联布局实现

struct InlineTextLayout: Layout {
    // 文本片段之间的间距
    let interItemSpacing: CGFloat = 4
    
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        guard let containerWidth = proposal.width else { return .zero }
        
        var currentX: CGFloat = 0
        var currentY: CGFloat = 0
        var currentLineMaxHeight: CGFloat = 0
        
        for subview in subviews {
            // 计算当前子视图在剩余宽度下的实际尺寸(自动换行后的大小)
            let availableWidth = containerWidth - currentX
            let subSize = subview.sizeThatFits(ProposedViewSize(width: availableWidth, height: .infinity))
            currentLineMaxHeight = max(currentLineMaxHeight, subSize.height)
            
            // 剩余空间不足,换行
            if currentX + subSize.width + interItemSpacing > containerWidth {
                currentY += currentLineMaxHeight
                currentX = 0
                currentLineMaxHeight = subSize.height
            }
            
            currentX += subSize.width + interItemSpacing
        }
        
        // 加上最后一行的高度
        currentY += currentLineMaxHeight
        return CGSize(width: containerWidth, height: currentY)
    }
    
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        let containerWidth = bounds.width
        var currentX: CGFloat = bounds.minX
        var currentY: CGFloat = bounds.minY
        var currentLineMaxHeight: CGFloat = 0
        
        for subview in subviews {
            let availableWidth = containerWidth - (currentX - bounds.minX)
            let subSize = subview.sizeThatFits(ProposedViewSize(width: availableWidth, height: .infinity))
            currentLineMaxHeight = max(currentLineMaxHeight, subSize.height)
            
            // 当前行放不下,换行处理
            if currentX + subSize.width > bounds.maxX {
                currentY += currentLineMaxHeight
                currentX = bounds.minX
                currentLineMaxHeight = subSize.height
                
                // 重新计算换行后的子视图尺寸(使用整行宽度)
                let wrappedSubSize = subview.sizeThatFits(ProposedViewSize(width: containerWidth, height: .infinity))
                subview.place(
                    at: CGPoint(x: currentX, y: currentY),
                    anchor: .topLeading,
                    proposal: ProposedViewSize(width: containerWidth, height: wrappedSubSize.height)
                )
                currentX += wrappedSubSize.width + interItemSpacing
            } else {
                // 当前行可以容纳,直接放置
                subview.place(
                    at: CGPoint(x: currentX, y: currentY),
                    anchor: .topLeading,
                    proposal: ProposedViewSize(width: availableWidth, height: subSize.height)
                )
                currentX += subSize.width + interItemSpacing
            }
        }
    }
}

使用方式

将带手势的Text视图放入自定义布局中即可:

struct ContentView: View {
    // 从API获取的字符串列表
    let sentenceList = [
        "这是一段较长的测试文本,用来验证内联布局的自动换行效果",
        "这是第二个独立的文本片段,带自己的点击手势",
        "第三个文本片段,长度足够长时会自动换行到下一行,同时保持和前一段的内联排列"
    ]
    
    var body: some View {
        InlineTextLayout(interItemSpacing: 6) {
            ForEach(sentenceList, id: \.self) { sentence in
                Text(sentence)
                    .onTapGesture {
                        print("点击了:\(sentence)")
                    }
                    .onLongPressGesture(minimumDuration: 0.5) {
                        print("长按了:\(sentence)")
                    }
                    .background(Color.red.opacity(0.1)) // 可选:显示文本范围,方便调试
            }
        }
        .padding()
        .frame(maxWidth: .infinity)
    }
}

为什么你的方案有问题

  1. 方案1:将每个Text视为固定宽度的块级元素,当行尾空间不足时直接将整个Text换行,没有让Text自身内容换行,导致文本片段无法内联排列。
  2. 方案2:尝试让Text换行,但位置计算逻辑错误,没有正确更新换行后的x/y坐标,导致后续视图位置混乱。

效果说明

  • 每个Text保持独立的点击/长按手势。
  • 文本片段会像普通段落一样内联排列,当行尾空间不足时,当前Text会自动换行,后续Text会接着在当前行剩余空间或新行排列。

内容的提问来源于stack exchange,提问作者avimatt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 18:00:54