如何使ScrollView中的TableView滚动行为自然


78

我需要做一个配置很奇怪的程序。

如下图所示,主视图是UIScrollView。然后,它的内部应该有一个UIPageView,而PageView的每个页面都应该有一个UITableView。

数字

到目前为止,我已经完成了所有这一切。但我的问题是,我想滚动到表现 自然

接下来是我自然的意思。当前,当我在其中一个UITableViews上滚动时,它将滚动tableview(而不是scrollview)。但是我希望它滚动ScrollView,除非scrollview无法滚动导致它到达顶部或底部(在那种情况下,我希望它滚动tableview)。

例如,假设我的滚动视图当前滚动到顶部。然后,我将手指放在(显示当前页面的)表格视图上,然后开始向下滚动。在这种情况下,我希望scrollview滚动(没有tableview)。如果我一直向下滚动滚动视图并到达底部,如果我从显示屏上移开手指并将其放回tebleview上方并再次向下滚动,则我希望我的表视图现在向下滚动,因为滚动视图已到达其底部并且没有滚动能够保持滚动。

你们对如何实现此滚动有任何想法吗?

我真的迷失了。任何帮助将不胜感激:(

谢谢!


如果您的用户想要在滚动视图中向上滚动怎么办?他们是否必须一直滚动到表格视图的顶部?
张丹

没有@DanielZhang。在这种情况下,scrollview应该向上滚动,直到无法滚动为止。然后应该是tableview应该向上滚动的时候了。
Marcela Mukel Balady 2015年

我添加了一个答案,并且意识到我在表视图中向上滚动时所做的与您所描述的相反。这样对我来说似乎很自然。您能否进一步描述行为如何不同?
Daniel Zhang

Answers:


59

同时处理滚动视图和表视图的解决方案围绕UIScrollViewDelegate。因此,让您的视图控制器遵守该协议:

class ViewController: UIViewController, UIScrollViewDelegate {

我将滚动视图和表格视图表示为出口:

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView!

我们还需要跟踪滚动视图内容的高度以及屏幕高度。稍后您会看到原因。

let screenHeight = UIScreen.mainScreen().bounds.height
let scrollViewContentHeight = 1200 as CGFloat

需要一些配置viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()

    scrollView.contentSize = CGSizeMake(scrollViewContentWidth, scrollViewContentHeight)
    scrollView.delegate = self
    tableView.delegate = self 
    scrollView.bounces = false
    tableView.bounces = false
    tableView.scrollEnabled = false
}

为了使事情简单,我关闭了弹跳功能。关键设置是滚动视图和表格视图的委托,并且首先关闭表格视图滚动。

这些是必需的,以便scrollViewDidScroll:委托方法可以处理到达滚动视图的底部和到达表格视图的顶部。这是该方法:

func scrollViewDidScroll(scrollView: UIScrollView) {
    let yOffset = scrollView.contentOffset.y

    if scrollView == self.scrollView {
        if yOffset >= scrollViewContentHeight - screenHeight {
            scrollView.scrollEnabled = false
            tableView.scrollEnabled = true
        }
    }

    if scrollView == self.tableView {
        if yOffset <= 0 {
            self.scrollView.scrollEnabled = true
            self.tableView.scrollEnabled = false
        }
    }
}

委托方法正在执行的操作是检测滚动视图何时到达其底部。发生这种情况时,可以滚动表视图。它还可以检测到表格视图何时到达重新启用滚动视图的顶部。

我创建了一个GIF来演示结果:

在此处输入图片说明


7
嗨,丹尼尔,非常感谢您的回答。有用!尽管用户每次需要启用/禁用一次滚动都需要滚动两次。我的意思是,那一刻滚动不会从一个滚动转移到另一个。您是否知道如何实现这一目标?再次感谢;)
Marcela Mukel Balady 2015年

1
不客气,马塞拉。要回答您的问题,我不知道将滚动从一个滚动视图转移到另一个滚动视图的任何便捷方法。在stackoverflow.com/questions/21554327/…中有有用的相关信息。有时候,由于需要进行大量的定制工作,这样做是不值得的,因为它涉及到的工作以及在将来的OS版本中可能会中断的风险。
张丹

@DanielZhang可以在调用委托方法scrollViewDidScroll时在要同步的视图之间共享.contentOffset来传输滚动。
伊恩

@Daniel Zhang良好的工作原理和演示,但就我而言,存在一个与我在视图,滚动视图和表视图上设置的约束有关的问题。一旦我修复它,它就会在1秒钟内起作用。
拉维

@DanielZhang此代码的tableview高度是多少?
e.ozmen,

