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

PreferenceKey 프로토콜 구현
우선 위의 PreferenceKey를 구현해보겠습니다. defaultValue와 reduce 함수를 정의해줍니다.
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()
    }

전체코드
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()
}
'언어 > 스위프트 UI' 카테고리의 다른 글
| [SwiftUI] 버튼 활성 / 비활성 처리하기 (0) | 2024.02.22 | 
|---|---|
| [SwiftUI] ScrollView 중앙에 위치 시키기 (0) | 2024.02.16 | 
| [SwiftUI] URLSession 통신 await async JSON 데이터 로딩 처리하기 (0) | 2024.01.18 | 
| [SwiftUI] 네비게이션 바 버튼 만들기 (0) | 2023.07.10 | 
| [SwiftUI] URLSession을 이용한 간단한 JSON 통신 및 데이터 파싱 (0) | 2023.03.21 |