In Swiftui, Where Are the Control Events, I.E. Scrollviewdidscroll to Detect the Bottom of List Data

In SwiftUI, where are the control events, i.e. scrollViewDidScroll to detect the bottom of list data

You can to check that the latest element is appeared inside onAppear.

struct ContentView: View {
@State var items = Array(1...30)

var body: some View {
List {
ForEach(items, id: \.self) { item in
Text("\(item)")
.onAppear {
if let last == self.items.last {
print("last item")
self.items += last+1...last+30
}
}
}
}
}
}

How to make a SwiftUI List scroll automatically?

Update: In iOS 14 there is now a native way to do this.
I am doing it as such

        ScrollViewReader { scrollView in
ScrollView(.vertical) {
LazyVStack {
ForEach(notes, id: \.self) { note in
MessageView(note: note)
}
}
.onAppear {
scrollView.scrollTo(notes[notes.endIndex - 1])
}
}
}

For iOS 13 and below you can try:

I found that flipping the views seemed to work quite nicely for me. This starts the ScrollView at the bottom and when adding new data to it automatically scrolls the view down.

  1. Rotate the outermost view 180 .rotationEffect(.radians(.pi))
  2. Flip it across the vertical plane .scaleEffect(x: -1, y: 1, anchor: .center)

You will have to do this to your inner views as well, as now they will all be rotated and flipped. To flip them back do the same thing above.

If you need this many places it might be worth having a custom view for this.

You can try something like the following:

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)

Here's a View extension to flip it

extension View {
public func flip() -> some View {
return self
.rotationEffect(.radians(.pi))
.scaleEffect(x: -1, y: 1, anchor: .center)
}
}

SwiftUI async Data fetch

Try the next, it use anchor preferences and simple model which mimics async operation to add some records to ScrollView (or List)

import SwiftUI

struct PositionData: Identifiable {
let id: Int
let center: Anchor<CGPoint>
}

struct Positions: PreferenceKey {
static var defaultValue: [PositionData] = []
static func reduce(value: inout [PositionData], nextValue: () -> [PositionData]) {
value.append(contentsOf: nextValue())
}
}

struct Data: Identifiable {
let id: Int
}

class Model: ObservableObject {
var _flag = false
var flag: Bool {
get {
_flag
}
set(newValue) {
if newValue == true {
_flag = newValue

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self._flag = false
self.rows += 20
print("done")
}
}
}
}
@Published var rows = 20
}
struct ContentView: View {
@ObservedObject var model = Model()
var body: some View {
List {
ForEach(0 ..< model.rows, id:\.self) { i in
Text("row \(i)").font(.largeTitle).tag(i)
}
Rectangle().tag(model.rows).frame(height: 0).anchorPreference(key: Positions.self, value: .center) { (anchor) in
[PositionData(id: self.model.rows, center: anchor)]
}.id(model.rows)
}
.backgroundPreferenceValue(Positions.self) { (preferences) in
GeometryReader { proxy in
Rectangle().frame(width: 0, height: 0).position(self.getPosition(proxy: proxy, tag: self.model.rows, preferences: preferences))
}
}
}
func getPosition(proxy: GeometryProxy, tag: Int, preferences: [PositionData])->CGPoint {
let p = preferences.filter({ (p) -> Bool in
p.id == tag
})
if p.isEmpty { return .zero }
if proxy.size.height - proxy[p[0].center].y > 0 && model.flag == false {
self.model.flag.toggle()
print("fetch")
}
return .zero
}
}



struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

and here is how it looks like ...

Sample Image

Why SwiftUI loads the items in the reverse order?

After trying many things I resolved the issue by using URLImage Library's option.

URLImages has a delay option that you can use to delay the process of image loading. And usage is as follows:

    URLImage(URL(string: DVRItem.getLogoUrlHD().replacingOccurrences(of: "http", with: "https", options: .literal, range: nil))!, delay: 0.25,  placeholder: { _ in Image("nologo").resizable().aspectRatio(contentMode: .fill).clipped() } ){
proxy in
proxy.image
.resizable() // Make image resizable
.aspectRatio(contentMode: .fill) // Fill the frame
.clipped() // Clip overlaping parts
}

