[SwiftUI] ScrollView 스크롤 오프셋 값 PreferenceKey 통해서 가져오기

반응형

    아이폰 개발 시 스크롤 뷰를 사용하면 델리게이터를 이용해서 스크롤의 오프셋 값을 쉽게 알 수 있습니다. 하지만 스위프트 UI를 사용하면 어떻게 가져올지 막막합니다. 특정 위치에 스크롤을 강제로 하로 싶을 때 ScrollViewReader를 사용해서 이동시킬 수는 있지만 Offset값을 실시간으로 알기 위해서는 PreferenceKey를 통해서 값을 값을 실시간으로 추적해야 합니다.

     

    SwiftUI 프로토콜 PreferenceKey


     

     

     

    PreferenceKey 프로토콜 구현

    우선 위의 PreferenceKey를 구현해보겠습니다. defaultValuereduce 함수를 정의해줍니다.


    struct ScrollPreferenceKey: PreferenceKey {
        static var defaultValue: CGFloat = .zero
        static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
            value += nextValue()
        }
    }


     

     

     

     

     

    ScrollView UI 생성

    이제 뷰를 만들어주겠습니다. 하나의 스크롤 뷰와 하단의 스크롤 위치 값을 표현하기 위한 Text를 위치시킵니다. 


    struct ContentView: View {
        @State private var offsetY: CGFloat = .zero
        
        var body: some View {
            VStack {
                ScrollView {
                    VStack(spacing: 0) {
                        LazyVStack(spacing: 0) {
                            ForEach(0...100, id: \.self) { item in
                                ZStack() {
                                    Color.gray
                                    Text(String(item))
                                }
                            }
                        }
                    }
                }
                
                Text("\(offsetY)")
                    .padding()
            }
            .clipped()
        }
        
    }


     

     

     

     

    hiddenView 생성

    이제 값을 추적할 하나의 화면을 만들어줍니다. 이름은 hiddenView로 해주고 GeometryReader를 통해서 오프셋 값의 y를 이전 화면에서 @State 로 정의한 offsetY에 대입해 줍니다. 이 화면은 View로 작성하는 것보다 백그라운드에 추가하는 편이 좋을 수도 있지만 필요 없을 때는 쉽게 제거할 수 있도록 View로 작성합니다.


        private var hiddenView: some View {
            GeometryReader { proxy in
                let offsetY = proxy.frame(in: .global).origin.y
                Color.clear
                    .preference(
                        key: ScrollPreferenceKey.self,
                        value: offsetY
                    )
                    .onAppear { // 나타날때 뷰의 최초위치를 저장하는 로직
                        self.offsetY = offsetY
                    }
            }
            .frame(height: 0)
        }


     

     

     

     

    Offset 값 추적하기

    마지막으로 좀 전에 만든 hiddenView를 화면에 삽입하고 onPreferenceChange 이벤트로 값을 추적합니다.


    var body: some View {
            VStack {
                ScrollView {
                    VStack(spacing: 0) {
                        hiddenView
                        LazyVStack(spacing: 0) {
                            ForEach(0...100, id: \.self) { item in
                                ZStack() {
                                    Color.gray
                                    Text(String(item))
                                }
                            }
                        }
                    }
                }
                .clipped()
                .onPreferenceChange(ScrollPreferenceKey.self, perform: { value in
                    self.offsetY = value
                })
                
                Text("\(offsetY)")
                    .padding()
            }
            .clipped()
        }


     

     

     

     

    ContentView


    전체코드


    import SwiftUI
    
    struct ContentView: View {
        @State private var offsetY: CGFloat = .zero
        
        private var hiddenView: some View {
            GeometryReader { proxy in
                let offsetY = proxy.frame(in: .global).origin.y
                Color.clear
                    .preference(
                        key: ScrollPreferenceKey.self,
                        value: offsetY
                    )
                    .onAppear { // 나타날때 뷰의 최초위치를 저장하는 로직
                        self.offsetY = offsetY
                    }
            }
            .frame(height: 0)
        }
        
        var body: some View {
            VStack {
                ScrollView {
                    VStack(spacing: 0) {
                        hiddenView
                        LazyVStack(spacing: 0) {
                            ForEach(0...100, id: \.self) { item in
                                ZStack() {
                                    Color.gray
                                    Text(String(item))
                                }
                            }
                        }
                    }
                }
                .clipped()
                .onPreferenceChange(ScrollPreferenceKey.self, perform: { value in
                    self.offsetY = value
                })
                
                Text("\(offsetY)")
                    .padding()
            }
            .clipped()
        }
        
    }
    
    struct ScrollPreferenceKey: PreferenceKey {
        static var defaultValue: CGFloat = .zero
        static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
            value += nextValue()
        }
    }
    
    #Preview {
        ContentView()
    }


     

    반응형

    댓글

    Designed by JB FACTORY