Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI TabView inside a ScrollView

Tags:

swiftui

Problem: My TabView with PageTabViewStyle has content of different heights. I can swipe between different pages, however inside the ScrollView my TabView shrinks to 0 height.

Expected: TabView has height of the largest child view.

I have encountered similar problem before, but just hardcoded approximate frame height of largest view, but it doesn't seem right way to go. Any ideas much appreciated.

struct ContentView: View {
    var body: some View {
//        ScrollView {
            VStack {
                TabView {
                    ForEach(1..<5, id:\.self) { index in
                        VStack {
                            DifferentSizeViews(times: index)
                            Spacer()
                        }
                    }
                }
                .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
                
                Text("Some other content")
            }
        }
//    }
}

struct DifferentSizeViews: View {
    var times: Int
    var body: some View {
        VStack {
            ForEach(0..<times, id:\.self) { index in
                Text("some text \(index)")
            }
        }
    }
}
like image 584
Wagm Avatar asked Nov 14 '22 21:11

Wagm


2 Answers

After many hours of pain I found this solution. Hope it helps.

@State var selection: Int = 0
@State var height: CGFloat = 0

@State var sizeArray: [CGSize] = [.zero, .zero, .zero]
@State var initialHeight: CGFloat = 0

var body: some View {
    ScrollView {
        VStack(spacing: 0) {
            Color.red
                .frame(height: 100)
            
            TabView(selection: $selection) {
                Text("1\n1\n1")
                    .fixedSize()
                    .readSize(onChange: { size in
                        sizeArray[0] = size
                        if initialHeight == 0 {
                            initialHeight = size.height
                        }
                    })
                    .tag(0)
                Text("2\n2\n2\n2\n2")
                    .fixedSize()
                    .readSize(onChange: { size in
                        sizeArray[1] = size
                    })
                    .tag(1)
                Text("3")
                    .fixedSize()
                    .readSize(onChange: { size in
                        sizeArray[2] = size
                    })
                    .tag(2)
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            .background(Color.white)
            .frame(height: height)
            .onChange(of: selection) { newValue in
                withAnimation {
                    height = sizeArray[newValue].height
                }
            }
            .onChange(of: initialHeight) { newValue in
                height = newValue
            }
        }
    }
    .background(Color.green)
}

And View extension

extension View {
    func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
        background(
            GeometryReader { geometryProxy in
                Color.clear
                    .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
            }
        )
            .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
    }
}
like image 72
krbiz Avatar answered May 31 '23 00:05

krbiz


Although this might seem pretty hard-coded and kind of wrong, actually it's a quick fix as long as you know how the approximate height ratio you want for the TabView:

//MARK: - Body
var body: some View {
  ScrollView(.vertical, showsIndicators: false, content: {
      VStack(spacing: 0) {
        SomeTabView()
          .frame(height: UIScreen.main.bounds.width / 1.475) 
          .padding(.vertical, 20)
      } //: VStack
   }) //: ScrollView
}

In this code snippet, the number 1.475 is completely up to you in the modifier ".frame(height: UIScreen.main.bounds.width / 1.475)"

like image 35
Kodirbek Kh. Avatar answered May 30 '23 22:05

Kodirbek Kh.