[SwiftUI Mater] #10 How to use: PreferenceKey

Woozoo·2023년 3월 28일
0

[SwiftUI Review]

목록 보기
25/41
post-custom-banner

네비게이션 뷰의 차일드 뷰에서 navigationTitle을 변경할 때 사실은
NavigationView의 title을 업데이트하고 있었음

그리고 Child View에서 데이터를 parent View로 넘길라고 한다면
Binding을 써줘야 했죠

근데 위의 네비게이션 과정에선 Binding이 필요 없었음!!
이럴 때 사용되는 게 PreferenceKey입니다~



요런 뷰가 있다고 해보자
Text(text) 에 있는 text는 사실 parent Level에서 전달받고 있음


SecondaryScreen을 만들고 위에 넣어줬다
지금도 보면 Parent Level에서 Child Level로 값을 전달해주고 있죠~


네비게이션 위치를 SecondaryScreen으로 바꿔줘봤음
근데 지금 보면 .navigationTitle이 안에 위체하게 됐는데도 NavigationView의 타이틀이 바뀌죠?
Child 에서 Parent로 데이터가 역으로 넘어가고 있죠?!
요런 뉘앙스를 이야기하는 겁니다!!


CustomTitlePreferenceKey를 만들어줬음
PreferenceKey를 준수하기 위해선 defaultValue가 필요한데 이때 오는 Value는 associatedtype이라고함


String을 준수하게 바꿔주고 빈 스트링을 넣어줬다
그리고 reduce메소드도 구현해줘야함!

긍까 이제 이 struct의 값을 바꿔주면 global밸류로 바껴서 위에 parent에서도 접근할 수 있게 된다는 거임!

다시 위에 뷰로 돌아와서

onPreferenceChange를 넣어주고 접근할 PreferenceKey랑 받은 value를 어떻게 할 건지 작성해주면 된다!


그리고 childView에서 Preference의 value를 바꿔주면 ParentView에서도 같이 변경이 되는 걸 볼 수 있음!!

더 보기 좋게 만들어 봅시다

View의 extension으로 customTitle이란 메소드를 작성해주고
.preference(key:, value)로 text가 변경되게 해줬습니다!
self.은 뷰가 하나밖에 없으니 빼줘도 되겠죠?


그리고 뷰에서 .customTitle을 호출하고 파라미터에 String을 넣게 되면!
preferenceKey가 바뀌게 되고, 이 값을 .onPreferenceChange에서 text에 넘겨주고 있으니까 text가 바뀌게 됩니다~

아 근데 customTitle 약간 navigationTitle이랑 느낌 비슷하게 해줄라고 외부 파라미터호출 이름은 생략하는걸로 바꿈!


.navigationTitle이랑 .customTitle이랑 비슷하죠?


그래서 어떨 때 쓰면 좋을까?
Child가 밸류를 들고 있지만 Parent는 들고 있지 않을 때 쓰면 되겠죠!

우선 위에 있던 .customTitle은 지워주고


SecondScreen으로 와서 .onAppear일 때 데이터를 Download해서 가져온다고 가정해봅시다
그러고 나서 .customTitle에 newValue를 넣어주면 PreferenceKey로 쓰고 있던 애들은 newValue로 바뀌겠죠?

오케이


자주 사용할 때는 geometry를 사용하게 될 때!!
새로 파일 만들어서 또 해봅시다


요런 뷰가 있다고 가정해보자
Text("Hello, world!")의 사이즈가 아래에 있는 중간 사각형의 크기와 똑같이 맞춰주고 싶다고 가정해보셈
근데 이 사이즈는 디바이스에 따라서 달라지죠?

screenSize계산해서 3으로 나누고 스페이싱 빼고~~ 카면서
해줄 수도 있겠고

지금처럼 이렇게 만들고 양 옆에 .opacity 0 줘서 만들 수도 있겠지만...
이상하잖음!! ㅋㅋㅋ

이렇게말고~~!!

GeometryReader 안에 Rectangle을 넣어준 담에
geo의 값이 텍스트로 출력되게 해줬음

그리고 아래에 새로운 PreferenceKey를 구성해주는데

