我正在创建一个聊天窗口。我们目前正处于从Objective-C到SwiftUI的迁移阶段,我们至少支持iOS 13+。
要获得滚动视图的行为,我想指向底部始终作为默认值,应该能够无缝地向上和向下滚动。
这里唯一的问题是这里的滚动只工作时,我从泡沫的聊天从其他地方拖动它不工作。
我已经调试了很长时间,无法找到问题。
反向滚动视图代码,我从这里得到https://www.process-one.net/blog/writing-a-custom-scroll-view-with-swiftui-in-a-chat-application/
struct ReverseScrollView<Content>: View where Content: View {
@State private var contentHeight: CGFloat = CGFloat.zero
@State private var scrollOffset: CGFloat = CGFloat.zero
@State private var currentOffset: CGFloat = CGFloat.zero
var content: () -> Content
// Calculate content offset
func offset(outerheight: CGFloat, innerheight: CGFloat) -> CGFloat {
let totalOffset = currentOffset + scrollOffset
return -((innerheight/2 - outerheight/2) - totalOffset)
}
var body: some View {
GeometryReader { outerGeometry in
// Render the content
// ... and set its sizing inside the parent
self.content()
.modifier(ViewHeightKey())
.onPreferenceChange(ViewHeightKey.self) { self.contentHeight = $0 }
.frame(height: outerGeometry.size.height)
.offset(y: self.offset(outerheight: outerGeometry.size.height, innerheight: self.contentHeight))
.clipped()
.animation(.easeInOut)
.gesture(
DragGesture()
.onChanged({ self.onDragChanged($0) })
.onEnded({ self.onDragEnded($0, outerHeight: outerGeometry.size.height)}))
}
}
func onDragChanged(_ value: DragGesture.Value) {
// Update rendered offset
self.scrollOffset = (value.location.y - value.startLocation.y)
}
func onDragEnded(_ value: DragGesture.Value, outerHeight: CGFloat) {
// Update view to target position based on drag position
let scrollOffset = value.location.y - value.startLocation.y
let topLimit = self.contentHeight - outerHeight
// Negative topLimit => Content is smaller than screen size. We reset the scroll position on drag end:
if topLimit < 0 {
self.currentOffset = 0
} else {
// We cannot pass bottom limit (negative scroll)
if self.currentOffset + scrollOffset < 0 {
self.currentOffset = 0
} else if self.currentOffset + scrollOffset > topLimit {
self.currentOffset = topLimit
} else {
self.currentOffset += scrollOffset
}
}
self.scrollOffset = 0
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
extension ViewHeightKey: ViewModifier {
func body(content: Content) -> some View {
return content.background(GeometryReader { proxy in
Color.clear.preference(key: Self.self, value: proxy.size.height)
})
}
}
聊天窗口
ReverseScrollView {
VStack{
HStack {
VStack(spacing: 5){
Text("message.text")
.padding(.vertical, 8)
.padding(.horizontal)
.background(Color(.systemGray5))
.foregroundColor(.primary)
.clipShape(ChatBubble(isFromCurrentUser: false))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
.lineLimit(nil) // Allow unlimited lines
.lineSpacing(4) // Adjust line spacing as desired
.fixedSize(horizontal: false, vertical: true) // Allow vertical expansion
Text("ormatTime(message.timeUtc)")
.font(.caption)
.foregroundColor(.secondary)
.background(Color.red)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 5)
}
.background(Color.blue)
Spacer()
}
ForEach(Array(viewModel.chats.indices), id: \.self){ index in
let message = viewModel.chats[index]
VStack(alignment: .leading, spacing: 5) {
// Chat bubble view for received messages
if(message.isIncoming){
HStack {
VStack(spacing: 5){
Text(message.text)
.padding(.vertical, 8)
.padding(.horizontal)
.background(Color(.systemGray5))
.foregroundColor(.primary)
.clipShape(ChatBubble(isFromCurrentUser: false))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
.lineLimit(nil) // Allow unlimited lines
.lineSpacing(4) // Adjust line spacing as desired
.fixedSize(horizontal: false, vertical: true) // Allow vertical expansion
.frame(maxWidth: .infinity, alignment: .leading)
Text(formatTime(message.timeUtc))
.font(.caption)
.foregroundColor(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 5)
}
Spacer()
}
}else{
HStack {
Spacer()
VStack(spacing: 5){
Text(message.text)
.padding(.vertical, 8)
.padding(.horizontal)
.background(Color(.systemBlue))
.foregroundColor(.white)
.clipShape(ChatBubble(isFromCurrentUser: true))
.padding(.horizontal)
.lineLimit(nil) // Allow unlimited lines
.lineSpacing(4) // Adjust line spacing as desired
.fixedSize(horizontal: false, vertical: true) // Allow vertical expansion
Text(formatTime(message.timeUtc))
.font(.caption)
.foregroundColor(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 5)
}
.frame(maxWidth: .infinity, alignment: .trailing)
}
}
}
}
if(viewModel.messageSending) {
VStack(spacing: 5){
HStack {
Spacer()
Text(sendingText)
.padding(.vertical, 8)
.padding(.horizontal)
.background(Color(.systemBlue))
.foregroundColor(.white)
.clipShape(ChatBubble(isFromCurrentUser: true))
.padding(.horizontal)
}
HStack {
Spacer()
ChatBubbleAnimationView()
.padding(.trailing, 8)
}
}
.padding(.bottom, 20)
.onDisappear(){
sendingText = ""
messageText = ""
}
}
}
}
聊天气泡 Package 器
struct ChatBubble: Shape {
var isFromCurrentUser: Bool
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: isFromCurrentUser ? [.topLeft, .bottomLeft, .bottomRight] : [.topRight, .bottomLeft, .bottomRight], cornerRadii: CGSize(width: 12, height: 12))
return Path(path.cgPath)
}
}
请让我知道一些其他的信息需要。我正在寻找建议,以获得行为记住它应该支持iOS 13+或任何帮助,以获得上述代码固定。
1条答案
按热度按时间t98cgbkg1#
一种选择是将内置的
ScrollView
上下颠倒。滚动指示符显示在左侧,但可以在iOS 16+中隐藏
如果您决定支持iOS 14+,则可以使用
ScrollViewReader
滚动到最新消息。