I’m making an attempt to implement a collapsible header in SwiftUI that collapses when the consumer scrolls down and expands when the consumer scrolls up.
I adopted this Medium publish: Collapsible Header in SwiftUI
The implementation works high quality when scrolling usually, however I’ve run into an issue:
- When the consumer drags the view down from the highest (pulls to refresh model) after which releases, the header disappears/hides unexpectedly.
Right here’s a brief display screen recording displaying the difficulty:
Display Recording
And right here is the code I’m utilizing:
public struct CollapsibleHeaderList: View {
// MARK: - Personal Vars
personal let gadgets = Array(0.. Bool {
guard let lastAnimationDate else {
return true
}
return abs(lastAnimationDate.timeIntervalSinceNow) > animationDuration
}
}
public enum HeaderState {
case preliminary
case collapse
case increase
}
public class CollapsibleHeaderViewModel: ObservableObject {
@Printed personal(set) public var state: HeaderState
personal var indexSubject = PassthroughSubject()
personal var cancellables = Set()
init() {
self.state = .preliminary
setupCollapsibleHeaderListener()
}
public func onCellAppear(index: Int) {
indexSubject.ship(index)
}
personal func setupCollapsibleHeaderListener() {
indexSubject
.throttle(for: .seconds(0.5), scheduler: DispatchQueue.most important, newest: true)
.withPrevious()
.map { (earlier, present) in
if let earlier, earlier AnyPublisher {
scan(Optionally available.none) { ($0?.1, $1) }
.compactMap { $0 }
.eraseToAnyPublisher()
}
}
Query:
How can I forestall the header from hiding when the consumer pulls down from the highest?
Replace:
I attempted to implement the answer @Benzy Neez
advised, however now I get a bizarre shaking/jitter impact when the header seems. Additionally, when the view seems for the very first time, the header isn’t seen — it disappears instantly.
Right here is the display screen recording : Display recording 2
Right here is myCurrent code:
@State personal var showingHeader = true
var physique: some View {
ZStack {
Colour.backgroundColor3
.edgesIgnoringSafeArea(.all)
VStack(spacing: 0) {
if showingHeader {
GlobalContactsFilterView(viewModel: viewModel)
.body(top: 60)
.transition(
.uneven(
insertion: .push(from: .high),
elimination: .push(from: .backside)
)
)
}
if viewModel.isRefreshing {
VStack {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
}
.body(maxWidth: .infinity)
.padding(.high, 10)
}
if viewModel.contacts.isEmpty && !viewModel.isLoading && !viewModel.isRefreshing {
NoDataView()
} else {
GeometryReader { outer in
let outerHeight = outer.measurement.top
ScrollView(.vertical) {
LazyVStack {
ForEach(Array(viewModel.contacts.enumerated()), id: .component.id) { index, contact in
VStack(alignment: .main) {
VStack {
VStack(spacing: 12) {
if contactVisibleFields.accommodates(.firstName) {
ContactDetailField(title: Strings.firstName, textual content: contact.firstName, textFont: .system(measurement: 13, weight: .semibold))
}
Rectangle()
.fill(.clear)
.body(maxWidth: .infinity, maxHeight: 1)
}
.padding(.high, 10)
}
.body(maxWidth: .infinity)
.background(.backgroundColor2)
.padding(.backside, 3)
}
.body(maxWidth: .infinity)
.background(index == viewModel.contacts.rely - 1 ? Colour.clear : Colour.filterBarBackground)
.padding(.backside, index == viewModel.contacts.rely - 1 ? 50 : 0)
}
.listRowBackground(Colour.clear)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)
HStack {
Spacer()
if viewModel.isLoading && !viewModel.isRefreshing {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
} else {
Colour.clear
.body(top: 1)
.onAppear {
if let _ = clerk.consumer {
viewModel.fetchContacts()
}
}
}
Spacer()
}
.body(maxWidth: .infinity)
.listRowSeparator(.hidden)
.listRowBackground(Colour.clear)
}
.background {
GeometryReader { proxy in
let contentHeight = proxy.measurement.top
let minY = max(
min(0, proxy.body(in: .named("ScrollView")).minY),
outerHeight - contentHeight
)
Colour.clear
.onChange(of: minY) { oldVal, newVal in
if (showingHeader && newVal oldVal {
showingHeader = newVal > oldVal
}
}
}
}
}
.coordinateSpace(title: "ScrollView")
}
.padding(.high, 1)
}
}
//.body(maxHeight: .infinity, alignment: .high)
.animation(.easeInOut, worth: showingHeader)