ios 在 UITableView 中滚动时移动视图

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25374090/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 01:55:05  来源:igfitidea点击:

Move a view when scrolling in UITableView

ioscocoa-touchuitableviewuiviewuiscrollview

提问by pajevic

I have a UIViewwith a UITableViewbelow it:

我有一个UIViewUITableView它下面:

enter image description here

在此处输入图片说明

What I would like to do is to have the view above the UITableViewmove up (out of the way) when the user starts scrolling in the table in order to have more space for the UITableView(and come down when you scroll down again).

我想要做的是UITableView当用户开始在表格中滚动时让上面的视图向上移动(移开),以便有更多的空间UITableView(并在您再次向下滚动时向下滚动)。

I know that this is normally done with a table header view, but my problem is that my table view is inside a tab (actually it is a side-scrolling page view implemented using TTSliddingPageviewcontroller). So while I only have one top UIViewthere are three UITableViews.

我知道这通常是通过表头视图完成的,但我的问题是我的表视图位于选项卡内(实际上它是使用 实现的横向滚动页面视图TTSliddingPageviewcontroller)。所以虽然我只有一个顶部,UIView但有三个UITableViews。

Is it possible to accomplish this manually? My first thought is to put everything in a UIScrollView, but according to Apple's documentation one should never place a UITableViewinside a UIScrollViewas this leads to unpredictable behavior.

是否可以手动完成此操作?我的第一个想法是将所有内容都放在 a 中UIScrollView,但根据 Apple 的文档,永远不应将 aUITableView放入 a 中,UIScrollView因为这会导致不可预测的行为。

采纳答案by NobodyNada - Reinstate Monica

Since UITableViewis a subclass of UIScrollView, your table view's delegate can receive UIScrollViewDelegatemethods.

由于UITableView是 的子类UIScrollView,您的表视图的委托可以接收UIScrollViewDelegate方法。

In your table view's delegate:

在您的表视图的委托中:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    static CGFloat previousOffset;
    CGRect rect = self.view.frame;
    rect.origin.y += previousOffset - scrollView.contentOffset.y;
    previousOffset = scrollView.contentOffset.y;
    self.view.frame = rect;
}

回答by George Muntean

Solution for Swift (Works perfectly with bounce enabled for scroll view):

Swift 的解决方案(与为滚动视图启用反弹完美配合):

 var oldContentOffset = CGPointZero
 let topConstraintRange = (CGFloat(120)..<CGFloat(300))

 func scrollViewDidScroll(scrollView: UIScrollView) {

    let delta =  scrollView.contentOffset.y - oldContentOffset.y

    //we compress the top view
    if delta > 0 && topConstraint.constant > topConstraintRange.start && scrollView.contentOffset.y > 0 {
        topConstraint.constant -= delta
        scrollView.contentOffset.y -= delta
    }

    //we expand the top view
    if delta < 0 && topConstraint.constant < topConstraintRange.end && scrollView.contentOffset.y < 0{
        topConstraint.constant -= delta
        scrollView.contentOffset.y -= delta
    }

    oldContentOffset = scrollView.contentOffset
 }

回答by Dvole

Swift 3 & 4:

斯威夫特 3 和 4:

    var oldContentOffset = CGPoint.zero
    let topConstraintRange = (CGFloat(0)..<CGFloat(140))

    func scrollViewDidScroll(_ scrollView: UIScrollView) {

        let delta =  scrollView.contentOffset.y - oldContentOffset.y

        //we compress the top view
        if delta > 0 && yourConstraint.constant > topConstraintRange.lowerBound && scrollView.contentOffset.y > 0 {
            yourConstraint.constant -= delta
            scrollView.contentOffset.y -= delta
        }

        //we expand the top view
        if delta < 0 && yourConstraint.constant < topConstraintRange.upperBound && scrollView.contentOffset.y < 0{
            yourConstraint.constant -= delta
            scrollView.contentOffset.y -= delta
        }
        oldContentOffset = scrollView.contentOffset
    }

回答by Ghulam Rasool Hashmi

More simple and fast approach

更简单快捷的方法

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 
{

    CGRect rect = self.view.frame;
    rect.origin.y =  -scrollView.contentOffset.y;
    self.view.frame = rect;

}

回答by Exception

I added some constraints to the last solution to prevent some strange behaviours in case of fast scrolling

