简体   繁体   中英

SwiftUI TabView with PageTabViewStyle dynamic height based on Content

How do I make my SwiftUI TabView with a PageTabViewStyle adjust its height to the height of the content?

I have a SwiftUI view like follows:

struct TabViewDynamicHeight: View {
    var body: some View {
        VStack {
            TabView {
                ForEach(0..<5, id: \.self) { index in
                    VStack {
                        Text("Text \(index)")
                        Text("Text \(index)")
                        Text("Text \(index)")
                    }
                }
            }
            .tabViewStyle(PageTabViewStyle())
            .background(Color.red)
            .fixedSize(horizontal: false, vertical: true)
        }
        .background(Color.blue)
    }
}

This produces an output like this:

在此处输入图像描述

You can see, that the content of the TabView is cut off. I'm aware, that I can remove .fixedSize , but than the view looks like this:

在此处输入图像描述

I would like the TabView to respond to the height of the content. Any ideas on how to achieve that?

A possible approach is to fetch content rect dynamically in run-time and transfer to parent via view prefs, so parent can set it as frame to fit content.

Tested with Xcode 13.3 / iOS 15.4

演示

Here is main part:

VStack {
    Text("Text \(index)")
    Text("Text \(index)")
    Text("Text \(index)")
}
.frame(maxWidth: .infinity)
.background(GeometryReader {
    Color.clear.preference(key: ViewRectKey.self,
                                  value: [$0.frame(in: .local)])
})

// ...

.frame(height: rect.size.height
         + 60 /* just to avoid page indicator overlap */)
.onPreferenceChange(ViewRectKey.self) { rects in
    self.rect = rects.first ?? .zero
}

Complete test code in project is here

I took inspiration from Asperi' answer, however I had to modify some things to make it work in my case.

On the 'Content' of the TabView, I added these:

.overlay(GeometryReader { proxy in
   Color.clear.preference(key: ViewRectKey.self, value: proxy.size)
 })
 .onPreferenceChange(ViewRectKey.self) { size in
    if self.viewPagerSize.height == .zero {
      self.viewPagerSize = size
    }
 }

Where viewPagerSize is initially:

@State var viewPagerSize: CGSize = .zero

My PreferenceKey looks like this:

struct ViewRectKey: PreferenceKey {
   static let defaultValue: CGSize = .zero
   static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
      value = nextValue()
   }
}

In total the code looks like this:

@ViewBuilder
func createViewPager() -> some View {
    TabView {
        ForEach(views(), id: \.id) { _ in
            createInformationSquareView()
                .background(Color(.systemGray6))
                .overlay(GeometryReader { proxy in
                    Color.clear.preference(key: ViewRectKey.self, value: proxy.size)
                })
                .onPreferenceChange(ViewRectKey.self) { size in
                    if self.viewPagerSize.height == .zero {
                        self.viewPagerSize = size
                    }
                }
        }
    }
    .cornerRadius(10)
    .frame(height: self.viewPagerSize.height + 60)
    .tabViewStyle(.page)
    .indexViewStyle(.page(backgroundDisplayMode: .always))
    .padding()
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM