📜

SwiftUIで左右の無限スクロール

2023/09/17に公開

iOS17から追加される

defaultScrollAnchor()

でScrollViewの開始位置を真ん中にした上で、以下の

handleItemAppearance(item: item)

のように、ScrollViewの端っこに到達したときに、itemsに要素を追加すると、左右の無限スクロールができます。

import SwiftUI
struct InfiniteScrollView: View {
    @State private var items: [Int] = Array(0..<10) // Initial data
    @State private var isLoading = false
    
    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack{
                ForEach(items, id: \.self) { item in
                    RoundedRectangle(cornerRadius: 10)
                        .fill(.orange)
                        .frame(width: 150, height: 40)
                        .overlay {
                            Text("Item \(item)")
                        }
                        .containerRelativeFrame(.horizontal, count: 8, span: 3, spacing: 0)
                        .onAppear{
                            handleItemAppearance(item: item)
                        }
                }
            }
            .scrollTargetLayout()
        }
        .scrollTargetBehavior(.viewAligned)
        .defaultScrollAnchor(.center)
    }
    
    private func handleItemAppearance(item: Int) {
        if item == items.last {
            updateIntList(byAdding: 1)
        } else if item == items.first {
            updateIntList(byAdding: -1)
        }
    }

    private func updateIntList(byAdding valueToAdd: Int) {
        guard !isLoading else { return }
        isLoading = true

        if let referenceValue = valueToAdd > 0 ? items.last : items.first {
            let newValue = referenceValue + valueToAdd
            if valueToAdd > 0 {
                items.append(newValue)
            } else {
                items.insert(newValue, at: 0)
            }
        }
        isLoading = false
    }
    
}

#Preview {
    InfiniteScrollView()
}

ScrollViewの開始位置が端っこだと、起動した途端に要素が無限に増え初めますが、開始位置を真ん中にすれば回避できます。

左右にスクロールするカレンダーアプリなら、以下のように現在時刻を起点に前後10日を取得し、それをForEachで回して.defaultScrollAnchor(.center)にすれば、現在日を真ん中に表示しながら無限スクロールが実装できます

.onAppear {
    // 今日を中心に前後10日(合計20日)の日付リストを作成
    for i in -10...10 {
        if let newDate = Calendar.current.date(byAdding: .day, value: i, to: currentDate) {
	    dateList.append(newDate)
        }
    }
}

ただ派手にスクロールすると挙動が不安定ですが...

Discussion