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


- 当前实现效果:


初始问题代码
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
相关产品推荐
相关产品推荐

