SwiftUI可折叠头部下拉手势后意外隐藏及优化后抖动/初始不显示问题求助
SwiftUI可折叠头部下拉手势后意外隐藏及优化后抖动/初始不显示问题求助
大家好,我最近在SwiftUI里实现一个滚动响应的可折叠头部:向下滚动列表时收起头部,向上滚动时展开头部。跟着教程做完后,正常滚动时功能都没问题,但遇到了两个头疼的问题,想请教下大家怎么解决:
第一个问题:下拉手势导致头部意外消失
当用户从列表顶部下拉(类似触发下拉刷新的手势)然后松开,头部会直接消失,录屏里能看到这个异常行为。下面是我最开始的实现代码:
public struct CollapsibleHeaderList: View { // MARK: - Private Vars private let items = Array(0..<100) @State private var currentHeight: Double = 0 @State private var lastOffset: Double = 0 @State private var animationDuration: TimeInterval = 0.2 @State private var lastAnimationDate: Date? private var expandedHeight: CGFloat = 40.0 @ObservedObject var viewModel: CollapsibleHeaderViewModel public init(viewModel: CollapsibleHeaderViewModel) { self.viewModel = viewModel } // MARK: - View public var body: some View { VStack { Rectangle() .foregroundColor(.pink) .frame(height: currentHeight) List { ForEach(items, id: \.self) { item in Text("Item \(item)") .onAppear { viewModel.onCellAppear(index: item) } } } } .environment(\.defaultMinListRowHeight, 0) .listStyle(.plain) .onReceive(viewModel.$state) { state in DispatchQueue.main.async { switch(state) { case .collapse: collapseHeader() case .expand: expandHeader() case .initial: break } } } } func expandHeader() { withAnimation(.easeOut(duration: animationDuration)) { currentHeight = expandedHeight lastAnimationDate = Date() } } func collapseHeader() { withAnimation(.easeOut(duration: animationDuration)) { currentHeight = 0 lastAnimationDate = Date() } } func didFinishLastAnimation() -> Bool { guard let lastAnimationDate else { return true } return abs(lastAnimationDate.timeIntervalSinceNow) > animationDuration } } public enum HeaderState { case initial case collapse case expand } public class CollapsibleHeaderViewModel: ObservableObject { @Published private(set) public var state: HeaderState private var indexSubject = PassthroughSubject<Int, Never>() private var cancellables = Set<AnyCancellable>() init() { self.state = .initial setupCollapsibleHeaderListener() } public func onCellAppear(index: Int) { indexSubject.send(index) } private func setupCollapsibleHeaderListener() { indexSubject .throttle(for: .seconds(0.5), scheduler: DispatchQueue.main, latest: true) .withPrevious() .map { (previous, current) in if let previous, previous < current { return .collapse } else { return .expand } } .removeDuplicates() .sink { [weak self] headerState in self?.state = headerState }.store(in: &cancellables) } } extension Publisher { /// Includes the current element as well as the previous element from the upstream publisher in a tuple where the previous element is optional. /// The first time the upstream publisher emits an element, the previous element will be `nil`. /// /// let range = (1...5) /// cancellable = range.publisher /// .withPrevious() /// .sink { print ("(\($0.previous), \($0.current))", terminator: " ") } /// // Prints: "(nil, 1) (Optional(1), 2) (Optional(2), 3) (Optional(3), 4) (Optional(4), 5) ". /// /// - Returns: A publisher of a tuple of the previous and current elements from the upstream publisher. public func withPrevious() -> AnyPublisher<(previous: Output?, current: Output), Failure> { scan(Optional<(Output?, Output)>.none) { ($0?.1, $1) } .compactMap { $0 } .eraseToAnyPublisher() } }
第二个问题:优化后出现的抖动与初始不显示
后来我尝试了@Benzy Neez的解决方案,引入了showingHeader状态来控制头部显示,结果又出现新问题:头部出现时会有奇怪的抖动/闪烁效果,而且视图首次加载的时候,头部直接就不见了,完全不显示。修改后的核心代码如下:
@State private var showingHeader = true var body: some View { ZStack { Color.backgroundColor3 .edgesIgnoringSafeArea(.all) VStack(spacing: 0) { if showingHeader { GlobalContactsFilterView(viewModel: viewModel) .frame(height: 60) .transition( .asymmetric( insertion: .push(from: .top), removal: .push(from: .bottom) ) ) } if viewModel.isRefreshing { VStack { ProgressView() .progressViewStyle(CircularProgressViewStyle()) } .frame(maxWidth: .infinity) .padding(.top, 10) } if viewModel.contacts.isEmpty && !viewModel.isLoading && !viewModel.isRefreshing { NoDataView() } else { GeometryReader { outer in let outerHeight = outer.size.height ScrollView(.vertical) { LazyVStack { ForEach(Array(viewModel.contacts.enumerated()), id: \.element.id) { index, contact in VStack(alignment: .leading) { VStack { VStack(spacing: 12) { if contactVisibleFields.contains(.firstName) { // 这里是Contact的具体内容 } } } } } } } } } } } }
现在想请教各位:
- 最初的下拉手势导致头部消失的问题,该怎么从根源上解决?
- 修改后出现的头部抖动和初始不显示的问题,又该如何修复?
内容来源于stack exchange
相关产品推荐
相关产品推荐

