如何在 Swiftui 中拖动以刷新网格视图 (LazyVGrid)?
How can you Drag to refresh a Grid View (LazyVGrid) in Swiftui?
在swiftui中如何拖动刷新网格视图?我知道您可以在 iOS 15 中使用带有可刷新修饰符的列表视图来做到这一点,但是如何使用 LazyVGrid 来做到这一点?在 iOS 15 之前的列表或网格视图中,您将如何操作?我在 swiftui 还很新。我附上了一张 gif,展示了我正在努力实现的目标。
Drag to Refresh
代码如下LazyVStack
:
import SwiftUI
struct PullToRefreshSwiftUI: View {
@Binding private var needRefresh: Bool
private let coordinateSpaceName: String
private let onRefresh: () -> Void
init(needRefresh: Binding<Bool>, coordinateSpaceName: String, onRefresh: @escaping () -> Void) {
self._needRefresh = needRefresh
self.coordinateSpaceName = coordinateSpaceName
self.onRefresh = onRefresh
}
var body: some View {
HStack(alignment: .center) {
if needRefresh {
VStack {
Spacer()
ProgressView()
Spacer()
}
.frame(height: 100)
}
}
.background(GeometryReader {
Color.clear.preference(key: ScrollViewOffsetPreferenceKey.self,
value: [=10=].frame(in: .named(coordinateSpaceName)).origin.y)
})
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { offset in
guard !needRefresh else { return }
if abs(offset) > 50 {
needRefresh = true
onRefresh()
}
}
}
}
struct ScrollViewOffsetPreferenceKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
这是典型用法:
struct ContentView: View {
@State private var refresh: Bool = false
@State private var itemList: [Int] = {
var array = [Int]()
(0..<40).forEach { value in
array.append(value)
}
return array
}()
var body: some View {
ScrollView {
PullToRefreshSwiftUI(needRefresh: $refresh,
coordinateSpaceName: "pullToRefresh") {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation { refresh = false }
}
}
LazyVStack {
ForEach(itemList, id: \.self) { item in
HStack {
Spacer()
Text("\(item)")
Spacer()
}
}
}
}
.coordinateSpace(name: "pullToRefresh")
}
}
这可以很容易地适应 LazyVGrid
,只需替换 LazyVStack
。
编辑:
这是更精致的变体:
struct PullToRefresh: View {
private enum Constants {
static let refreshTriggerOffset = CGFloat(-140)
}
@Binding private var needsRefresh: Bool
private let coordinateSpaceName: String
private let onRefresh: () -> Void
init(needsRefresh: Binding<Bool>, coordinateSpaceName: String, onRefresh: @escaping () -> Void) {
self._needsRefresh = needsRefresh
self.coordinateSpaceName = coordinateSpaceName
self.onRefresh = onRefresh
}
var body: some View {
HStack(alignment: .center) {
if needsRefresh {
VStack {
Spacer()
ProgressView()
Spacer()
}
.frame(height: 60)
}
}
.background(GeometryReader {
Color.clear.preference(key: ScrollViewOffsetPreferenceKey.self,
value: -[=12=].frame(in: .named(coordinateSpaceName)).origin.y)
})
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { offset in
guard !needsRefresh, offset < Constants.refreshTriggerOffset else { return }
withAnimation { needsRefresh = true }
onRefresh()
}
}
}
private struct ScrollViewOffsetPreferenceKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
private enum Constants {
static let coordinateSpaceName = "PullToRefreshScrollView"
}
struct PullToRefreshScrollView<Content: View>: View {
@Binding private var needsRefresh: Bool
private let onRefresh: () -> Void
private let content: () -> Content
init(needsRefresh: Binding<Bool>,
onRefresh: @escaping () -> Void,
@ViewBuilder content: @escaping () -> Content) {
self._needsRefresh = needsRefresh
self.onRefresh = onRefresh
self.content = content
}
var body: some View {
ScrollView {
PullToRefresh(needsRefresh: $needsRefresh,
coordinateSpaceName: Constants.coordinateSpaceName,
onRefresh: onRefresh)
content()
}
.coordinateSpace(name: Constants.coordinateSpaceName)
}
}
在swiftui中如何拖动刷新网格视图?我知道您可以在 iOS 15 中使用带有可刷新修饰符的列表视图来做到这一点,但是如何使用 LazyVGrid 来做到这一点?在 iOS 15 之前的列表或网格视图中,您将如何操作?我在 swiftui 还很新。我附上了一张 gif,展示了我正在努力实现的目标。
Drag to Refresh
代码如下LazyVStack
:
import SwiftUI
struct PullToRefreshSwiftUI: View {
@Binding private var needRefresh: Bool
private let coordinateSpaceName: String
private let onRefresh: () -> Void
init(needRefresh: Binding<Bool>, coordinateSpaceName: String, onRefresh: @escaping () -> Void) {
self._needRefresh = needRefresh
self.coordinateSpaceName = coordinateSpaceName
self.onRefresh = onRefresh
}
var body: some View {
HStack(alignment: .center) {
if needRefresh {
VStack {
Spacer()
ProgressView()
Spacer()
}
.frame(height: 100)
}
}
.background(GeometryReader {
Color.clear.preference(key: ScrollViewOffsetPreferenceKey.self,
value: [=10=].frame(in: .named(coordinateSpaceName)).origin.y)
})
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { offset in
guard !needRefresh else { return }
if abs(offset) > 50 {
needRefresh = true
onRefresh()
}
}
}
}
struct ScrollViewOffsetPreferenceKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
这是典型用法:
struct ContentView: View {
@State private var refresh: Bool = false
@State private var itemList: [Int] = {
var array = [Int]()
(0..<40).forEach { value in
array.append(value)
}
return array
}()
var body: some View {
ScrollView {
PullToRefreshSwiftUI(needRefresh: $refresh,
coordinateSpaceName: "pullToRefresh") {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation { refresh = false }
}
}
LazyVStack {
ForEach(itemList, id: \.self) { item in
HStack {
Spacer()
Text("\(item)")
Spacer()
}
}
}
}
.coordinateSpace(name: "pullToRefresh")
}
}
这可以很容易地适应 LazyVGrid
,只需替换 LazyVStack
。
编辑: 这是更精致的变体:
struct PullToRefresh: View {
private enum Constants {
static let refreshTriggerOffset = CGFloat(-140)
}
@Binding private var needsRefresh: Bool
private let coordinateSpaceName: String
private let onRefresh: () -> Void
init(needsRefresh: Binding<Bool>, coordinateSpaceName: String, onRefresh: @escaping () -> Void) {
self._needsRefresh = needsRefresh
self.coordinateSpaceName = coordinateSpaceName
self.onRefresh = onRefresh
}
var body: some View {
HStack(alignment: .center) {
if needsRefresh {
VStack {
Spacer()
ProgressView()
Spacer()
}
.frame(height: 60)
}
}
.background(GeometryReader {
Color.clear.preference(key: ScrollViewOffsetPreferenceKey.self,
value: -[=12=].frame(in: .named(coordinateSpaceName)).origin.y)
})
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { offset in
guard !needsRefresh, offset < Constants.refreshTriggerOffset else { return }
withAnimation { needsRefresh = true }
onRefresh()
}
}
}
private struct ScrollViewOffsetPreferenceKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
private enum Constants {
static let coordinateSpaceName = "PullToRefreshScrollView"
}
struct PullToRefreshScrollView<Content: View>: View {
@Binding private var needsRefresh: Bool
private let onRefresh: () -> Void
private let content: () -> Content
init(needsRefresh: Binding<Bool>,
onRefresh: @escaping () -> Void,
@ViewBuilder content: @escaping () -> Content) {
self._needsRefresh = needsRefresh
self.onRefresh = onRefresh
self.content = content
}
var body: some View {
ScrollView {
PullToRefresh(needsRefresh: $needsRefresh,
coordinateSpaceName: Constants.coordinateSpaceName,
onRefresh: onRefresh)
content()
}
.coordinateSpace(name: Constants.coordinateSpaceName)
}
}