SwiftUI - 滚动时隐藏/折叠部分

Jas*_*ain 7 animation scrollview swiftui

我一直在寻找一种方法来复制 Airbnb 和许多其他公司所做的滚动动画,视图的顶部折叠/隐藏在滚动中,并在用户再次开始向上滚动时立即重新出现。请注意“日期”和“访客”按钮在附加图像中滚动时如何变透明。

爱彼迎

下面我附上了一个我刚刚拼凑起来的简单视图。我试过包括我想在滚动视图内外折叠的区域。我猜它需要在滚动视图之外,因为它会独立于您在滚动区域内的位置进行动画处理。

import SwiftUI

struct HideScrollView: View {
var body: some View {
    ScrollView {
        HStack {
            Text("Hide Me")
            Spacer()
        }.padding(.horizontal) .frame(height: 60) .background(Color.red) .foregroundColor(Color.white)
        ForEach(0 ..< 20) { item in
            VStack {
                HStack {
                    Text("Content Items")
                    Spacer()
                }.padding(.horizontal) .frame(height: 40)
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

}

小智 7

由于没有像 didScroll 或 any 之类的 scrollView 委托,因此有两种方法可以实现所需的结果。

#1 你应该实现 DragGesture 手势,这不是首选方式,因为手势不会被同时调用。

.simultaneousGesture(DragGesture().onChanged({ transition in
    if transition.translation.height > 0{
        withAnimation{
            self.viewIsShown = true
        }
    } else {
        withAnimation{
            self.viewIsShown = false
        }
    }
})
)
Run Code Online (Sandbox Code Playgroud)

#2 首选方式:根据Martin 的教程,您应该使用GeometryReaderinsideScrollView作为第一个孩子,这使您有可能获得元素 inside 的确切位置GeometryReader,如下所示:

ScrollView(.vertical, showsIndicators: false) {
    GeometryReader { geometry in
        Color.clear.preference(key: OffsetKey.self, value: geometry.frame(in:.global).minY).frame(height: 0)
   }
//Your scrollable content here
}
   
Run Code Online (Sandbox Code Playgroud)

添加符合 PreferenceKey 协议的首选项键:

struct OffsetKey: PreferenceKey {
    static let defaultValue: CGFloat? = nil
    static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
        value = value ?? nextValue()
    }
}
Run Code Online (Sandbox Code Playgroud)

读取在首选项键中设置的值,这样做添加 .onPreferenceChange(OffsetKey.self)到 ScrollView 或其父视图中。

你应该有@State两种包装initialOffestoffset

.onPreferenceChange(OffsetKey.self) {
    if self.initialOffset == nil || self.initialOffset == 0 {
        self.initialOffset = $0
        //setting initialOffest when view first appeared.
    }
    self.offset = $0
    
}
Run Code Online (Sandbox Code Playgroud)

检查initialOffset和之间的差异offset会告诉您视图的滚动状态。如果initialOffset高于offset则您的视图向下滚动,您应该隐藏您想要的视图。

根据您发布的有问题的代码,它应该如下所示:

struct HideScrollView: View {
    
    @State var initialOffset: CGFloat?
    @State var offset: CGFloat?
    @State var viewIsShown: Bool = true
    
    var body: some View {
        
        VStack{
            HStack {
                Text("Hide Me")
                Spacer()
            }.padding(.horizontal) .frame(height: 60) .background(Color.red) .foregroundColor(Color.white).opacity(self.viewIsShown ? 1 : 0)
            
            ScrollView {
                
                GeometryReader { geometry in
                    Color.clear.preference(key: OffsetKey.self, value: geometry.frame(in: .global).minY)
                        .frame(height: 0)
                }
                
                ForEach(0 ..< 20) { item in
                    VStack {
                        HStack {
                            Text("Content Items")
                            Spacer()
                        }.padding(.horizontal) .frame(height: 40)
                    }
                }
            }
        }.onPreferenceChange(OffsetKey.self) {
            if self.initialOffset == nil || self.initialOffset == 0 {
                self.initialOffset = $0
            }
            
            self.offset = $0
            
            guard let initialOffset = self.initialOffset,
                let offset = self.offset else {
                return
            }
            
                
                if(initialOffset > offset){
                    self.viewIsShown = false
                    print("hide")
                } else {
                    self.viewIsShown = true
                    print("show")
                }
            
        
            
        }
        
    }
}

struct OffsetKey: PreferenceKey {
    static let defaultValue: CGFloat? = nil
    static func reduce(value: inout CGFloat?,
                       nextValue: () -> CGFloat?) {
        value = value ?? nextValue()
    }
}
Run Code Online (Sandbox Code Playgroud)