28

修改了丹尼尔(Daniel)的答案,使其更加高效且无错误。

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var tableHeight: NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()
    //Set table height to cover entire view
    //if navigation bar is not translucent, reduce navigation bar height from view height
    tableHeight.constant = self.view.frame.height-64
    self.tableView.isScrollEnabled = false
    //no need to write following if checked in storyboard
    self.scrollView.bounces = false
    self.tableView.bounces = true
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 20
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 30))
    label.text = "Section 1"
    label.textAlignment = .center
    label.backgroundColor = .yellow
    return label
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    cell.textLabel?.text = "Row: \(indexPath.row+1)"
    return cell
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView == self.scrollView {
        tableView.isScrollEnabled = (self.scrollView.contentOffset.y >= 200)
    }

    if scrollView == self.tableView {
        self.tableView.isScrollEnabled = (tableView.contentOffset.y > 0)
    }
}

完整的项目可以在这里看到:https : //gitlab.com/vineetks/TableScroll.git


@Vinit Kumar Singh很好,它解决了这个问题。非常感谢
Vinayak Bhor

@Vineet Kumar如果我没有导航栏,那怎么办?在您的代码中,如果我们删除导航栏,它将仅滚动到第11行
Bhanuteja

1
导航栏的默认高度为64。尝试在viewDidLoad()方法中将其减小为零。
Vineet Kumar Singh,

这是有效的解决方案。@Marcela请接受这个答案
Naveed Ahmad

有效的解决方案,为我工作。
Pritesh

4

经过多次试验和错误,这对我来说是最有效的。该解决方案必须解决两个需求:1)确定应使用谁的滚动属性;tableView或scrollView?2)确保tableView在到达其表/内容的顶部之前,不向scrollView授予权限。

为了查看是否应该使用scrollview进行滚动而不是使用tableview,我检查了tableview上方的UIView是否在框架内。如果UIView在框架内,可以肯定地说scrollView应该有权滚动。如果UIView不在框架内,则表示tableView占据了整个窗口,因此应该有权滚动。

func scrollViewDidScroll(_ scrollView: UIScrollView) {

    if scrollView.bounds.intersects(UIView.frame) == true {
     //the UIView is within frame, use the UIScrollView's scrolling.   

        if tableView.contentOffset.y == 0 {
            //tableViews content is at the top of the tableView.

        tableView.isUserInteractionEnabled = false
       tableView.resignFirstResponder()
        print("using scrollView scroll")

        } else {

            //UIView is in frame, but the tableView still has more content to scroll before resigning its scrolling over to ScrollView.

            tableView.isUserInteractionEnabled = true
            scrollView.resignFirstResponder()
            print("using tableView scroll")
        }

    } else {

        //UIView is not in frame. Use tableViews scroll.

        tableView.isUserInteractionEnabled = true
       scrollView.resignFirstResponder()
        print("using tableView scroll")

    }

}

希望这对某人有帮助!


UIView.frame应该是什么?
安迪·奥布塞克

@AndyObusek任何紧靠您的表格视图的UIView。
Felecia Genet

您能否上传一个与此相关的项目,因为很多人都在努力查看其工作原理。我已经在下面的Vineet演示项目中实现了您的解决方案(因为可以随时对其进行测试),但是内部的tableview被阻止了,并且滚动没有被转移。
帕万

2
您的解决方案不会同时传输滚动条。您必须点击并滚动两次,才能从滚动视图滚动到表格视图滚动。
帕万

3

我也在这个问题上挣扎。有一个非常简单的解决方案。

在界面生成器中:

  1. 创建简单的ViewController
  2. 添加一个简单的View,它将成为我们的标题,并将其约束为superview
    • 这是下面示例中的红色视图
    • 我从顶部,左侧和右侧添加了12px,并将固定高度设置为128px
  3. 嵌入一​​个PageViewController,确保它被限制在superview而不是header中

ib

现在,这是有趣的部分:对于您添加的每个页面,请确保其tableView的顶部偏移。而已。例如,可以使用以下代码进行操作(假设您使用UITableViewController作为页面):

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let tables = viewControllers.compactMap { $0 as? UITableViewController }
    tables.forEach {
        $0.tableView.contentInset = UIEdgeInsets(top: headerView.bounds.height, left: 0, bottom: 0, right: 0)
        $0.tableView.contentOffset = CGPoint(x: 0, y: -headerView.bounds.height)
    }
}

表格视图内的滚动内部没有混乱的滚动,没有委托的混乱,没有重复的滚动,完全自然的行为。如果看不到标题,则可能是因为tableView背景色。您必须将其设置为clear,以使标题在tableView下可见。


