I'm trying to create a profile screen similar to Twitter or Instagram, where there is a header with a floating tab bar around halfway down the page, and selecting different tabs switches the page of content underneath. The tricky part is that the entire view has to scroll vertically together, but the content on one tab is often a completely different height than the content on another tab. On top of this, the content height is dynamic, as it will increase as more content is fetched from a server, independently of other tabs.
I accomplished this in UIKit a while back, but now I'm updating my app to SwiftUI. I'm close to getting the behaviour I want, but as seen in the video below, switching pages is jarring because the moment the page index updates the content heights are resized. Ideally the content should always line up to the top of the page/bottom of the tab bar, and be able to change seamlessly.
Here's my code so far:
import SwiftUI
struct TestProfileView: View {
let headerHeight: CGFloat = 300
let tabBarHeight: CGFloat = 50
static let tab1Height: CGFloat = 100
static let tab2Height: CGFloat = 800
@State var indexHeights: [Int: CGFloat] = [0: Self.tab1Height, 1: Self.tab2Height]
@State var tabIndex = 0
var body: some View {
ScrollView {
VStack(spacing: 0) {
header
bottom
}
.frame(height: headerHeight + tabBarHeight + indexHeights[tabIndex]!)
}
}
private var header: some View {
Text("Header")
.frame(maxWidth: .infinity)
.frame(height: headerHeight)
.background(Color.green)
}
private var bottom: some View {
LazyVStack(spacing: 0, pinnedViews: .sectionHeaders) {
Section {
pager
.frame(height: indexHeights[tabIndex])
} header: {
tabBar
}
}
}
private var tabBar: some View {
HStack(spacing: 0) {
tab(title: "Tab 1", at: 0)
tab(title: "Tab 2", at: 1)
}
.frame(maxWidth: .infinity)
.frame(height: tabBarHeight)
.background(Color.gray)
}
private func tab(title: String, at index: Int) -> some View {
Button {
withAnimation {
tabIndex = index
}
} label: {
Text(title)
.foregroundColor(.black)
.frame(width: UIScreen.main.bounds.width / 2)
}
}
private var pager: some View {
TabView(selection: $tabIndex) {
Text("Content 1")
.frame(height: Self.tab1Height)
.frame(maxWidth: .infinity)
.background(Color.yellow)
.tag(0)
Text("Content 2")
.frame(height: Self.tab2Height)
.frame(maxWidth: .infinity)
.background(Color.orange)
.tag(1)
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
}
// MARK: - Previews
struct TestProfileView_Previews: PreviewProvider {
static var previews: some View {
TestProfileView()
}
}