SwiftUI: componente ScrollView

Qué es ScrollView

El componente ScrollView es uno de los componentes de SwiftUI que permite mostrar todo el contenido de una vista que por su maquetación se sale de los márgenes visibles de la pantalla, tanto en la vertical como en la horizontal. Es un componente equivalente a UIScrollView de UIKit.

Aquí podéis consultar la documentación oficial

Para usarlo tenemos que incluir en su ViewBuilder todas las vistas sobre las que se les quiere dar la capacidad de hacer scroll.

Scroll Vertical

ScrollView {
    VStack(spacing: 10) {
        ForEach(0..<100) {
            Text("Index: ($0)")
        }
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.background(Color.yellow.opacity(0.3))

Scroll Horizontal

ScrollView(.horizontal) {
    HStack(spacing: 10) {
        ForEach(0..<100) {
            Text("Index: ($0)")
        }
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.background(Color.yellow.opacity(0.3))

En la inicialización de ScrollView debemos indicar la dirección que tendrá el propio scroll (por defecto es vertical) y también podemos indicar si queremos que se muestre la barra indicadora del nivel del scroll (por defecto true). Por último, se le pasarán las vistas que queramos incluir dentro del ScrollView.

De esta forma el ScrollView adaptará el contenido 'scolleable' dependiendo de las vistas que contenga.

También se puede indicar que el ScrollView pueda hacer scroll tanto en la vertical como en la horizontal de la siguiente forma.

ScrollView([.vertical, .horizontal])

Es muy común que cuando tengamos TextField en una pantalla, toda la pantalla esté contenida dentro de un ScrollView para que podamos ver todo el contenido cuando se presente el teclado. Podéis consultar cómo hacer esto en este artículo.

Modificadores comunes para ScrollView

El componente ScrollView comparte los mismos métodos de personalización que el componente View y pueden ser consultados en el siguiente enlace.

Cómo mover el scroll programáticamente con ScrollViewReader (iOS 14)

A partir de iOS 14 Apple ha incluido el componente ScrollViewReader que nos permite tener control para movernos en el contenido del ScrollView.

Para usarlo debemos incluirlo como dentro del ScrollView e incluir todas las vistas que hubieran estado en él dentro del ScrollViewReader.

Cada uno de los componentes a los que podremos hacer scroll deberán tener definido un tag para que podamos hacer referencia al item al que queremos movernos.

import SwiftUI
import Combine


struct ContentView: View {
    @State private var index: String = ""
    @State private var selectedIndex: Int?
    @State private var doScroll: Bool = false
    
    var body: some View {
        VStack {
            HeaderSectionView("ScrollViewReader")
            HStack(spacing: 15) {
                TextField("Index between 0-99", text: $index)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .keyboardType(.numberPad)
                    .onReceive(Just(index), perform: { value in
                        let filtered = "(value)".filter { "0123456789".contains($0) }
                        if filtered != value {
                            self.index = "(filtered)"
                        }
                    })
                Button("Scroll!") {
                    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                    doScroll = true
                }
            }
            ScrollView {
                ScrollViewReader { proxy in
                    VStack {
                        ForEach(0..<100) {
                            if let selectedIndex = selectedIndex, selectedIndex == $0 {
                                Text("Index: ($0)")
                                    .padding()
                                    .background(Color.red.opacity(0.3))
                                    .tag($0)
                            } else {
                                Text("Index: ($0)")
                                    .padding()
                                    .tag($0)
                            }
                        }
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
                    .onReceive(Just(doScroll)) { value in
                        if let index = Int(index), value {
                            selectedIndex = index
                            withAnimation {
                                proxy.scrollTo(index, anchor: .center)
                            }
                        }
                        
                        doScroll = false
                    }
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
            .background(Color.yellow.opacity(0.3))
        }
        .padding()
    }
}

En el ejemplo anterior se ha implementado un Button que recoge el valor del TextField. Este valor será el tag del elemento al que queremos hacer scroll y lo haremos con la siguiente función:

proxy.scrollTo(index, anchor: .center)

El parámetro proxy lo proporciona el ScrollViewReader e implementa el método scrollTo  para mover el scroll.

Ejemplo

Puedes encontrar este ejemplo en https://github.com/SDOSLabs/SwiftUI-Test bajo el apartado ScrollView.

Rafael Fernández,
iOS Tech Lider