使用swiftui的ScrollView实现滚动位置的监听和设置

有个需求就是需要获取当前滚动的元素是第几个了,然后把滚动到的元素设置到最中心位置,并且改变这个元素的背景和边框,然后缩放1.5倍。

第一版代码

实现监听滚动到了第几个元素 

import SwiftUI

struct ContentView: View {
    let items = Array(0 ..< 10)
    @State private var currentIndex: Int = 0

    var body: some View {
        VStack {
            Text("当前中心索引: \(currentIndex)")
                .font(.title)
                .padding()
            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 20) {
                    ForEach(items, id: \.self) { index in
                        GeometryReader { _ in
                            Color.blue
                                .frame(width: 150, height: 150)
                                .overlay(Text("\(index)").foregroundColor(.white))
                                .background(GeometryReader { innerProxy in
                                    Color.clear
                                        .onAppear {
                                            updateCurrentIndex(innerProxy: innerProxy, index: index)
                                        }
                                        .onChange(of: innerProxy.frame(in: .global).midX) { oldValue, newValue in
                                            updateCurrentIndex(innerProxy: innerProxy, index: index)
                                        }
                                })
                        }
                        .frame(width: 150, height: 150)
                    }
                }
                .padding(.horizontal, (UIScreen.main.bounds.width - 150) / 2)
            }
        }
    }

    private func updateCurrentIndex(innerProxy: GeometryProxy, index: Int) {
        let screenCenter = UIScreen.main.bounds.width / 2
        let itemCenterX = innerProxy.frame(in: .global).midX
        let distance = abs(itemCenterX - screenCenter)

        DispatchQueue.main.async {
            if distance < 85 { // 85 是 150 宽度的一半 + 余量
                currentIndex = index
            }
        }
    }
}

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

第二版代码

实现滚动结束后,定位到最后的那个元素,并且增加红色边框

代码如下:

import SwiftUI

struct ContentView: View {
    let items = Array(0 ..< 10)
    @State private var currentIndex: Int = 0
    @State private var timer: Timer?

    var body: some View {
        VStack {
            Text("当前中心索引: \(currentIndex)")
                .font(.title)
                .padding()
            ScrollViewReader { scrollProxy in
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack(spacing: 20) {
                        ForEach(items, id: \.self) { index in
                            GeometryReader { _ in
                                Color.blue
                                    .frame(width: 150, height: 150)
                                    .overlay(Text("\(index)").foregroundColor(.white))
                                    .clipShape(Circle())
                                    .overlay(
                                        Circle()
                                            .stroke(currentIndex == index ? Color.red : Color.clear, lineWidth: 4) // 红色边框
                                    )
                                    .background(GeometryReader { innerProxy in
                                        Color.clear
                                            .onAppear {
                                                updateCurrentIndex(innerProxy: innerProxy, index: index)
                                            }
                                            .onChange(of: innerProxy.frame(in: .global).midX) { _, _ in
                                                updateCurrentIndex(innerProxy: innerProxy, index: index)
                                            }
                                    })
                            }
                            .frame(width: 150, height: 150)
                            .id(index)
                        }
                    }
                    .padding(.horizontal, (UIScreen.main.bounds.width - 150) / 2)
                }
                .onChange(of: currentIndex) { _, _ in
                    startTimer {
                        withAnimation {
                            scrollProxy.scrollTo(currentIndex, anchor: .center)
                        }
                    }
                }
            }
        }
    }

    private func updateCurrentIndex(innerProxy: GeometryProxy, index: Int) {
        let screenCenter = UIScreen.main.bounds.width / 2
        let itemCenterX = innerProxy.frame(in: .global).midX
        let distance = abs(itemCenterX - screenCenter)

        DispatchQueue.main.async {
            if distance < 85 { // 85 是 150 宽度的一半 + 余量
                currentIndex = index
            }
        }
    }

    private func startTimer(completion: @escaping () -> Void) {
        timer?.invalidate()
        timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in
            completion()
            print("done")
        }
    }
}

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