[英]SwiftUI: Add refreshable to LazyVStack?
当我使用列表视图时,我可以轻松添加refreshable
修饰符来触发刷新逻辑。 我的问题是如何在使用 LazyVStack 时达到同样的效果。
我有以下代码:
struct TestListView: View {
var body: some View {
Text("the list view")
// WORKS:
// VStack {
// List {
// ForEach(0..<10) { n in
// Text("N = \(n)")
// }
// }
// .refreshable {
//
// }
// }
// DOES NOT SHOW REFRESH CONTROL:
ScrollView {
LazyVStack {
ForEach(0..<10) { n in
Text("N = \(n)")
}
}
}
.refreshable {
}
}
}
如何在LazyVStack
案例中获得刷新行为?
实际上这是可能的,我想说,我理解 Apple 的想法 - 他们为重List
提供默认的内置行为,但只准备轻量级ScrollView
以便我们可以以我们需要的任何方式对其进行自定义。
所以这里是一个解决方案的演示(使用 Xcode 13.4 / iOS 15.5 测试)
主要部分:
struct ContentView: View {
var body: some View {
ScrollView {
RefreshableView()
}
.refreshable { // << injects environment value !!
await fetchSomething()
}
}
}
struct RefreshableView: View {
@Environment(\.refresh) private var refresh // << refreshable injected !!
@State private var isRefreshing = false
var body: some View {
VStack {
if isRefreshing {
MyProgress()
.transition(.scale)
}
// ...
.onPreferenceChange(ViewOffsetKey.self) {
if $0 < -80 && !isRefreshing { // << any creteria we want !!
isRefreshing = true
Task {
await refresh?() // << call refreshable !!
await MainActor.run {
isRefreshing = false
}
}
}
}
基于Asperi 的回答:
import SwiftUI
struct ContentView: View {
var body: some View {
ScrollView {
RefreshableView {
RoundedRectangle(cornerRadius: 20)
.fill(.red).frame(height: 100).padding()
.overlay(Text("Button"))
.foregroundColor(.white)
}
}
.refreshable { // << injects environment value !!
await fetchSomething()
}
}
func fetchSomething() async {
// demo, assume we update something long here
try? await Task.sleep(nanoseconds: 2 * 1_000_000_000)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct RefreshableView<Content: View>: View {
var content: () -> Content
@Environment(\.refresh) private var refresh // << refreshable injected !!
@State private var isRefreshing = false
var body: some View {
VStack {
if isRefreshing {
MyProgress() // ProgressView() ?? - no, it's boring :)
.transition(.scale)
}
content()
}
.animation(.default, value: isRefreshing)
.background(GeometryReader {
// detect Pull-to-refresh
Color.clear.preference(key: ViewOffsetKey.self, value: -$0.frame(in: .global).origin.y)
})
.onPreferenceChange(ViewOffsetKey.self) {
if $0 < -80 && !isRefreshing { // << any creteria we want !!
isRefreshing = true
Task {
await refresh?() // << call refreshable !!
await MainActor.run {
isRefreshing = false
}
}
}
}
}
}
struct MyProgress: View {
@State private var isProgress = false
var body: some View {
HStack{
ForEach(0...4, id: \.self){index in
Circle()
.frame(width:10,height:10)
.foregroundColor(.red)
.scaleEffect(self.isProgress ? 1:0.01)
.animation(self.isProgress ? Animation.linear(duration:0.6).repeatForever().delay(0.2*Double(index)) :
.default
, value: isProgress)
}
}
.onAppear { isProgress = true }
.padding()
}
}
public struct ViewOffsetKey: PreferenceKey {
public typealias Value = CGFloat
public static var defaultValue = CGFloat.zero
public static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
只需在您的项目中创建文件RefreshableScrollView
public struct RefreshableScrollView<Content: View>: View {
var content: Content
var onRefresh: () -> Void
public init(content: @escaping () -> Content, onRefresh: @escaping () -> Void) {
self.content = content()
self.onRefresh = onRefresh
}
public var body: some View {
List {
content
.listRowSeparatorTint(.clear)
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.listStyle(.plain)
.refreshable {
onRefresh()
}
}
}
然后在项目中的任何地方使用RefreshableScrollView
例子:
RefreshableScrollView{
// Your content LaztVStack{}
} onRefresh: {
// do something you want
}
现在 SwiftUI 将.refreshable
修饰符添加到ScrollView
。
就像使用List
一样使用它
ScrollView {
LazyVStack {
// Loop and add View
}
}
.refreshable {
refreshLogic()
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.