双向滚动UITableViews

我已经知道您在想什么……这没有道理。 UITableView是通过垂直滚动来显示其内容的,为什么您要一个双向滚动呢?

当前,在Shine Labs,我们正在构建一个iPad应用程序,该应用程序使用具有非常宽内容的单元格来实现全屏UITableView。 随着开发的进行,客户希望该应用程序同时支持iPad多任务处理和iPhone。 为了做到这一点,我们需要采取一种对时间敏感且微创的方法来适应各种屏幕尺寸。 我们想出的解决方案非常简单,并允许我们重用整个现有的UI,并有一些小的警告。

通过在运行时将UITableView嵌入UIScrollView中,我们可以确定UITableView是否足够宽以容纳其所有内容。 如果没有,我们将其宽度增加一定程度,并使UIScrollView能够水平滚动。

让我们来看一个实际的例子。

在UIViewController的viewDidLoad方法中,我们配置UITableView和UIScrollView的布局。

为了配置布局,我们使用了自动布局抽象库SnapKit来使约束代码更加简洁。

  私人  scrollView = UIScrollView() 
私人 tableView = UITableView()
  覆盖 func viewDidLoad(){ 
超级 .viewDidLoad()
configureConstraints()
}
  私人 功能 configureConstraints(){ 
view.addSubview(scrollView)
  scrollView.snp.makeConstraints { 
$ 0.edges.equalToSuperview()
}
  scrollView.addSubview(tableView) 

tableViewWidth = view.frame.width
tableView.snp.makeConstraints {
$ 0.edges.equalToSuperview()
$ 0.height.equalTo(视图)
$ 0.width.equalTo(tableViewWidth)
}
}

我们首先将scrollView边缘固定到视图。 然后,将tableView边缘固定到scrollView ,使tableView高度等于view高度,并为tableView赋予一个宽度,该宽度的常数等于view.frame.width

设置约束后,我们需要配置scrollView

  覆盖 func viewDidLoad(){ 
超级 .viewDidLoad()
configureConstraints()
configureScrollView()
}
  私人 功能 configureScrollView(){ 
scrollView.isDirectionalLockEnabled = true
scrollView.bounces = false
}

通过将isDirectionalLockEnabled设置为true ,我们阻止了scrollView能够同时在两个方向上滚动。

通过将scrollView设置为false ,我们可以防止scrollView过度滚动,并在滚动后执行本机UIScrollView bounce动画。

然后,我们需要确定屏幕尺寸是否足够大以容纳tableView内容。 为此,我们创建了一个计算属性compactModeActive

  私人 var compactModeActive:布尔{ 
// 504是多任务模式下的iPad Pro 10.5“
//根据您的视图需求,此值是任意的
返回 view.frame.width <505
}

最后,在UIViewController的viewDidLayoutSubviews方法中,我们更新了tableView宽度,并将scrollView.isScrollEnabled属性设置为compactModeActive

  覆盖 func viewDidLayoutSubviews(){ 
超级 .viewDidLayoutSubviews()
scrollView.isScrollEnabled = compactModeActive
updateConstraints()
}
  私人 功能 updateConstraints(){ 
viewWidth = view.frame.width
tableViewWidth:CGFloat = compactModeActive吗? viewWidth * 2:viewWidth
  tableView.snp.updateConstraints { 
$ 0.width.equalTo(tableViewWidth)
}
}

好极了! 我们已经成功实现了tableView以根据屏幕尺寸水平滚动。 但是我们尚未完成-tableView使用UIRefreshControl作为触发数据重新加载的方式。 由于当compactModeActivetrue ,我们已将tableView的宽度增加了2 compactModeActive ,因此UIRefreshControl仍位于tableView的中心。

为了正确refreshControl ,我们必须做一点框架数学以更新其bounds

  私人让 refreshControl = UIRefreshControl() 
  私人 var refreshControlX:CGFloat { 
返回 [[scrollView.contentSize.width / 2)-(scrollView.frame.width / 2))-scrollView.contentOffset.x
}
  覆盖 func viewDidLayoutSubviews(){ 
超级 .viewDidLayoutSubviews()
scrollView.isScrollEnabled = compactModeActive
updateConstraints()
updateRefreshControl()
}
  私人 功能 updateRefreshControl(){ 
守卫 compactModeActive else {
refreshControl.bounds.origin.x = 0
返回
}
  refreshControl.bounds.origin.x = refreshControlX 
}
  私人 功能 configureScrollView(){ 
scrollView.isDirectionalLockEnabled = true
scrollView.bounces = false
scrollView.delegate =自我
}
  扩展 ViewController:UIScrollViewDelegate { 
func scrollViewDidScroll( _ scrollView:UIScrollView){
updateRefreshControl()
}
}

首先,我们创建了一个计算属性refreshControlX ,以计算refreshControlX的值,该值是scrollView.contentSize.width一半减去scrollView.contentOffset.x一半减去scrollView.contentOffset.x

然后,我们的UIViewController采用了UIScrollViewDelegate,并在scrollViewDidScroll期间调用了updateRefreshControl 。 当用户来回滚动时,我们将利用scrollView.contentOffset.x来确定bounds.origin.x的新refreshControl

最后,在viewDidLayoutSubviews ,我们再次调用updateRefreshControl以根据帧大小的变化重新定位refreshControl

就是这样!

感谢您抽出宝贵的时间阅读这篇文章,如果您有任何疑问,请随时发表评论!

示例代码可以在这里找到:https://github.com/shine-labs/Bidirectional-Scrolling-UITableView

如有任何开发疑问,请联系dylan@shinelabs.tech。