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

问题描述 投票:55回答:10

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

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

figure

到目前为止,我已经完成了这一切。但我的问题是我希望滚动行为自然。

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

例如,假设我的滚动视图当前滚动到顶部。然后我把手指放在桌面视图(显示的当前页面)上并开始向下滚动。在这种情况下,我希望滚动视图滚动(没有tableview)。如果我继续向下滚动我的滚动视图并且它到达底部,如果我从显示器上移开手指并将其放回tebleview并再次向下滚动,我希望我的tableview现在向下滚动,因为滚动视图到达底部并且它不是能够继续滚动。

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

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

谢谢!

swift uitableview uiscrollview uikit uipageviewcontroller
10个回答
49
投票

同时处理滚动视图和表视图的解决方案围绕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来演示结果:

enter image description here


-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);

18
投票

修改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


5
投票

经过多次试验和错误,这对我来说最有效。解决方案必须解决两个需求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")

    }

}

希望这有助于某人!


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

因此,当您添加手势时,您可以通过更改setScrollEnabled中的respondToTapGesture来简单地控制活动视图。


1
投票

我找到了一个很棒的图书馆MXParallaxHeader

在故事板中,只需将UIScrollView类设置为MXScrollView然后魔术就会发生。

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

此外,这个库提供CocoapodsCarthage

我附上了一张代表UIViewHierarchy的图片。 MXScrollView Hierarchy


1
投票

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

在界面构建器中:

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

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背景颜色。您必须将其设置为清除,以便从tableView下可以看到标题。


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或其他),在storyboard中设置任意高度,并为类创建一个出口。在适当的情况下,(viewDidLoad()或...)将tableView的高度设置得足够大,以便tableView不需要滚动。 (需要提前知道行数)然后只有外部scrollView才能很好地滚动。

© www.soinside.com 2019 - 2024. All rights reserved.