繁体   English   中英

SwiftUI:向 LazyVStack 添加可刷新?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM