Il nostro team specializzato dopo alcuni giorni di ricerca e raccolta di informazioni, abbiamo ottenuto la risposta, speriamo che tutto questo articolo sia utile per il tuo lavoro.
Soluzione:
Aggiornamento: in iOS 14 c'è ora un modo nativo per farlo.
Io lo faccio in questo modo
ScrollView(.vertical) {
ScrollViewReader { scrollView in
LazyVStack {
ForEach(notes, id: .self) { note in
MessageView(note: note)
}
}
.onAppear {
scrollView.scrollTo(notes[notes.endIndex - 1])
}
}
}
Per iOS 13 e successivi potete provare:
Ho scoperto che l'inversione delle viste sembra funzionare abbastanza bene per me. In questo modo la ScrollView viene avviata in basso e, quando si aggiungono nuovi dati, la vista scorre automaticamente verso il basso.
- Ruotare la vista più esterna di 180
.rotationEffect(.radians(.pi))
- Capovolgere la vista sul piano verticale
.scaleEffect(x: -1, y: 1, anchor: .center)
Dovrete eseguire questa operazione anche per le viste interne, poiché ora saranno tutte ruotate e capovolte. Per capovolgerle di nuovo, fate la stessa cosa di cui sopra.
Se si ha bisogno di molti posti, potrebbe valere la pena di avere una vista personalizzata per questo.
Si può provare qualcosa di simile a quanto segue:
List(chatController.messages, id: .self) { message in
MessageView(message.text, message.isMe)
.rotationEffect(.radians(.pi))
.scaleEffect(x: -1, y: 1, anchor: .center)
}
.rotationEffect(.radians(.pi))
.scaleEffect(x: -1, y: 1, anchor: .center)
Ecco un'estensione della vista per capovolgerla
extension View {
public func flip() -> some View {
return self
.rotationEffect(.radians(.pi))
.scaleEffect(x: -1, y: 1, anchor: .center)
}
}
Poiché per ora non esiste una funzione integrata (né per List né per ScrollView), in Xcode 11.2, ho dovuto codificare una ScrollView personalizzata con il comportamento ScrollToEnd
!!! Ispirato da questo articolo.
Ecco il risultato dei miei esperimenti, spero che sia utile anche a voi. Naturalmente ci sono altri parametri che potrebbero essere configurabili, come i colori, ecc. ma sembra banale e fuori portata.
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
È possibile farlo ora, da Xcode 12, con la nuova funzione ScrollViewProxy
Ecco un esempio di codice:
È possibile aggiornare il codice sottostante con il proprio chatController.messages
e la chiamata scrollViewProxy.scrollTo(chatController.messages.count-1)
.
Quando farlo? Forse sulla nuova interfaccia SwiftUI onChange
!
struct ContentView: View {
let itemCount: Int = 100
var body: some View {
ScrollViewReader { scrollViewProxy in
VStack {
Button("Scroll to top") {
scrollViewProxy.scrollTo(0)
}
Button("Scroll to buttom") {
scrollViewProxy.scrollTo(itemCount-1)
}
ScrollView {
LazyVStack {
ForEach(0 ..< itemCount) { i in
Text("Item (i)")
.frame(height: 50)
.id(i)
}
}
}
}
}
}
}
Ti invitiamo ad aggiungere valore al nostro contenuto informativo aggiungendo la tua esperienza nelle interpretazioni.