DEV Community

GoyesDev
GoyesDev

Posted on

[SUI] Invocar una acción cuando una vista sea visible en un ScrollView

  • onScrollVisibilityChange(threshold:_:): Agrega una acción (action) a ser ejecutada cuando la vista cruza el umbral (threshold) para ser considerada dentro/fuera de la pantalla. threshold es la cantidad de vista (de 0.0 a 1.0) que se requiere que sea visible dentro del "parent view" para disparar la acción.
struct ContentView: View {
  let data: [Content] = [
    .init(text: "1"),
    .init(text: "2"),
    .init(text: "3"),
    .init(text: "4"),
    .init(text: "5"),
    .init(text: "6"),
    .init(text: "7"),
    .init(text: "8"),
    .init(text: "9"),
    .init(text: "10"),
    .init(text: "11"),
    .init(text: "12"),
    .init(text: "13"),
    .init(text: "14"),
    .init(text: "15"),
    .init(text: "16"),
  ]
  @State var selected: Content? = nil
  @State var loaded = false

  var body: some View {
    VStack {
      ScrollViewReader { proxy in
        Spacer()

        ScrollView(.horizontal) {
          LazyHStack {
            ForEach(data) { i in
              Text("Item: [\(i.text)]")
                .background(i.id == selected?.id ? .gray : .clear)
                .containerRelativeFrame(.horizontal, count: 8, span: 1, spacing: 0, alignment: .center)
                .scrollTransition(.interactive, axis: .horizontal) { effect, phase in
                  effect
                    .opacity(phase.isIdentity ? 1: 0)
                    .scaleEffect(phase.isIdentity ? 1 : 0.5)
                }
                .if(loaded && (i.id == data.last?.id || i.id == data.first?.id)) {
                  $0
                    .onScrollVisibilityChange(threshold: 0.5) { visible in
                      if visible {
                        print("Debo cargar más entradas")
                      }
                    }
                }
            }
          }
          .scrollTargetLayout()
        }
        .frame(height: 100)
        .scrollTargetBehavior(.viewAligned)
        .onAppear {
          Task {
            try! await Task.sleep(for: .milliseconds(50))
            selected = data[data.count / 2]
            proxy.scrollTo(selected!.id, anchor: .center)
            try! await Task.sleep(for: .milliseconds(50))
            loaded = true
          }
        }

        Spacer()
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

En la implementación anterior, primero se centra el contenido (en onAppear) y luego se agrega de forma condicional onScrollVisibilityChange en la primera y última "celda", para cargar más valores.

Para que el ejemplo pueda funcionar, es necesario tener la siguiente extensión:

extension View {
  @ViewBuilder func `if`<T>(_ condition: Bool, transform: (Self) -> T) -> some View where T : View {
    if condition {
      transform(self)
    } else {
      self
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)