SwiftKickMobile / SwiftUIMaterialTabs

Material 3-style tabs and Sticky Headers rolled into one SwiftUI library
MIT License
61 stars 9 forks source link

Weird Header behavior when providing custom headerTabBar #13

Closed chichkanov closed 2 months ago

chichkanov commented 2 months ago
  1. Add custom header tab bar
  2. Try to scroll up, like when you do pull to refresh
  3. Look at the header. Something triggers it's weird re-rending

Environment: iOS18, iPhone 16 pro max simulator. Latest main branch, commit bb97ec4

https://github.com/user-attachments/assets/62404d90-9f41-410b-9cd1-ac5ce6306d70

Code

import SwiftUI
import SwiftUIMaterialTabs

enum Tab: String, Hashable, CaseIterable {
    case first
    case second
}

struct ContentView: View {

    @State var selectedTab: Tab = .first

    var body: some View {
        MaterialTabs(
            selectedTab: $selectedTab,
            headerTitle: { context in
                VStack {
                    Text("Material Tabs")
                        .font(.title)

                    Text("Some long long description bla\nsecond line of text bla bla bal")
                        .font(.callout)
                        .foregroundStyle(.secondary)
                }
                .animation(.bouncy, value: context.height)
                .frame(maxWidth: .infinity)
                .geometryGroup()
                .padding()
                .headerStyle(OffsetHeaderStyle(fade: true), context: context)
            },
            headerTabBar: { context in
                // Using a custom tab bar makes the header behave really weird
                HStack {
                    ForEach(Tab.allCases, id: \.self) { tab in
                        Text(tab.rawValue)
                            .foregroundStyle(selectedTab == tab ? .primary : .secondary)
                            .onTapGesture { selectedTab = tab }
                    }
                }
                .frame(width: context.width)
                .background(Color.red.opacity(0.2))
            },
            headerBackground: { context in
                Color.white
            },
            content: {
                firstTabContent()
                secondTabContent()
            }
        )
    }

    @ViewBuilder private func firstTabContent() -> some View {
        MaterialTabsScroll(tab: Tab.first) { _ in
            LazyVStack {
                ForEach(0..<100) { index in
                    Text("Row \(index)")
                        .padding()
                }
            }
        }
        .materialTabItem(tab: Tab.first, label: .secondary("First"))
    }

    @ViewBuilder private func secondTabContent() -> some View {
        MaterialTabsScroll(tab: Tab.second) { _ in
            LazyVStack {
                ForEach(0..<100) { index in
                    Text("Row \(index)")
                        .padding()
                }
            }
        }
        .materialTabItem(tab: Tab.second, label: .secondary("Second"))
    }
}
wtmoose commented 2 months ago

The header text is wrapping when the header's container expands. See #14—dynamic header height isn't currently supported, so I'm labelling as an enhancement.

chichkanov commented 2 months ago

But the header content is static here, always two Texts.

wtmoose commented 2 months ago

Yeah, you're right. I reclassified this one as a bug.

It seems to be an issue with multi-line text in general. If you change the second text label to the following, it stops glitching out:

Group {
    Text("Some long long description bla")
    Text("second line of text bla bla bal")
}
.font(.callout)
.foregroundStyle(.secondary)

I'm worried this one might be a SwiftUI bug, but I haven't had time to dig in. If you find any clues about why the text is truncating, let me know.

wtmoose commented 2 months ago

Update: Revised workaround (2)—in my initial version I copy-pasted the wrong line of code.

I'm closing this one—it is most definitely a SwiftUI bug related to multi-line text rendering. The only thing that SwiftUIMaterialTabs does to your title view is expand the height by setting frame(height:) when you pull the scroll up into negative content offset.

I have a couple of workarounds:

  1. Remove the geometryGroup(). I don't see any need for it here. This modifier was added as the result of one of my Radars, so I think I have a good handle on what problem it solves. But maybe you've found some specific uses for it that I'm not aware of?
  2. Alternatively, you can replace geomtryGroup() with .transformEffect(.identity)—it effectively accomplishes the same thing. This workaround was given to me by Apple when geomtryGroup() was still a private API.

Reopen if you think I've missed something.