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

SwiftUI实现强制文本固定为2行的方案(替代Jetpack Compose)

SwiftUI 强制文本固定显示2行的布局实现方案

要实现类似Jetpack Compose中Modifier.width(intrinsicSize.min)结合lineLimit的效果——让文本强制显示为2行,容器宽度仅适配最长行的宽度,且无需硬编码换行符,可通过以下两种布局方式实现:

方案1:自定义布局容器

自定义一个布局容器,通过测量文本的单行与两行状态下的尺寸,动态约束容器宽度为最长行的宽度,同时强制文本拆分为2行:

struct TwoLineTextLayout: Layout {
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        guard let textSubview = subviews.first else { return .zero }
        
        // 测量文本单行完整宽度
        let singleLineSize = textSubview.sizeThatFits(.init(width: .infinity, height: .infinity))
        // 模拟文本拆分为两行后的宽度(取单行宽度的一半作为参考,测量实际换行后的宽度)
        let halfWidthProposal = ProposedViewSize(width: singleLineSize.width / 2, height: .infinity)
        let firstLineWidth = textSubview.sizeThatFits(halfWidthProposal).width
        let secondLineWidth = textSubview.sizeThatFits(halfWidthProposal).width
        
        // 容器宽度取两行中较长的那一行宽度,高度取两行总高度
        let targetWidth = max(firstLineWidth, secondLineWidth)
        let twoLineHeight = textSubview.sizeThatFits(.init(width: targetWidth, height: .infinity)).height
        return CGSize(width: targetWidth, height: twoLineHeight)
    }
    
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        guard let textSubview = subviews.first else { return }
        let textSize = textSubview.sizeThatFits(.init(width: bounds.width, height: .infinity))
        // 居中放置文本
        let origin = CGPoint(
            x: bounds.midX - textSize.width / 2,
            y: bounds.midY - textSize.height / 2
        )
        textSubview.place(at: origin, proposal: .init(size: textSize))
    }
}

// 使用示例
struct TwoLineText: View {
    let text: String
    
    var body: some View {
        Text(text)
            .lineLimit(2)
            .fixedSize(horizontal: false, vertical: true)
            .layout(TwoLineTextLayout())
    }
}

方案2:结合GeometryReader与动态宽度计算

利用GeometryReader和文本尺寸测量,动态计算出刚好让文本拆分为2行的最小宽度,再约束文本容器到该宽度:

struct ForcedTwoLineText: View {
    let text: String
    
    @State private var targetWidth: CGFloat = .zero
    
    var body: some View {
        Text(text)
            .lineLimit(2)
            .fixedSize(horizontal: false, vertical: true)
            .frame(width: targetWidth)
            .background(
                // 隐藏的单行文本用于获取完整宽度
                Text(text)
                    .hidden()
                    .fixedSize()
                    .onSizeChange { size in
                        // 初始取单行宽度的一半作为参考,再测量换行后的实际最长行宽度
                        let initialProposedWidth = size.width / 2
                        let tempTextSize = Text(text)
                            .lineLimit(2)
                            .fixedSize(horizontal: false, vertical: true)
                            .sizeThatFits(.init(width: initialProposedWidth, height: .infinity))
                        targetWidth = tempTextSize.width
                    }
            )
    }
}

// 扩展View获取尺寸变化
extension View {
    func onSizeChange(_ perform: @escaping (CGSize) -> Void) -> some View {
        background(
            GeometryReader { proxy in
                Color.clear
                    .preference(key: SizePreferenceKey.self, value: proxy.size)
            }
        )
        .onPreferenceChange(SizePreferenceKey.self, perform: perform)
    }
}

struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

关键说明

  • 两种方案均无需修改原字符串,完全通过布局约束实现强制两行的效果,适配多语言(包括非拉丁字符)场景。
  • 必须设置lineLimit(2)确保文本最多显示2行,fixedSize(horizontal: false, vertical: true)让文本高度自适应两行内容。
  • 方案1的自定义布局更稳定,适合复杂场景;方案2代码更简洁,适合快速实现。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 17:00:39