Reputation: 4502
I’m hoping someone can point me in the right direction - I have been experimenting with SwiftUI, I’ve created a view somewhat similar to the Twitter Profile UI.
I seem to have a weird effect when the header collapses.
There is a ‘sweet spot’ in which the header is collapsed and the tab bar resizes by a small amount before the scroll view contains to scroll under the header.
You can see in this video.
I don’t want the tab segment to resize at all, instead it should simply stay in place once the header has collapsed and the scroll view should move freely underneath it.
I’m sure I am missing something obvious, however some fresh eyes might be just the help I need.
Any thoughts on this would be much appreciated!
import SwiftUI
struct ContentView: View {
@State private var safeArea: EdgeInsets = EdgeInsets(.zero)
@State private var offset: CGFloat = 0
@State private var tabBarOffset: CGFloat = 0
@State var currentTab = "Tab #1"
@Namespace var animation
var body: some View {
GeometryReader { foo in
ScrollView {
VStack(spacing: 0) {
// Header
GeometryReader { proxy -> AnyView in
// Sticky Header
DispatchQueue.main.async {
offset = proxy.frame(in: .global).minY
safeArea = foo.safeAreaInsets
}
return AnyView(makeHeader())
}
.frame(height: 180)
.zIndex(1)
// Profile
VStack(spacing: 0) {
VStack(spacing: 0) {
HStack(spacing: 0) {
TabButton(title: "Tab #1", currentTab: $currentTab, animation: animation)
.frame(maxWidth: .infinity)
TabButton(title: "Tab #2", currentTab: $currentTab, animation: animation)
.frame(maxWidth: .infinity)
TabButton(title: "Tab #3", currentTab: $currentTab, animation: animation)
.frame(maxWidth: .infinity)
}
Divider()
}
.padding(.top, 20)
.background(Color.white)
.offset(y: tabBarOffset < 90 ? -tabBarOffset + 90 : 0)
.overlay(
GeometryReader { proxy -> Color in
DispatchQueue.main.async {
tabBarOffset = proxy.frame(in: .global).minY
}
return Color.clear
}
.frame(width: 0, height: 0),
alignment: .top
)
.zIndex(1)
VStack {
ForEach((0..<50)) {
Text("Row #\($0)")
Divider()
}
}
.padding(.top)
.zIndex(0)
}
}
}
.ignoresSafeArea(.all, edges: .top)
}
}
@ViewBuilder func makeHeader() -> some View {
ZStack {
// Cover
Color.gray
.frame(maxWidth: .infinity)
.frame(height: offset > 0 ? 180 + offset : 180)
}
.clipped()
// Stretchy Effect
.frame(height: offset > 0 ? 180 + offset : nil)
.offset(y: offset > 0 ? -offset : -offset < 80 ? 0 : -offset - 80)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct TabButton: View {
var title: String
@Binding var currentTab: String
var animation: Namespace.ID
var body: some View {
Button(
action: {
withAnimation { currentTab = title }
},
label: {
LazyVStack(spacing: 12) {
Text(title)
.fontWeight(.semibold)
.foregroundColor(currentTab == title ? .blue : .gray)
.padding(.horizontal)
if currentTab == title {
Capsule()
.fill(Color.blue)
.frame(height: 1.2)
.matchedGeometryEffect(id: "TAB", in: animation)
} else {
Capsule()
.fill(Color.clear)
.frame(height: 1.2)
}
}
}
)
}
}
Upvotes: 1
Views: 1168
Reputation: 18904
Here, Set 90 instead of 80. As your setting top tabbar view y offset 90 and your whole logic is based on 90 value.
@ViewBuilder func makeHeader() -> some View {
ZStack {
// Cover
Color.red
.frame(maxWidth: .infinity)
.frame(height: offset > 0 ? 180 + offset : 180)
}
.clipped()
// Stretchy Effect
.frame(height: offset > 0 ? 180 + offset : nil)
.offset(y: offset > 0 ? -offset : -offset < 90 ? 0 : -offset - 90) //<=== Here
}
Upvotes: 2