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

SwiftUI聊天应用:消息向上重叠、高度固定问题解决方案咨询

问题解决方案

1. 动态适配消息框高度

你当前的SelectableTextView设置了固定高度frame(width: 400, height: 100),这是文本重叠的核心原因,需修改为自适应高度:

修改SelectableTextView

让NSTextView支持自动布局,自动计算内容高度:

struct SelectableTextView: NSViewRepresentable {
  let text: String
  let font: NSFont?

  init(text: String, font: NSFont? = nil) {
    self.text = text
    self.font = font
  }

  func makeNSView(context: Context) -> NSTextView {
    let textView = NSTextView(frame: .zero)
    textView.isEditable = false
    textView.isSelectable = true
    textView.backgroundColor = .clear
    textView.font = font ?? NSFont.systemFont(ofSize: NSFont.systemFontSize)
    textView.string = text
    // 开启自动布局,让textView自适应内容尺寸
    textView.translatesAutoresizingMaskIntoConstraints = false
    textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
    textView.setContentHuggingPriority(.defaultHigh, for: .vertical)
    
    textView.selectedTextAttributes = [
      NSAttributedString.Key.foregroundColor: NSColor.systemGreen,
      NSAttributedString.Key.backgroundColor: NSColor.systemRed
    ]
    textView.delegate = context.coordinator
    return textView
  }

  func updateNSView(_ nsView: NSTextView, context: Context) {
    nsView.string = text
    nsView.font = font ?? NSFont.systemFont(ofSize: NSFont.systemFontSize)
    nsView.invalidateIntrinsicContentSize()
  }
  
  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
  
  class Coordinator: NSObject, NSTextViewDelegate {
    var parent: SelectableTextView
    
    init(_ parent: SelectableTextView) {
      self.parent = parent
    }
    
    func textDidChange(_ notification: Notification) {
      guard let textView = notification.object as? NSTextView else { return }
      textView.invalidateIntrinsicContentSize()
    }
  }
}

修改messageRow中的调用

移除固定高度约束,让SwiftUI自动适配:

// 原代码
SelectableTextView(text: component).frame(width: 400, height: 100)
// 修改为
SelectableTextView(text: component)
  .frame(maxWidth: .infinity, alignment: .leading)

2. 实现消息向下生成并自动滚动

通过ScrollViewReader结合滚动逻辑,实现新消息在底部展示并自动滚动:

消息列表容器结构

假设你有@State var messages: [MessageRow]存储消息,用以下结构包裹列表:

ScrollViewReader { proxy in
  ScrollView {
    LazyVStack(spacing: 0) {
      // 反转数组,让最新消息显示在底部
      ForEach(messages.reversed()) { message in
        MessageRowView(message: message, retryCallback: { msg in
          // 你的重试逻辑
        })
        .id(message.id) // 必须设置唯一ID用于滚动定位
      }
    }
  }
  .onChange(of: messages) { _ in
    // 消息更新后自动滚动到底部
    guard let lastMessageId = messages.last?.id else { return }
    withAnimation {
      proxy.scrollTo(lastMessageId, anchor: .bottom)
    }
  }
  .onAppear {
    // 初始加载时滚动到最新消息
    guard let lastMessageId = messages.last?.id else { return }
    proxy.scrollTo(lastMessageId, anchor: .bottom)
  }
}

关键注意点

  • 确保MessageRow有唯一id属性(如UUID),否则ScrollViewReader无法准确定位。
  • 若消息数组按发送顺序存储(最早消息在首位),反转数组后最新消息会出现在列表底部,符合常规聊天布局。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 08:39:59