探究contentOffset和contentInset,并解决上拉加载更多,uitableview抖动问题

本文章主要研究:scrollView的几个属性contentSize、contentOffset和contentInset的关系。并提供解决拉加载更多,tableview抖动问题的方案 。

相关文章: UITableview Deceleration 加速滑动(惯性滑动)、弹性回归原理

概念

1. contentSize 是scrollview可以滚动的区域,比如frame = (0 ,0 ,320 ,480) contentSize = (320 ,960),代表你的scrollview可以上下滚动,滚动区域为frame大小的两倍。

2. contentOffset 是scrollview当前显示区域顶点相对于frame顶点的偏移量(向屏幕内拉,偏移量是负值。向屏幕外推,偏移量是正数),比如上个例子,从初始状态向下拉50像素,contentoffset就是(0 ,-50),从初始状态向上推tableview100像素,contentOffset就是(0 ,100)。

3. contentInset 是scrollview的contentview的顶点相对于scrollview的位置,例如你的contentInset = (0 ,100),那么你的contentview就是从scrollview的(0 ,100)开始显示.

UIEdgeInsets的四个属性,如下:

typedef struct UIEdgeInsets {
    CGFloat top, left, bottom, right;  // specify amount to inset (positive) for each of the edges. values can be negative to 'outset'
} UIEdgeInsets;

分别代表距离上,左,下,右边的像素长度。

下面举个iPad例子:


(图1)

tableview初始化后,如上图所示,并打印了各个属性的值:

2015-04-01 19:43:00.501 xxx[2058:871144] contentInset:{64, 0, 56, 0}
2015-04-01 19:43:00.502 xxx[2058:871144] contentOffset:{-0, -64}
2015-04-01 19:43:00.503 xxx[2058:871144] contentSize{1024, 2037}
2015-04-01 19:43:00.503 xxx[2058:871144] frame{{0, 0}, {1024, 768}}
2015-04-01 19:43:00.504 xxx[2058:871144] bounds{{-0, -64}, {1024, 768}}

● 首先从contentInset分析,top=64,即NaviBar的高度,意思是从这儿开始显示tableview,而不是从(0,0)开始显示。

● 那么contentOffset = -64也就不难理解了:当前显示区域顶点相对于frame的偏移量,即-64。你可以这么理解:它等同于从(0,0)下拉到了y = 64的位置。

● 最后contentSize,宽度是iPad的屏幕宽度,而高度2037 = (cell.count 乘以 cell.height)。

● 另外两个属性frame是提前设定的pad的屏幕长和宽,bounds则是{{-0, -64}, {1024, 768}}


设置contentInset的好处

有要求如下:tableview显示的区域是NaviBar和tabBar中间的区域,且tableView滑动通过NaivBar和tabBar的时候,仍可以透过背景显示,故设置contentInset可以达到此效果。如图:


(图2)

上图描述了两个场景:

1 初始化的时候,tableview顶头是NaviBar的下沿。

2. 向上推的时候,通过NaviBar的背景可以看到tableView。

仅仅设置tableview的frame是无法达到这个效果的。


解决上拉加载更多,tableview抖动问题

【问题描述】在一个自己实现加载更多的App中,当上拉操作的时候,从网络端下载下来数据,并更新tableview,如图loading所示:


(图3,上拉刷新,loading)

2015-04-01 19:04:16.078 xxx[2044:865035] contentInset:{64, 0, 56, 0}
2015-04-01 19:04:16.079 xxx[2044:865035] setContentOffset:{-0, -64}
2015-04-01 16:40:20.573 xxx[1869:836104] contentInset:{64, 0, 172, 0}
2015-04-01 16:40:20.575 xxx[1869:836104] setContentOffset:{0, 1441}
2015-04-01 16:40:20.789 xxx[1869:836104] contentInset:{64, 0, 56, 0}
2015-04-01 16:40:20.792 xxx[1869:836104] setContentOffset:{0, 1325}
从上面的log中可以看到,tableView初始化后,contentInset.bottom是56 top是64(恰好是tabBar和NaviBar的高度)。

● 然后出现contentInset.bottom = 172,因为此时“加载更多”的view加到了tableview的末尾,所以contentInset.bottom += “加载更多”的view.height ,最后即是172.