defaultValue는 geo의 size 타입인 CGSize가 될 예정

뷰 extension까지 추가완료!


아까 있던 GeometryReader안의 Rectangle에 geo.size를 넘겨줍니다

struct GeometryPreferenceKeyBootcamp: View {
    @State private var rectSize: CGSize = .zero
    
    var body: some View {
        VStack {
            Text("Hello, world!")
                .frame(width: rectSize.width, height: rectSize.height)
                .background(Color.blue)
            
            Spacer()
            HStack {
                Rectangle()
                
                GeometryReader { geo in
                    Rectangle()
                        .updateRectangleGeoSize(geo.size)
                }
                Rectangle()
            }
            .frame(height: 55)
        }
        .onPreferenceChange(RectangleGeometrySizePreferenceKey.self) { value in
            self.rectSize = value
        }
    }
}

짜!
.onPreferenceChage에서 rectSize가 value(프리퍼런스키의)가 되도록 해주고
Text가 rectSize를 가지고 frame을 그려보면
사이즈가 아래의 가운데 Rectangle똑같아지는 걸 볼 수 있습니다~!


새파일 만들고
하나 더 해봅시다

지금처럼 스크롤일 때 네비게이션 타이틀이 inline으로 바뀌게 되잖음?
넵 타이틀이 스크롤의 위치를 어느 정도 하게 될 때쯤 바뀌는 요거!!

Custom으로 한번 똑같이 구현해봅시다


넵뷰 는 지워주고 ScrollView만 밖으로 꺼내줬음!!
Text 크기 크게해주고
위에 만든거랑 보이는건 똑같은 모양이죠?


그리고 이 scrollView의 overlay로 Text를 만들어줌!!

overlay라서 밑에 title이 가려지고 있죠


뷰들 다 변수로 빼서 보기 좋게 만들어주고
우선은 overlay는 코멘트아웃해줬습니다

이제 우리가 알고 싶은 건 NewTitle 텍스트가 현재 스크린에서 어디에 위치하고 있느냐~ 가 알고 싶어요


PreferenceKey로 CGFloat을 구성해줬습니다~!
(참고로 reduce 메소드에선 nextValue의 커스텀이 가능합니다! 지금은 그냥 바로 넘겨주고 있지마는)

.onPreferenceChage에서 scrollViewOffset에 value를 넣어줄 수 있게 해줬어요
그리고 .overlay로 scrollViewOffset값이 보여지게 해줬습니다


그리고 titleLayer의 background로
GeometryReader를 넣고 빈 텍스트(깡통)으로
preference key를 넘겨주는데 이 때 value는
geo의 글로벌에서의 프레임 크기에서 minY값을 넘겨주게 해줬습니다!!

이제 offset이 찍히네요
0이 아닌거는 padding넣은거랑 safeArea만큼 크기가 있어서 그럼


이제 scrollViewOffset의 이용해서 opacity가 요렇게 계산되게해주면~!!

오케이이이이

나는 위에꺼
닉이 한방법은 아래꺼

근데 이거 나눠주는 숫자는 시뮬레이터 크기마다 달라지네

초기 오프셋
14 pro는 75였는데
14은 63임
이거 초기 오프셋 저장해주면서 써야할듯


뷰 익스텐션으로 만들어서 쓰면 완전 편함


아까 작성했던 내용들
.background 랑
.onPreferenceChage 복사해서 가지고와줬으

지금 에러 뜨는 곳
파라미터로 escaping Closure 받아줘야 할 듯

extension View {
    func onScrollViewOffsetChanged(action: @escaping (_ offset: CGFloat) -> Void) -> some View {
        self
            .background {
                GeometryReader { geo in
                    Text("")
                        .preference(key: ScrollViewOffsetPreferenceKey.self, value: geo.frame(in: .global).minY)
                }
            }
            .onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in
                action(value)
            }
    }
}

요렇게 말이죠

다시 원래 뷰로 돌아와서 기존꺼 지워주고 방금 만든 메소드 넣어주면 끝!!

profile
우주형
post-custom-banner

0개의 댓글