import SwiftUI struct ChatMessage: Identifiable { var id: UUID = .init() var sender: Sender var content: String enum Sender { case me case other } } let lorem = "Lorem ipsum dolor sit amet. " let sampleMessages: [ChatMessage] = [ .init(sender: .me, content: String(repeating: lorem, count: 10)), .init(sender: .other, content: String(repeating: lorem, count: 5)), .init(sender: .me, content: String(repeating: lorem, count: 3)), .init(sender: .other, content: String(repeating: lorem, count: 8)), ] struct ChatBubblesList: View { var messages: [ChatMessage] @ScaledMetric(relativeTo: .body) private var textSize: CGFloat = 18 @Environment(\.debugLayout) private var debugLayout: Bool var body: some View { ScrollView { LazyVStack(spacing: debugLayout ? 80 : 40) { ForEach(messages) { message in ChatBubble(message: message) .textSelection(.enabled) } } .font(.system(size: textSize)) .padding() .padding(.bottom, 96) } .navigationTitle("Chat") .animation(.default, value: debugLayout) } } @MainActor struct ChatBubble: View { var message: ChatMessage @Environment(\.debugLayout) private var debugLayout: Bool var body: some View { VStack { let alignment: Alignment = message.sender == .me ? .trailing : .leading let bubbleColor: Color = message.sender == .me ? Color("chat-bubble-tint") : Color("chat-bubble-neutral") let textColor: Color = message.sender == .me ? .white : .primary let content = Text(message.content) .redacted(reason: .placeholder) .padding(.vertical, 8) .padding(.horizontal, 16) content .foregroundStyle(textColor) .background(bubbleColor, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) .frame(maxWidth: 400) .debugOverlay("maxW=400", color: .orange, alignment: .bottom, offset: -40) .relativeProposed(width: 0.8) .debugOverlay("relW=80 %", color: .red, alignment: .bottom, offset: -16) .frame(maxWidth: .infinity, alignment: alignment) .debugOverlay("maxW=infinity", color: .purple, alignment: .bottom, offset: 16) } } } struct ChatBubblesList_Previews: PreviewProvider { static var previews: some View { ChatBubblesList(messages: sampleMessages) .debugLayout(true) .previewLayout(.fixed(width: 900, height: 1000)) } }