如何使ScrollView中的TableView滚动自然地表现

时间:2015-10-22 23:23:21

标签: swift uitableview uiscrollview uikit uipageviewcontroller

我需要做这个有奇怪配置的应用程序。

如下图所示,主视图是UIScrollView。然后在里面它应该有一个UIPageView,并且PageView的每个页面都应该有一个UITableView。

figure

到目前为止,我已完成了所有这些工作。但我的问题是我希望滚动到表现 自然

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

例如,让我们说我的滚动视图目前滚动到顶部。然后我将手指放在桌面视图上(显示当前页面)并开始滚动向下。在这种情况下,我希望滚动视图滚动(没有tableview)。如果我继续向下滚动我的滚动视图并且它到达底部,如果我从显示器上移开我的手指并将其放回tebleview并再次向下滚动,我希望我的tableview现在向下滚动因为滚动视图到达底部并且它&# 39;无法继续滚动。

你们有没有想过如何实现这种滚动?

我真的迷失了。任何帮助都会非常感激:(

谢谢!

13 个答案:

答案 0 :(得分:47)

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

class ViewController: UIViewController, UIScrollViewDelegate {

我将滚动视图和表视图表示为outlet:

@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来演示结果:

enter image description here

答案 1 :(得分:17)

修改Daniel的答案,使其更有效率和无bug。

@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

答案 2 :(得分:4)

经过多次试验和错误后,这对我来说最有效。解决方案必须解决两个需求1)确定应该使用谁的滚动属性; tableView还是scrollView? 2)确保tableView没有授予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")

    }

}

希望这有助于某人!

答案 3 :(得分: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

因此,当您添加手势时,您只需更改setScrollEnabled中的respondToTapGesture即可控制有效视图。

答案 4 :(得分:1)

这里的答案都不适合我。每个人都有自己的细微问题(当一个滚动视图击中它的底部时需要重复滑动,或滚动指示器看起来不正确等等),所以我想再投入一个答案。

Ole Begemann非常清楚地写了https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/

尽管这是一个旧帖子,但这些概念仍然适用于当前的API。此外,他的方法https://github.com/eyeem/OLEContainerScrollView

还保持了(Xcode 9兼容)Objective-C实现

答案 5 :(得分:1)

我也在努力解决这个问题。有一个非常简单的解决方案。

在界面构建器中:

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

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下可见。

答案 6 :(得分:0)

我发现了一个很棒的库 MXParallaxHeader

在故事板中,只需将UIScrollView课程设为MXScrollView,就会发生魔法。

当我嵌入UIScrollView容器视图时,我使用此类来处理UIPageViewController。即使您可以插入视差标题视图以获取更多细节。

此外,此库提供CocoapodsCarthage

我附上了一张代表UIView层次结构的图片。 MXScrollView Hierarchy

答案 7 :(得分:0)

也许是蛮力的,但是运行得很好:顺便说一下,我使用自动布局。

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

答案 8 :(得分:0)

如果您面临嵌套滚动问题,请使用以下最简单的解决方案。

  1. 转到设计屏幕
  2. 选择滚动视图,然后禁用滚动跳动
  3. 如果您的视图在滚动视图内使用表格视图,则也要在表格视图滚动时禁用跳动
  4. 运行并检查问题是否解决

check how to disable bounce on scroll of a scroll view

check how to disable bounce on scroll of a tableview view

答案 9 :(得分:0)

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

如果导航栏或容器视图之外的其他视图中显示的帧数过多,Gesture将自动停止响应。

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

答案 10 :(得分:0)

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

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

答案 11 :(得分:0)

SWIFT 5

当由于各种不同的屏幕尺寸而无法保证scrollView内容偏移量(Y)时,我在使用Vineet的答案时遇到了一些麻烦。为了解决这个问题,我更改了启用tableView滚动时的第一个触发事件。

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView.bounds.contains(button.frame) {
            tableView.isScrollEnabled = true
        }

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

scrollView.bounds.contains将检查给定元素的框架是否在scrollView的可见内容内为FULLY。我将其设置为tableView下面的按钮。如果您唯一的条件是tableView完全可见,则可以将其设置为tableVIew的框架。

我保留了何时禁用tableView滚动的原始实现,并且效果很好。

答案 12 :(得分:-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);