How to Make the scroll of a TableView inside ScrollView behave naturally

我们两清 提交于 2019-11-26 23:49:51

问题


I need to do this app that has a weird configuration.

As shown in the next image, the main view is a UIScrollView. Then inside it should have a UIPageView, and each page of the PageView should have a UITableView.

I've done all this so far. But my problem is that I want the scrolling to behave naturally.

The next is what I mean naturally. Currently when I scroll on one of the UITableViews, it scrolls the tableview (not the scrollview). But I want it to scroll the ScrollView unless the scrollview cannot scroll cause it got to its top or bottom (In that case I'd like it to scroll the tableview).

For example, let's say my scrollview is currently scrolled to the top. Then I put my finger over the tableview (of the current page being shown) and start scrolling down. I this case, I want the scrollview to scroll (no the tableview). If I keep scrolling down my scrollview and it reaches the bottom, if I remove my finger from the display and put it back over the tebleview and scroll down again, I want my tableview to scroll down now because the scrollview reached its bottom and it's not able to keep scrolling.

Do you guys have any idea about how to implement this scrolling?

I'm REALLY lost with this. Any help will be greatly appreciate it :(

Thanks!


回答1:


The solution to simultaneously handling the scroll view and the table view revolves around the UIScrollViewDelegate. Therefore, have your view controller conform to that protocol:

class ViewController: UIViewController, UIScrollViewDelegate {

I’ll represent the scroll view and table view as outlets:

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

We’ll also need to track the height of the scroll view content as well as the screen height. You’ll see why later.

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

A little configuration is needed in 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
}

where I’ve turned off bouncing to keep things simple. The key settings are the delegates for the scroll view and the table view and having the table view scrolling being turned off at first.

These are necessary so that the scrollViewDidScroll: delegate method can handle reaching the bottom of the scroll view and reaching the top of the table view. Here is that method:

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
        }
    }
}

What the delegate method is doing is detecting when the scroll view has reached its bottom. When that has happened the table view can be scrolled. It is also detecting when the table view reaches the top where the scroll view is re-enabled.

I created a GIF to demonstrate the results:




回答2:


Modified Daniel's answer to make it more efficient and bug free.

@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)
    }
}

Complete project can be seen here: https://gitlab.com/vineetks/TableScroll.git




回答3:


After many trials and errors, this is what worked best for me. The solution has to solve two needs 1) determine who's scrolling property should be used; tableView or scrollView? 2) make sure that the tableView doesn't give authority to the scrollView until it has reached the top of it's table/content.

In order to see if the scrollview should be used for scrolling vs the tableview, i checked to see if the UIView right above my tableview was within frame. If the UIView is within frame, it's safe to say the scrollView should have authority to scroll. If the UIView is not within frame, that means that the tableView is taking up the entire window, and therefor should have authority to scroll.

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

    }

}

hope this helps someone!




回答4:


None of the answers here worked perfectly for me. Each one had it's owned nuanced problem (needing to do a repeated swipe when one scrollview hit it's bottom, or the scroll indicator not looking correct, etc), so figured I'd throw in another answer.

Ole Begemann has a great write up on doing this exactly https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/

Despite being an old post, the concepts still apply to the current APIs. Additionally, there is a maintained (Xcode 9 compatible) Objective-C implementation of his approach https://github.com/eyeem/OLEContainerScrollView




回答5:


I think there are two options.

Since you know the size of the scroll view and the main view, you are unable to tell whether the scroll view hit the bottom or not.

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

So when it hit; you basically set

[contentScrollView setScrollEnabled:NO];

and other way around for your tableView.

The other thing, which is more precise I think, is to add Gesture to your views.

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

So when you add Gesture, you can simply control the active view by changing setScrollEnabled in the respondToTapGesture.




回答6:


I found an awesome library MXParallaxHeader

In Storyboard just set UIScrollView class to MXScrollView then magic happens.

I used this class to handle my UIScrollView when I embed a UIPageViewController container view. even you can insert a parallax header view for more detail.

Also, this library provides Cocoapods and Carthage

I attached an image below which represent UIViewHierarchy. MXScrollView Hierarchy




回答7:


I was struggling with this problem, too. There is a very simple solution.

In interface builder:

  1. create simple ViewController
  2. add a simple View, it will be our header, and constrain it to superview
    • it's the red view on the example below
    • I have added 12px from top, left and right, and set fixed height to 128px
  3. embed a PageViewController, making sure it is constrained to the superview, and not the header

Now, here comes the fun part: for each page you add, make sure its tableView has an offset from top. Thats it. You can do if with this code, for example (assuming you use UITableViewController as a page):

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

No messy scroll inside scroll inside table view, no mangling with delegates, no duplicated scrolls, perfectly natural behavior. If you can't see the header, it is probably because of the tableView background color. You have to set it to clear, for the header to be visible from under the tableView.




回答8:


I tried the solution marked as the correct answer, but it was not working properly. The user need to click two times on the table view for scroll and after that I was not able to scroll the entire screen again. So I just applied the following code in viewDidLoad():

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

And the code below is the implementation of the actions:

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

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



回答9:


Maybe brute-force, but working perfectly: by the way, I use auto layout.

for the tableView (or collectionView or whatever), set an arbitrary height in storyboard, and make an outlet to class. Wherever appropriate, (viewDidLoad() or...) set the tableView's height big enough so that tableView doesn't need to scroll. (need to know the number of rows in advance) Then only the outer scrollView will scroll nicely.




回答10:


If you are facing problem with the nested scrolling issue , here tis the simplest solution for it .

  1. go to your design screen
  2. select your scroll view and then disable bounce on scroll
  3. if your view uses table view inside scroll view then disable bounce on scroll of the table view as well
  4. run and check it is solved

check how to disable bounce on scroll of a scroll view

check how to disable bounce on scroll of a tableview view




回答11:


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


来源:https://stackoverflow.com/questions/33292427/how-to-make-the-scroll-of-a-tableview-inside-scrollview-behave-naturally

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!