● 最后,contentInset.bottom又恢复为56,这是因为“加载更多”的view隐藏了,tableview的ContentInset又恢复了,效果如上图3。

    当“加载更多”获取数据下来,tableview更新后。由于contentInset从56—>172—>56。所以,会有一个抖动的现象:如下图4:


(图4)

从loading开始,加载更多后,“悄巴蜀”这个cell出来了,但是tableview先向下滑动,在向上滑动,产生了抖动现象

【原因】当loading的时候,contentInset.bottom是172,当loading隐藏的时候contentInset.bottom = 56.这是因为在对tableview的contentInset赋值的时候,contentOffset也会相应改变。contentOffset的变化导致了抖动。

下面的log展示了,contentInset和contentOffset相关联的问题:

2015-04-01 16:40:20.573 xxx[1869:836104] contentInset:{64, 0, 172, 0}
2015-04-01 16:40:20.575 xxx[1869:836104] setContentOffset:{0, 1441}
2015-04-01 16:40:20.789 xxx[1869:836104] contentInset:{64, 0, 56, 0}
2015-04-01 16:40:20.792 xxx[1869:836104] setContentOffset:{0, 1325}
两次contentOffset的差值116,正好是contentInset的差值。
【解决】tableview的contentInset还是要恢复的,但是contentOffset达到1441的较高值后,后面1325就可以忽略了。这样即可解决抖动

另外:当loading失败的时候,没有新的cell进来,会不会造成tableview末端悬空?

答案是:不会悬空;这是因为:无论如何contentInset一定会恢复。既然contentInset已经恢复为56了,那么即便contentOffset在较高位置,它也会自动滑下来。因为contentInset限制了tableview一定要滑回来。类似与普通的tableview,用户手动将tableview的尾部向上拉,松手后,tableview自动还原到原来位置。

(完)



测试图片来自 http://www.th7.cn/Program/IOS/201412/326493.shtml

部分内容摘自:http://www.cnblogs.com/pengyingh/articles/2346128.html

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 SwiftUI 中,LazyVGrid 是一种用于创建网格布局的容器视图。它可以根据需要自动加载和显示项目,以提高性能和内存效率。要实现上拉加载更多的功能,你可以结合使用 LazyVGrid 和 ScrollView。 下面是一种实现上拉加载更多的方法: 1. 创建一个状态变量来跟踪是否需要加载更多数据,例如 `@State` 修饰的变量 `isLoadingMore`。 2. 在 LazyVGrid 的外部包裹一个 ScrollView,并监听滚动位置的变化。 3. 当滚动位置接近底部时,触发加载更多数据的操作。 下面是一个示例代码: ```swift struct ContentView: View { @State private var items: [String] = ["Item 1", "Item 2", "Item 3"] @State private var isLoadingMore = false var body: some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) { ForEach(items, id: \.self) { item in Text(item) .frame(height: 100) } } .padding() .onAppear { // 监听滚动位置 UIScrollView.appearance().delegate = context.coordinator } } .onReceive(context.coordinator.didScrollPublisher) { _ in // 判断是否接近底部 let offset = context.coordinator.scrollView.contentOffset.y let height = context.coordinator.scrollView.contentSize.height let screenHeight = UIScreen.main.bounds.height if offset > height - screenHeight * 2 { // 开始加载更多数据 loadMoreData() } } } private func loadMoreData() { // 模拟加载更多数据的操作 isLoadingMore = true DispatchQueue.main.asyncAfter(deadline: .now() + 2) { items.append(contentsOf: ["Item 4", "Item 5", "Item 6"]) isLoadingMore = false } } // Coordinator 用于监听滚动位置的变化 class Coordinator: NSObject, UIScrollViewDelegate { let didScrollPublisher = PassthroughSubject<Void, Never>() var scrollView: UIScrollView! func scrollViewDidScroll(_ scrollView: UIScrollView) { self.scrollView = scrollView didScrollPublisher.send(()) } } func makeCoordinator() -> Coordinator { Coordinator() } } ``` 在上面的示例中,我们使用了 `ScrollView` 和 `LazyVGrid` 来创建一个带有网格布局的可滚动视图。通过监听滚动位置的变化,当滚动位置接近底部时,触发加载更多数据的操作。 注意,上述代码中的 `loadMoreData()` 方法只是一个示例,你可以根据实际需求来实现加载更多数据的逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值