使用此解决方案无法与标题视图进行用户交互,对吗?
Knut Valen

我无法完成这项工作。该表的节标题在contentInset / offset上方不可见,并且标题中的按钮不可单击。滚动是平滑的,但感觉很好。
Knut Valen

2

这里没有一个答案对我来说是完美的。每个人都有它自己的细微问题(需要在一个滚动视图到达底部时重复滑动,或者滚动指示器看起来不正确,等等),所以我想着要给出另一个答案。

Ole Begemann撰写了一篇很好的文章,准确地做到了这一点https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/

尽管是老文章,但这些概念仍然适用于当前的API。此外,他的方法还有一个维护(兼容Xcode 9)的Objective-C实现https://github.com/eyeem/OLEContainerScrollView



1

我认为有两种选择。

由于您知道滚动视图和主视图的大小,因此无法确定滚动视图是否触底。

if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
    // reach bottom
}

所以当它击中时 你基本上设置

[contentScrollView setScrollEnabled:NO];

和其他方法为您的tableView。

我认为更准确的另一件事是将Gesture添加到您的视图中。

UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(respondToTapGesture:)];

// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;

// Add the tap gesture recognizer to the view
[self.view addGestureRecognizer:tapRecognizer];

// Do any additional setup after loading the view, typically from a nib

所以,当你添加手势,你可以简单地控制通过改变活动视图setScrollEnabledrespondToTapGesture


1

我发现了一个很棒的库 MXParallaxHeader

在情节提要中,只需将UIScrollView课程设置为,MXScrollView就会发生魔术。

UIScrollView嵌入UIPageViewController容器视图时,我使用了此类来处理我的事情。即使您可以插入视差标题视图以获取更多详细信息。

此外,该库提供CocoapodsCarthage

我在下面附加了代表UIView层次结构的图像。 MXScrollView层次结构


0

我尝试了标记为正确答案的解决方案,但无法正常工作。用户需要在表格视图上单击两次以进行滚动,然后我无法再次滚动整个屏幕。所以我只是在viewDidLoad()中应用了以下代码:

tableView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(tableViewSwiped)))
scrollView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(scrollViewSwiped)))

下面的代码是操作的实现:

func tableViewSwiped(){
    scrollView.isScrollEnabled = false
    tableView.isScrollEnabled = true
}

func scrollViewSwiped(){
    scrollView.isScrollEnabled = true
    tableView.isScrollEnabled = false
}

0

也许是蛮力的,但效果很好:顺便说一句,我使用自动布局。

对于tableView(或collectionView或其他),在情节提要中设置任意高度,并为类提供一个出口。(viewDidLoad()或...)在适当的地方将tableView的高度设置得足够大,以使tableView不需要滚动。(需要提前知道行数)然后只有外部scrollView可以很好地滚动。


不适

0

一个简单的技巧,如果要实现它,就是用普通的容器视图替换父滚动视图。在容器视图上添加平移手势,您可以在第一个视图的顶部约束下玩以分配负值。您可以检查页面视图的来源,如果它达到顶部,则可以开始在页面视图的子视图的内容偏移量上分配该值。直到用户在容器视图中处于最高级视图的状态下获得表视图之前,您可以禁用页面tableView的滚动,并可以通过设置内容偏移来手动滚动。因此,最初页面浏览高度的底部将被折叠(或说在屏幕外)或更小。稍后向下滚动时,它将扩展以占用更多空间。

如果在导航栏或容器视图之外的其他视图中显示帧超限,则手势将自动停止响应。

手势是许多应用程序中使用的用户交互过渡的关键。您可以使用它来模拟滚动一定时间。


0

在我的情况下,我对高度使用约束是这样的:

self.heightTableViewConstraint.constant = self.tableView.contentSize.height
self.scrollView.contentInset.bottom = self.tableView.contentSize.height

-1
CGFloat tableHeight = 0.0f;

YourArray =[response valueForKey:@"result"];

tableHeight = 0.0f;
for (int i = 0; i < [YourArray count]; i ++) {
    tableHeight += [self tableView:self.aTableviewDoc heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
}

self.aTableviewDoc.frame = CGRectMake(self.aTableviewDoc.frame.origin.x, self.aTableviewDoc.frame.origin.y, self.aTableviewDoc.frame.size.width, tableHeight);

你好 该问题被标记为“快速”。请根据标签写出答案。谢谢!:)
Eric Aya

1
您还应该在代码中添加一些说明。此代码段如何解决OP的问题?
卡·安吉莱蒂
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.