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

SwiftUI实现WhatsApp风格聊天气泡 顶部双标签自适应宽度布局

SwiftUI 实现WhatsApp风格聊天气泡:顶部标签自适应消息宽度

核心需求

实现类似WhatsApp的聊天气泡组件,要求气泡顶部的两个标签间距随下方消息内容的实际宽度动态调整:一个标签对齐消息内容的左边缘,另一个对齐消息内容的右边缘。直接使用Spacer会将两个标签推到气泡允许的最大宽度两端,无法跟随文本实际宽度自适应。

效果参考

  • 目标效果:
    目标效果1
    目标效果2
  • 当前实现效果:
    当前实现效果1
    当前实现效果2

初始问题代码

import SwiftUI

struct TestingView: View {
    
    var body: some View {
        ZStack {
            /// header
            VStack(alignment: .trailing) {
                HStack {
                    HStack() {
                        Text("abcd")
    
                    }
                    HStack {
                        Text("~abcd")
                    }
                }.padding([.trailing, .leading], 15)
                    .fixedSize(horizontal: false, vertical: true)
                
                /// text
                HStack {
                    Text("Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!")
                    
                }.padding([.leading, .trailing], 15)
                
                /// timestamp
                HStack(alignment: .center) {
                    Text("12:00 PM")
                }.padding(.trailing,15)
                
            }.background(Color.gray)
                .padding(.leading, 15)
                .frame(maxWidth: 250, alignment: .leading)
            
        }
        
    }
}

struct TestingView_Previews: PreviewProvider {
    static var previews: some View {
        TestingView()
    }
}

实现方案

核心思路是通过PreferenceKey获取消息文本渲染后的实际宽度,将宽度值绑定给顶部标签容器,让标签栏宽度和消息文本宽度完全一致,此时标签栏内部的Spacer只会在文本宽度范围内排布,不会撑满整个气泡的最大允许宽度。

可直接运行的完整代码

import SwiftUI

// 自定义PreferenceKey用于传递子视图尺寸
struct MessageWidthPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

struct WhatsAppBubble: View {
    let messageContent: String
    let leftTopTag: String
    let rightTopTag: String
    let timeText: String
    private let maxBubbleWidth: CGFloat = 250
    
    @State private var actualTextWidth: CGFloat = 0
    
    var body: some View {
        VStack(alignment: .trailing, spacing: 4) {
            // 顶部标签栏,宽度与消息文本完全一致
            HStack {
                Text(leftTopTag)
                Spacer()
                Text(rightTopTag)
            }
            .frame(width: actualTextWidth)
            .padding(.horizontal, 15)
            
            // 消息正文
            Text(messageContent)
                .padding(.horizontal, 15)
                .background(
                    GeometryReader { proxy in
                        Color.clear
                            .preference(key: MessageWidthPreferenceKey.self, value: proxy.size.width)
                    }
                )
            
            // 时间戳
            Text(timeText)
                .font(.caption)
                .padding(.trailing, 15)
        }
        .padding(.vertical, 8)
        .background(Color.gray.opacity(0.7))
        .clipShape(RoundedRectangle(cornerRadius: 12))
        .padding(.leading, 15)
        .frame(maxWidth: maxBubbleWidth, alignment: .leading)
        .onPreferenceChange(MessageWidthPreferenceKey.self) { newWidth in
            actualTextWidth = newWidth
        }
    }
}

struct TestingView: View {
    var body: some View {
        VStack(spacing: 20) {
            // 短文本测试
            WhatsAppBubble(
                messageContent: "Hi",
                leftTopTag: "abcd",
                rightTopTag: "~abcd",
                timeText: "12:00 PM"
            )
            // 长文本换行测试
            WhatsAppBubble(
                messageContent: "Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!",
                leftTopTag: "abcd",
                rightTopTag: "~abcd",
                timeText: "12:00 PM"
            )
        }
    }
}

struct TestingView_Previews: PreviewProvider {
    static var previews: some View {
        TestingView()
    }
}

实现说明

  • 利用GeometryReader在消息文本的透明背景层读取文本实际渲染宽度,通过SwiftUI的偏好值机制将宽度传递给上层视图
  • 顶部标签栏直接绑定读取到的文本宽度,和正文保持完全一致的水平内边距,即可实现两个标签分别对齐正文左右边缘的效果
  • 短文本场景下标签栏会自动收缩宽度,长文本触达最大宽度换行时标签栏会自动匹配最大正文宽度,完全符合WhatsApp气泡的交互效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 18:33:26