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

SwiftUI中如何实现双文本自适应同排/换行布局?

SwiftUI实现自适应文本布局方案

针对你提出的长文本与短文本的自适应布局需求(最后一行空间足够时短文本嵌入末尾,不足时上下排列),可以借助iOS 17+引入的TextRenderer API结合自定义Layout协议实现,具体方案如下:

核心思路

  1. 用TextRenderer解析长文本的布局细节,获取每一行的宽度和位置信息。
  2. 计算容器宽度下长文本最后一行的剩余空间,对比短文本的实际宽度。
  3. 通过自定义Layout动态调整布局:空间足够时将短文本放置在长文本最后一行末尾;不足时采用上下排列的布局。

完整代码实现

import SwiftUI

// 自定义布局,处理自适应文本排列
struct AdaptiveTextLayout: Layout {
    let longText: String
    let shortText: String
    let shortTextFont: Font
    
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let containerWidth = proposal.width ?? UIScreen.main.bounds.width
        let longTextRenderer = TextRenderer(Text(longText))
        let longLayout = longTextRenderer.layout(size: .init(width: containerWidth, height: .infinity))
        let shortTextSize = subviews[1].sizeThatFits(proposal)
        
        guard let lastLine = longLayout.lines.last else {
            return CGSize(width: containerWidth, height: shortTextSize.height)
        }
        
        let remainingWidth = containerWidth - lastLine.size.width
        if remainingWidth >= shortTextSize.width {
            return CGSize(width: containerWidth, height: longLayout.totalSize.height)
        } else {
            let longTextHeight = subviews[0].sizeThatFits(proposal).height
            return CGSize(width: containerWidth, height: longTextHeight + shortTextSize.height + 8) // 8为上下文本间距
        }
    }
    
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        let containerWidth = bounds.width
        let longTextRenderer = TextRenderer(Text(longText))
        let longLayout = longTextRenderer.layout(size: .init(width: containerWidth, height: .infinity))
        let shortTextSize = subviews[1].sizeThatFits(proposal)
        
        guard let lastLine = longLayout.lines.last else {
            // 长文本为空时,短文本右对齐放置
            subviews[1].place(at: CGPoint(x: bounds.maxX - shortTextSize.width, y: bounds.minY), proposal: proposal)
            return
        }
        
        let remainingWidth = containerWidth - lastLine.size.width
        if remainingWidth >= shortTextSize.width {
            // 放置长文本
            subviews[0].place(at: bounds.origin, proposal: proposal)
            // 计算短文本位置:最后一行末尾垂直居中
            let shortTextX = bounds.minX + lastLine.size.width + (remainingWidth - shortTextSize.width)
            let totalLineHeight = longLayout.lines.dropLast().reduce(0) { $0 + $1.size.height }
            let shortTextY = bounds.minY + totalLineHeight + (lastLine.size.height - shortTextSize.height)/2
            subviews[1].place(at: CGPoint(x: shortTextX, y: shortTextY), proposal: proposal)
        } else {
            // 上下排列:长文本左对齐,短文本右对齐
            subviews[0].place(at: bounds.origin, proposal: proposal)
            let longTextHeight = subviews[0].sizeThatFits(proposal).height
            subviews[1].place(at: CGPoint(x: bounds.maxX - shortTextSize.width, y: bounds.minY + longTextHeight + 8), proposal: proposal)
        }
    }
}

// 封装成可复用的View
struct AdaptiveTextPair: View {
    let longText: String
    let shortText: String
    let shortTextFont: Font = .caption
    
    var body: some View {
        AdaptiveTextLayout(longText: longText, shortText: shortText, shortTextFont: shortTextFont) {
            Text(longText)
            Text(shortText)
                .font(shortTextFont)
        }
    }
}

// 使用示例
struct TextTest: View {
    var body: some View {
        AdaptiveTextPair(
            longText: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sa laborum.",
            shortText: "(c) 2024"
        )
        .padding()
        Spacer()
    }
}

说明

  • 该方案依赖iOS 17及以上系统,因为TextRenderer是iOS 17新增的API,用于解析文本的底层布局信息。
  • 自定义Layout协议负责完成布局尺寸计算和子视图位置摆放,确保两种场景的自动切换。
  • 之前尝试的文本拼接无法单独控制短文本的对齐和字体;SwiftUI的AttributedString暂不支持段落级对齐控制,因此需要通过TextRenderer手动计算布局来实现需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 16:15:53