17

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:

enter image description here

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:

enter image description here

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

1
  • have you found the solution yet? Commented Aug 25, 2022 at 23:55

3 Answers 3

5

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

demo

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

Sign up to request clarification or add additional context in comments.

2 Comments

Hey what library are you using? I tried to get 'rects.first' but the system says rects has no member first?
@DucDang, it is ViewRectKey preference key, it is declared in different module of referenced test project
0

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()
}

Comments

0

I found a solution which works fine with accessibility.

  1. Statically define a font for your texts

  2. define height for each ContentSizeCategory (if you are using system fonts, you can use Typography specifications from Apple HIG.

  3. Dynamically generate a UIFont using UIFont.withSize(_) from ContentSizeCategory retrieved using @Environment(\.sizeCategory)

  4. Display text using with a SwiftUI Font based on UIFont

  5. Use GeometryProxy to retrieve width of your screen.

  6. From screen width, you can calculate remaining width for your text based on margins and components size

  7. You can calculate minimum height with margins and components around your text

  8. With CoreText and ContentSizeCategory, you can calculate height of each text by using NSAttributedString, CTFramesetterCreateWithAttributedString() and CTFramesetterSuggestFrameSizeWithConstraints()

  9. For each item that will be displayed on your TabView, there is an height that can be calculated from @Environment(\.sizeCategory) and GeometryProxy. And you can also calculate the maximum height of all items.

  10. Apply the maximum height for all views inside TabView

  11. Enjoy 😉

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.