我在最后一个解决方案中添加了一些约束,以防止在快速滚动的情况下出现一些奇怪的行为

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let delta =  scrollView.contentOffset.y - oldContentOffset.y

    //we compress the top view
    if delta > 0 && topConstraint.constant > topConstraintRange.lowerBound && scrollView.contentOffset.y > 0 {
        searchHeaderTopConstraint.constant = max(topConstraintRange.lowerBound, topConstraint.constant - delta)
        scrollView.contentOffset.y -= delta
    }

    //we expand the top view
    if delta < 0 && topConstraint.constant < topConstraintRange.upperBound && scrollView.contentOffset.y < 0 {
        topConstraint.constant = min(searchHeaderTopConstraint.constant - delta, topConstraintRange.upperBound)
        scrollView.contentOffset.y -= delta
    }
    oldContentOffset = scrollView.contentOffset
}

回答by pajevic

Someone asked for the code for my solution so I am posting it here as an answer. The credit for the idea should still go to NobodyNada.

有人要求提供我的解决方案的代码,所以我将其发布在这里作为答案。这个想法的功劳应该归功于NobodyNada。

In my UITableViewControllerI implement this delegate method:

在我的UITableViewController我实现这个委托方法:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"TableViewScrolled" object:nil userInfo:scrollUserInfo];
}

scrollUserInfois a NSDictionarywhere I put my UITableViewto pass it with the notification (I do this in viewDidLoadso I only have to do it once):

scrollUserInfoNSDictionary我把UITableView它和通知一起传递的地方(我这样做,viewDidLoad所以我只需要做一次):

scrollUserInfo = [NSDictionary dictionaryWithObject:self.tableView forKey:@"scrollView"];

Now, in the view controller that has the view I want to move off screen while scrolling I do this in viewDidLoad:

现在,在具有我想在滚动时移出屏幕的视图的视图控制器中,我执行以下操作viewDidLoad

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleScroll:) name:@"TableViewScrolled" object:nil];

And finally I have the method:

最后我有方法:

- (void)handleScroll:(NSNotification *)notification {
    UIScrollView *scrollView = [notification.userInfo valueForKey:@"scrollView"];
    CGFloat currentOffset = scrollView.contentOffset.y;
    CGFloat height = scrollView.frame.size.height;
    CGFloat distanceFromBottom = scrollView.contentSize.height - currentOffset;

    if (previousOffset < currentOffset && distanceFromBottom > height) {
        if (currentOffset > viewHeight)
            currentOffset = viewHeight;
        self.topVerticalConstraint.constant += previousOffset - currentOffset;
        previousOffset = currentOffset;
    }
    else {
        if (previousOffset > currentOffset) {
            if (currentOffset < 0)
                currentOffset = 0;
            self.topVerticalConstraint.constant += previousOffset - currentOffset;
            previousOffset = currentOffset;
        }
    }
}

previousOffsetis an instance variable CGFloat previousOffset;. topVerticalConstraintis a NSLayoutConstraintthat is set as a IBOutlet. It goes from the top of the view to the top of its superview and the initial value is 0.

previousOffset是一个实例变量CGFloat previousOffset;topVerticalConstraint是 aNSLayoutConstraint设置为 a IBOutlet。它从视图的顶部到其父视图的顶部,初始值为 0。

It's not perfect. For instance, if the user scrolls very aggressively up the movement of the view can get a bit jerky. The issue is worse for large views; if the view is small enough there is no problem.

这并不完美。例如,如果用户非常积极地向上滚动,则视图的移动可能会有点生涩。对于大视图,这个问题更糟;如果视图足够小,则没有问题。

回答by Yassine En

To create like this animation,

要创建这样的动画,

enter image description here

在此处输入图片说明

lazy var redView: UIView = {

    let view = UIView(frame: CGRect(x: 0, y: 0, width: 
    self.view.frame.width, height: 100))
    view.backgroundColor = .red
    return view
}()

var pageMenu: CAPSPageMenu?

override func viewDidLoad() {
     super.viewDidLoad()
     self.view.addSubview(redView)

    let rect = CGRect(x: 0, y: self.redView.frame.maxY, width: self.view.bounds.size.width, height:(self.view.bounds.size.height - (self.redView.frame.maxY)))
    pageMenu?.view.frame = rect
    self.view.addSubview(pageMenu!.view)

  }

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
  let offset = scrollView.contentOffset.y

    if(offset > 100){
        self.redView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 0)
    }else{
        self.redView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 100 - offset)
    }

    let rect = CGRect(x: 0, y: self.redView.frame.maxY, width: self.view.bounds.size.width, height:(self.view.bounds.size.height - (self.redView.frame.maxY)))
    pageMenu?.view.frame = rect
}

