将内容添加到ListView时,我希望它自动向下滚动。
我正在使用SwiftUI“列表”和“ BindableObject”作为控制器。新数据将追加到列表中。
List(chatController.messages, id: \.self) { message in
MessageView(message.text, message.isMe)
}
我希望列表向下滚动,因为我将新数据追加到消息列表中。但是,我必须手动向下滚动。
由于Xcode 11.2目前没有内置的此类功能(List和ScrollView都没有),因此我需要使用ScrollToEnd行为对自定义ScrollView进行编码
!!! 灵感来自此文章。
这是我实验的结果,希望有人也能有所帮助。当然,还有更多的参数,这些参数可能是可配置的,例如颜色等,但是它显得微不足道,超出了范围。
import SwiftUI struct ContentView: View { @State private var objects = ["0", "1"] var body: some View { NavigationView { VStack { CustomScrollView(scrollToEnd: true) { ForEach(self.objects, id: \.self) { object in VStack { Text("Row \(object)").padding().background(Color.yellow) NavigationLink(destination: Text("Details for \(object)")) { Text("Link") } Divider() }.overlay(RoundedRectangle(cornerRadius: 8).stroke()) } } .navigationBarTitle("ScrollToEnd", displayMode: .inline) // CustomScrollView(reversed: true) { // ForEach(self.objects, id: \.self) { object in // VStack { // Text("Row \(object)").padding().background(Color.yellow) // NavigationLink(destination: Text("Details for \(object)")) { // Image(systemName: "chevron.right.circle") // } // Divider() // }.overlay(RoundedRectangle(cornerRadius: 8).stroke()) // } // } // .navigationBarTitle("Reverse", displayMode: .inline) HStack { Button(action: { self.objects.append("\(self.objects.count)") }) { Text("Add") } Button(action: { if !self.objects.isEmpty { self.objects.removeLast() } }) { Text("Remove") } } } } } } struct CustomScrollView: View where Content: View { var axes: Axis.Set = .vertical var reversed: Bool = false var scrollToEnd: Bool = false var content: () -> Content @State private var contentHeight: CGFloat = .zero @State private var contentOffset: CGFloat = .zero @State private var scrollOffset: CGFloat = .zero var body: some View { GeometryReader { geometry in if self.axes == .vertical { self.vertical(geometry: geometry) } else { // implement same for horizontal orientation } } .clipped() } private func vertical(geometry: GeometryProxy) -> some View { VStack { content() } .modifier(ViewHeightKey()) .onPreferenceChange(ViewHeightKey.self) { self.updateHeight(with: $0, outerHeight: geometry.size.height) } .frame(height: geometry.size.height, alignment: (reversed ? .bottom : .top)) .offset(y: contentOffset + scrollOffset) .animation(.easeInOut) .background(Color.white) .gesture(DragGesture() .onChanged { self.onDragChanged($0) } .onEnded { self.onDragEnded($0, outerHeight: geometry.size.height) } ) } private func onDragChanged(_ value: DragGesture.Value) { self.scrollOffset = value.location.y - value.startLocation.y } private func onDragEnded(_ value: DragGesture.Value, outerHeight: CGFloat) { let scrollOffset = value.predictedEndLocation.y - value.startLocation.y self.updateOffset(with: scrollOffset, outerHeight: outerHeight) self.scrollOffset = 0 } private func updateHeight(with height: CGFloat, outerHeight: CGFloat) { let delta = self.contentHeight - height self.cOntentHeight= height if scrollToEnd { self.cOntentOffset= self.reversed ? height - outerHeight - delta : outerHeight - height } if abs(self.contentOffset) > .zero { self.updateOffset(with: delta, outerHeight: outerHeight) } } private func updateOffset(with delta: CGFloat, outerHeight: CGFloat) { let topLimit = self.contentHeight - outerHeight if topLimit <.zero { self.cOntentOffset= .zero } else { var proposedOffset = self.contentOffset + delta if (self.reversed ? proposedOffset : -proposedOffset) <.zero { proposedOffset = 0 } else if (self.reversed ? proposedOffset : -proposedOffset) > topLimit { proposedOffset = (self.reversed ? topLimit : -topLimit) } self.cOntentOffset= proposedOffset } } } 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) }) } } #if DEBUG struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } #endif