I updated my DVRItem and added a new variable called index:

    var ind: Int

And used that variable to load the first 5 elements without delay, load the next 15 elements with 0.15 delay and load the rest of the elements with 0.25 delay. That way it immediately loads the first 5 elements and then loads the rest. My final code is like this:

struct dvrItem: View {

var DVRItem: dvr
var ind: Int

var body: some View {
VStack( alignment: .leading) {
URLImage(URL(string: DVRItem.getLogoUrlHD().replacingOccurrences(of: "http", with: "https", options: .literal, range: nil))!, delay: (self.ind < 5) ? 0 : (self.ind < 20 ? 0.15 : 0.25), placeholder: { _ in Image("nologo").resizable().aspectRatio(contentMode: .fill).clipped() } ){
proxy in
proxy.image
.resizable() // Make image resizable
.aspectRatio(contentMode: .fill) // Fill the frame
.clipped() // Clip overlaping parts
}
.frame(width: 198, height: 114)
.cornerRadius(7)
.padding(.leading, 10)
.padding(.top, 5)
Text(self.DVRItem.getName())
.foregroundColor(Color.white)
.padding(.leading, 5)
.padding(.trailing, 5)

Text(self.DVRItem.getDescription())
.foregroundColor(Color(red: 80.0/150, green: 80.0/150, blue: 80.0/150))
.lineLimit(nil)
.padding(.leading, 5)
.padding(.trailing, 5)
Spacer()
.frame(height: 5)

}
}
}

struct dvrItem_Previews: PreviewProvider {
static var previews: some View {
dvrItem(DVRItem: channels[0].getDVR()[0], ind: 0)
}
}

And I call the DVRItem as follows and assign the ind with index values:

ForEach(1..<chan.getDVR().count, id: \.self){index in

Button(action:
{
self.str = self.chan.getDVR()[index].getHlsStreamURL()
self.refresh = false
self.ind = index

}) {
dvrItem(DVRItem: self.chan.getDVR()[index], ind: index)
.buttonStyle(PlainButtonStyle())
.padding(.trailing,3)
.cornerRadius(7)

}.buttonStyle(PlainButtonStyle())
}

I still don't know why it starts loading from the last item but this solved my problems. I tried similar solutions like in this post In SwiftUI, where are the control events, i.e. scrollViewDidScroll to detect the bottom of list data but they don't work for horizontal scrolls.

Detect scroll in Webview with Swift

To get scroll position updates you need to create coordinator and override makeCoordinator() method and return instance of your coordinator. In makeUIView(_:) method just assign scrollview delegate to context.coordinator (context object in provided in arguments of makeUIView method)

Pass binding from view to coordinator and coordinator is responsible to update that binding. Here is code for that

struct WebView: UIViewRepresentable {

var url: String
@Binding var contentOffset: CGPoint

init(url: String, contentOffset: Binding<CGPoint>) {
self.url = url
_contentOffset = contentOffset
}

let webView = WKWebView()

func makeUIView(context: Context) -> WKWebView {
webView.scrollView.delegate = context.coordinator // assign delegation

webView.evaluateJavaScript("navigator.userAgent") { (result, error) in
print(result as! String)
}
return webView
}

func updateUIView(_ uiView: WKWebView, context: Context) {
let request = URLRequest(url: URL(string:url)!)
uiView.load(request)
}

func makeCoordinator() -> Coordinator {
.init(contentOffset: $contentOffset) // create coordinator for delegation
}


class Coordinator: NSObject, UIScrollViewDelegate {

@Binding var contentOffset: CGPoint

init(contentOffset: Binding<CGPoint>) {
_contentOffset = contentOffset
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
contentOffset = scrollView.contentOffset
}
}
}

Ajax callback with 2 parameters (int, List)

You need to json stringify the id also, for example:

var jsonText = JSON.stringify({ levels: levels, id : 1});


Related Topics



Leave a reply



Submit