本例主要是根据判断GeometryReader距离系统顶部实时监听用户下拉操作。
源代码地址:https://github.com/Zyf210304/TopRefreshSwiftUI
// TopRefresh Created by 张亚飞 on 2021/1/24.
import SwiftUI
struct ContentView: View {
//准备一个菜谱列表显示数组
@State private var recipeData = ["豉椒炒花甲", "招待宾客的高配菜", "莲藕炖肉", "板栗焖排骨", "农家小炒牛肉丝", "招待宾客的高配菜"]
@State private var isRefresh = Refresh(started: false, released: false) //刷新状态
var body: some View {
VStack (spacing: 0){
TopTitle(title: "下拉更新列表")
/*Divider 当包含在stack中时,Divider横跨stack的短轴;当不在stack中时则水平延伸。*/
Divider()//分频器:可用于分隔其他内容。
ScrollView{
//监听下拉事件
GeometryReader { reader -> AnyView in
//利用GeometryReader监听容器向下拉的距离
DispatchQueue.main.async {
//更新黑夜开始偏移值为距离顶部值
if isRefresh.startOffset == 0 {
isRefresh.startOffset = reader.frame(in: .global).minY//当前reader距离系统顶部的值:120
print("起始偏移量(startOffset):",isRefresh.startOffset) //DeBug:打印全局Y轴偏移量(被更新为了120)
}
isRefresh.offset = reader.frame(in: .global).minY //实时记录偏移(用户下拉实时更新)
//print("偏移量(offset):",isRefresh.offset) //DeBug:打印全局Y轴偏移量(实时打印)
if isRefresh.offset - isRefresh.startOffset > 80 && !isRefresh.started {
print("偏移差:",isRefresh.offset - isRefresh.startOffset)//偏移差超过80更新started状态
isRefresh.started = true //拉得够长(超过80)更新started状态
}
//用户松手offset == startOffset(偏移位置 == 初始偏移的位置)&&下拉超过80&&未发布状态(released == false)
if isRefresh.startOffset == isRefresh.offset && isRefresh.started && !isRefresh.released {
withAnimation(Animation.linear) {//启用动画
isRefresh.released = true //更新数据(允许追加数据)
}
updateData()//更新数据
}
//存在无效下拉时,重新激活更新一次数据
if isRefresh.startOffset == isRefresh.offset && isRefresh.started && isRefresh.released && isRefresh.invalid {
isRefresh.invalid = false //设置为有效状态
updateData()
}
}
return AnyView(Color.black.frame(width: 0, height: 0))
}
.frame(width: 0, height: 0)
//视图显示
ZStack(alignment: Alignment(horizontal: .center, vertical: .top)) {
if isRefresh.started && isRefresh.released {
ProgressView()
.offset(y : -35)
}else {
Image(systemName: "arrow.down")
.font(.system(size: 16, weight: .heavy))
.foregroundColor(.gray)
.rotationEffect(.init(degrees: isRefresh.started ? 180 : 0))
.offset(y : -25)
.animation(.easeIn)
}
VStack {
ForEach(recipeData, id:.self) { title in
singleRecipeList(title:title) //列表项
}
}
.background(Color.white)
}
.offset(y : isRefresh.released ? 40 : -10)
}
}
.background(Color.black.opacity(0.06).ignoresSafeArea())
}
//向数组里追回数据
func updateData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation(Animation.linear) {
//用户松手(列表弹回去了)
if isRefresh.startOffset == isRefresh.offset {
recipeData.append("追加菜谱" + String(recipeData.count))//向数组里追加数据
isRefresh.released = false//发布完成改状态(下次重用)
isRefresh.started = false//改状态下次用
}else {
print("updateData()无效下拉")
isRefresh.invalid = true //无效下拉(还没有弹回顶部又拉了),因为上次的数据还没有追加完成呢!
}
}
}
}
}
//刷新状态
struct Refresh {
var startOffset : CGFloat = 0 //开始偏移位置默认为0
var offset : CGFloat = 0 //偏移量
var started : Bool//初始状态
var released : Bool//刷新结果状态
var invalid : Bool = false //验证下拉是否有效,未回到起始位置为true
}
//顶部标题
struct TopTitle:View {
var title:String
var body: some View{
HStack {
Spacer()
Text(title)
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.orange)
Spacer()
}
.padding()
.background(Color.white.ignoresSafeArea(.all, edges: .top))
}
}
//单个菜谱项
struct singleRecipeList:View {
var title:String
var body: some View{
HStack {
Image(systemName: "bookmark.circle.fill")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40)
.padding(.trailing)
Text(title)
.font(.title3)
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(.black)
}
.padding()
}
}
//预览视图
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:https://www.55mx.com/post/115
《【SwiftUI实战】下拉更新(追加)列表项》的网友评论(0)