I am currently trying to implement a scroll to hide header. How can I shrink the height of the header and fade it in as the user scrolls down. Then do the opposite when the user scrolls up(expand the header height to its initial value and fade it out). I have tried animating the opacity but it wasn't smooth. Here is the code for hiding and snapping the heading based on scroll direction.
struct ScrollToHideView: View {
@State private var naturalScrollOffset: CGFloat = 0
@State private var lastNaturalOffset: CGFloat = 0
@State private var headerOffset: CGFloat = 0
@State private var isScrollingUp = false
@State private var opacity = 1.0
@State private var tempH = 0.0
@State private var top = 0.0
var body: some View {
GeometryReader {
let safeArea = $0.safeAreaInsets
let headerHeight = 60.0 + safeArea.top
ScrollView(.vertical) {
LazyVStack {
ForEach(0..<10, id: \.self) { index in
DummyView()
}
}
.padding(16)
}
.overlay(content: {
Text("\(naturalScrollOffset)")
})
.safeAreaInset(edge: .top, spacing: 0, content: {
HeaderView()
.padding(.bottom, 16)
.frame(height: headerHeight, alignment: .bottom)
.background(.blue)
.opacity(opacity)
.offset(y: -headerOffset)
})
.onScrollGeometryChange(for: CGFloat.self) { proxy in
let maxHeight = proxy.contentSize.height - proxy.containerSize.height
return max(min(proxy.contentOffset.y + headerHeight, maxHeight),0)
} action: { oldValue, newValue in
let isScrollingUp = oldValue < newValue
headerOffset = min(max(newValue - lastNaturalOffset, 0), headerHeight)
self.isScrollingUp = isScrollingUp
// animating opacity
withAnimation(.easeIn(duration: 2)) {
if self.isScrollingUp {
opacity = 0
} else {
opacity = 1
}
}
naturalScrollOffset = newValue
}
.onScrollPhaseChange({ oldPhase, newPhase, context in
if !newPhase.isScrolling &&
(headerOffset != 0 || headerOffset != headerHeight) {
withAnimation(.snappy(duration: 0.25, extraBounce: 0)) {
if headerOffset > (headerHeight * 0.5) &&
naturalScrollOffset > headerHeight {
headerOffset = headerHeight
} else {
headerOffset = 0
}
lastNaturalOffset = naturalScrollOffset - headerOffset
}
}
})
.onChange(of: isScrollingUp) { oldValue, newValue in
lastNaturalOffset = naturalScrollOffset - headerOffset
}
.ignoresSafeArea(.container, edges: .top)
}
}
}
extension ScrollToHideView {
@ViewBuilder func HeaderView() -> some View {
HStack(spacing: 20) {
Image("user1")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 25)
Spacer(minLength: 0)
Button("", systemImage: "airplayvideo") {
}
Button("", systemImage: "bell") {
}
Button("", systemImage: "magnifyingglass") {
}
}
.font(.title2)
.foregroundStyle(.primary)
.padding(.horizontal, 16)
}
@ViewBuilder
func DummyView() -> some View {
VStack(alignment: .leading, spacing: 6) {
RoundedRectangle(cornerRadius: 6)
.frame(height: 220)
HStack(spacing: 10) {
Circle()
.frame(width: 45, height: 45)
VStack(alignment: .leading, spacing: 4) {
Rectangle()
.frame(height: 10)
HStack {
Rectangle()
.frame(width: 100)
Rectangle()
.frame(width: 80)
Rectangle()
.frame(width: 80)
}
.frame(height: 10.0)
}
}
}
.foregroundStyle(.tertiary)
}
}
I am currently trying to implement a scroll to hide header. How can I shrink the height of the header and fade it in as the user scrolls down. Then do the opposite when the user scrolls up(expand the header height to its initial value and fade it out). I have tried animating the opacity but it wasn't smooth. Here is the code for hiding and snapping the heading based on scroll direction.
struct ScrollToHideView: View {
@State private var naturalScrollOffset: CGFloat = 0
@State private var lastNaturalOffset: CGFloat = 0
@State private var headerOffset: CGFloat = 0
@State private var isScrollingUp = false
@State private var opacity = 1.0
@State private var tempH = 0.0
@State private var top = 0.0
var body: some View {
GeometryReader {
let safeArea = $0.safeAreaInsets
let headerHeight = 60.0 + safeArea.top
ScrollView(.vertical) {
LazyVStack {
ForEach(0..<10, id: \.self) { index in
DummyView()
}
}
.padding(16)
}
.overlay(content: {
Text("\(naturalScrollOffset)")
})
.safeAreaInset(edge: .top, spacing: 0, content: {
HeaderView()
.padding(.bottom, 16)
.frame(height: headerHeight, alignment: .bottom)
.background(.blue)
.opacity(opacity)
.offset(y: -headerOffset)
})
.onScrollGeometryChange(for: CGFloat.self) { proxy in
let maxHeight = proxy.contentSize.height - proxy.containerSize.height
return max(min(proxy.contentOffset.y + headerHeight, maxHeight),0)
} action: { oldValue, newValue in
let isScrollingUp = oldValue < newValue
headerOffset = min(max(newValue - lastNaturalOffset, 0), headerHeight)
self.isScrollingUp = isScrollingUp
// animating opacity
withAnimation(.easeIn(duration: 2)) {
if self.isScrollingUp {
opacity = 0
} else {
opacity = 1
}
}
naturalScrollOffset = newValue
}
.onScrollPhaseChange({ oldPhase, newPhase, context in
if !newPhase.isScrolling &&
(headerOffset != 0 || headerOffset != headerHeight) {
withAnimation(.snappy(duration: 0.25, extraBounce: 0)) {
if headerOffset > (headerHeight * 0.5) &&
naturalScrollOffset > headerHeight {
headerOffset = headerHeight
} else {
headerOffset = 0
}
lastNaturalOffset = naturalScrollOffset - headerOffset
}
}
})
.onChange(of: isScrollingUp) { oldValue, newValue in
lastNaturalOffset = naturalScrollOffset - headerOffset
}
.ignoresSafeArea(.container, edges: .top)
}
}
}
extension ScrollToHideView {
@ViewBuilder func HeaderView() -> some View {
HStack(spacing: 20) {
Image("user1")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 25)
Spacer(minLength: 0)
Button("", systemImage: "airplayvideo") {
}
Button("", systemImage: "bell") {
}
Button("", systemImage: "magnifyingglass") {
}
}
.font(.title2)
.foregroundStyle(.primary)
.padding(.horizontal, 16)
}
@ViewBuilder
func DummyView() -> some View {
VStack(alignment: .leading, spacing: 6) {
RoundedRectangle(cornerRadius: 6)
.frame(height: 220)
HStack(spacing: 10) {
Circle()
.frame(width: 45, height: 45)
VStack(alignment: .leading, spacing: 4) {
Rectangle()
.frame(height: 10)
HStack {
Rectangle()
.frame(width: 100)
Rectangle()
.frame(width: 80)
Rectangle()
.frame(width: 80)
}
.frame(height: 10.0)
}
}
}
.foregroundStyle(.tertiary)
}
}
Share
Improve this question
asked Mar 12 at 5:00
Jobie JJobie J
2672 silver badges9 bronze badges
1 Answer
Reset to default 0Instead of using a separate state variable for the opacity and animating this independently, you could make the opacity depend on the headerOffset
:
// ScrollToHideView
// @State private var opacity = 1.0
.safeAreaInset(edge: .top, spacing: 0) {
HeaderView()
.padding(.bottom, 16)
.frame(height: headerHeight, alignment: .bottom)
.background(.blue)
.opacity(1.0 - min(1.0, max(0.0, headerOffset / headerHeight))) //
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744769792a4592688.html
评论列表(0条)