Skip to content

Come far scorrere automaticamente un elenco SwiftUI?

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.

  1. Ruotare la vista più esterna di 180 .rotationEffect(.radians(.pi))
  2. 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.

Scorri fino alla finecontenuto inverso

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 ScrollViewProxyEcco 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.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.