본문 바로가기

iOS/SwiftUI

SwiftUI - TabView 내부 컨텐츠의 OffsetX 구하기

SwiftUI TabView를 사용하는 뷰에서 내부 컨텐츠들의 개별 offsetX를 탭뷰 기준으로 구하고 싶다.

원하는 것은 탭뷰의 좌측 상단으로 부터 컨텐츠가 얼마나 이동했는가 이다.


GeometryReader 추가 전 예제 코드

좌우 패딩 20을 갖는 간단한 탭뷰 코드

struct TabViewCoordinateSpaceExample: View {
    
    @State private var selection: Int = 0
    
    private let tabs: [Color] = [
        .red.opacity(0.2),
        .blue.opacity(0.2),
        .green.opacity(0.2),
        .brown.opacity(0.2),
        .yellow.opacity(0.2),
        .cyan.opacity(0.2)
    ]
    
    var body: some View {
        tabView
    }
    
    var tabView: some View {
        TabView(selection: $selection) {
            ForEach(0..<tabs.count, id: \.self) { i in
                tabs[i]
            }
        }
        .tabViewStyle(.page)
        .padding(.horizontal, 20)
        .frame(height: 300)
    }
}

 


GeometryReader 추가

offset 측정을 위해 GemoetryReader를 추가

import SwiftUI

struct TabViewCoordinateSpaceExample: View {
    
    @State private var selection: Int = 0
    
    private let tabs: [Color] = [
        .red.opacity(0.2),
        .blue.opacity(0.2),
        .green.opacity(0.2),
        .brown.opacity(0.2),
        .yellow.opacity(0.2),
        .cyan.opacity(0.2)
    ]
    
    private let tabViewCoordinateSpaceName: String = "tabCoordinateSpace"
    
    var body: some View {
        tabView
    }
    
    var tabView: some View {
        TabView(selection: $selection) {
            ForEach(0..<tabs.count, id: \.self) { i in
                tabs[i]
                    .background(geometryReader) // 3️⃣
            }
        }
        .tabViewStyle(.page)
        .coordinateSpace(name: tabViewCoordinateSpaceName) // 1️⃣
        .padding(.horizontal, 20)
        .frame(height: 300)
    }
    
    var geometryReader: some View { // 2️⃣
        GeometryReader { proxy in
            let frame = proxy.frame(in: .named(tabViewCoordinateSpaceName))
            
            Text("\(frame.minX)")
                .frame(width: frame.width)
        }
    }
}
  1. TabView를 coordinatorSpace 모디파이어를 이용하여 커스텀 좌표계로 지정했다.
  2. GeometryReader로 해당 좌표계를 이용한 origin을 가져왔다.
  3. 탭뷰 내 컨텐츠의 background에 적용했다.

 
그런데 TabView 내부에서 사용하는 .named(tabViewCoordinateSpaceName)은 제대로 동작하지 않고 항상 .global 처럼 동작한다.
 
세로 모드일 때는 20부터 시작 / 가로모드일 때는 79부터 시작

  
뭔가 잘못 알고 있나 싶어서 HStack으로 동일한 예제를 만들어 봤는데, 0부터 잘 시작한다.

struct VStackCoordinateSpaceExample: View {
    
    private let hStackCoordinateSpaceName: String = "vStackCoordinateSpace"
    
    private let items: [Color] = [
        .red.opacity(0.2),
        .blue.opacity(0.2),
        .green.opacity(0.2),
        .brown.opacity(0.2),
        .yellow.opacity(0.2),
        .cyan.opacity(0.2)
    ]
    
    var body: some View {
        HStack {
            ForEach(0..<items.count, id: \.self) { i in
                items[i]
                    .background(geometryReader)
            }
        }
        .tabViewStyle(.page)
        .coordinateSpace(name: hStackCoordinateSpaceName)
        .padding(.horizontal, 20)
        .frame(height: 400)
    }
    
    var geometryReader: some View {
        GeometryReader { proxy in
            let originX = proxy.frame(in: .named(hStackCoordinateSpaceName)).origin.x
            
            Text("\(originX)")
        }
    }
}

 

 

해결

한참을 끙끙대다가 결국 포기하고, GeometryReader를 외부와 내부 각각 두어 직접 계산하였다.
TabView 내의 뷰들은 커스텀 좌표계를 인지 못하는 듯 하다.

import SwiftUI

struct TabViewContentOffsetExample: View {
    
    @State private var selection: Int = 0
    
    private let tabs: [Color] = [
        .red.opacity(0.2),
        .blue.opacity(0.2),
        .green.opacity(0.2),
        .brown.opacity(0.2),
        .yellow.opacity(0.2),
        .cyan.opacity(0.2)
    ]
    
    var body: some View {
        tabView
    }
    
    var tabView: some View {
        GeometryReader { proxy in
            TabView(selection: $selection) {
                ForEach(0..<tabs.count, id: \.self) { i in
                    tabs[i]
                        .background(geometryReader(containerMinX: proxy.frame(in: .global).minX))
                }
            }
        }
        .tabViewStyle(.page)
        .padding(.horizontal, 20)
        .frame(height: 300)
    }
    
    func geometryReader(containerMinX: CGFloat) -> some View {
        GeometryReader { proxy in
            let minX = proxy.frame(in: .global).minX
            let offsetX = minX - containerMinX
            Text("\(offsetX)")
                .frame(width: proxy.size.width)
        }
    }
}

 
가로 / 세로 모두 0부터 시작


흑흑 더 좋은 방법을 아시는 분은 코멘트 부탁드립니다

'iOS > SwiftUI' 카테고리의 다른 글

SwiftUI - GeometryReader, CoordinateSpace  (0) 2024.08.20
SwiftUI 뷰가 다시 그려지는 조건 탐구 (Redraw)  (1) 2022.11.20