you must change pageMenu.viewwith your collectionView/tableView

您必须使用collectionView/tableView更改pageMenu.view

回答by Rodrigo

You can make it like this:

你可以这样做:

Add these as your class variables:

将这些添加为您的类变量:

private let NUMBER_OF_ROWS: Int = 256
private let ROW_HEIGHT: CGFloat = 75.0
private let MINIMUM_CONSTANT_VALUE: CGFloat = -150.0 /// This is the hidable view's height

@IBOutlet weak var hidableViewTopConstraint: NSLayoutConstraint! /// This is an outlet from your storyboard
private var lastContentOffset: CGFloat = 0.0

This goes on your viewDidLoad():

这继续你的viewDidLoad()

tableView.delegate = self

And then you add this as an extension for your view controller:

然后将其添加为视图控制器的扩展:

extension ViewController: UIScrollViewDelegate {

    func scrollViewDidScroll(_ scrollView: UIScrollView) {

        let delta = tableView.contentOffset.y - lastContentOffset

        let canScrollUp: Bool =
            delta < 0 &&
              hidableViewTopConstraint.constant < 0 &&
                scrollView.contentOffset.y < 0



        let canScrollDown: Bool =
            delta > 0 &&
              hidableViewTopConstraint.constant > MINIMUM_CONSTANT_VALUE &&
                tableView.contentOffset.y > 0


        if canScrollUp || canScrollDown{

            hidableViewTopConstraint.constant -= delta
            tableView.contentOffset.y -= delta

        }

        lastContentOffset = tableView.contentOffset.y

    }
}

But note that the scroll mechanism will only work when the TableView is scrolled. The top view wont collapse or expand if you scroll it.

但请注意,滚动机制仅在 TableView 滚动时才起作用。如果滚动它,顶视图不会折叠或展开。

I made an example project you can check, it is called iOSFixedHeaderList

我做了一个你可以检查的示例项目,它叫做iOSFixedHeaderList

回答by Rishabh Sanghvi

I know this post in very old. I tried above solutions but neither worked for me for tried my own, hopefully it can help you. This scenario is pretty common, as apple suggested not to use TableViewController inside any ScrollView because the compiler will confused as in whom to respond becuase it will be getting two delegate call back - one from ScrollViewDelegate and another from UITableViewDelegate.

我知道这个帖子很老了。我尝试了上述解决方案,但对我自己的尝试都不奏效,希望它可以帮助您。这种情况很常见,因为苹果建议不要在任何 ScrollView 中使用 TableViewController,因为编译器会混淆响应谁,因为它将获得两个委托回调 - 一个来自 ScrollViewDelegate,另一个来自 UITableViewDelegate。

Instead we can use ScrollViewDelegate and disable the UITableViewScrolling.

相反,我们可以使用 ScrollViewDelegate 并禁用 UITableViewScrolling。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGFloat currentOffSetY = scrollView.contentOffset.y;
    CGFloat diffOffset = self.lastContentOffset - currentOffSetY;

    self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 400 + [self.tableView contentSize].height);

    if (self.lastContentOffset < scrollView.contentOffset.y) {
        tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y , tableView.frame.size.width,  tableView.size.height - diffOffset);
    }

    if (self.lastContentOffset > scrollView.contentOffset.y) {
        tableView.frame = CGRectMake(tableView.frame.origin.x, tableViewframe.origin.y, tableViewframe.size.width,  tableView.frame.size.height + diffOffset);
    }

    self.lastContentOffset = currentOffSetY;
}

Here lastContentOffset is CGFloat defined as property

这里 lastContentOffset 是 CGFloat 定义为属性

The View Heirarchy is as follows: ViewController --> View contains ScrollView (whose delegate method is defined above) --> Contain TableView.

View Heirarchy如下:ViewController --> View包含ScrollView(其委托方法在上面定义)-->包含TableView。

By the above code we are manually increasing and decreasing the height of the table view along with the content size of ScrollView.

通过上面的代码,我们手动增加和减少了表格视图的高度以及 ScrollView 的内容大小。

Remember to disable the Scrolling of TableView.

记得禁用 TableView 的滚动。