【问题标题】:Move a view when scrolling in UITableView在 UITableView 中滚动时移动视图
【发布时间】:2014-10-12 00:04:27
【问题描述】:

我有一个UIView,下面有一个UITableView

我想要做的是让UITableView 上方的视图在用户开始滚动表格时向上移动(不碍事),以便为UITableView 留出更多空间(然后向下移动)当您再次向下滚动时)。

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

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

【问题讨论】:

  • 我只是想问一下,这一切都在一个 UIViewController 类下吗?
  • 整个场景在UIViewController 中,顶部有一个常规视图,底部有一个容器视图。然后将带有我的UITableViewController 的“选项卡”嵌入到容器视图中。

标签: ios cocoa-touch uitableview uiview uiscrollview


【解决方案1】:

你可以这样:

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

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

你的viewDidLoad()

tableView.delegate = self

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

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

    }
}

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

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

【讨论】:

    【解决方案2】:

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

    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
    }
    

    【讨论】:

    • 我在哪里可以得到 topConstraint 变量?
    • @Kakumanusivakrishna,您可以在界面构建器中创建并为其创建 IBOutlet
    • 做了同样的事情。谢谢
    • 很好的解决方案,但是一个非常小的细节会使滚动更加流畅。 //we expand the top view 的代码在哪里,不要检查 delta &lt; 0scrollView.contentOffset.y &lt; 0,而是将两者都更改为 &lt;= 0
    • topConstraint 和 searchHeaderTopConstraint 是同一元素的顶级约束?
    【解决方案3】:

    要创建像这样的动画,

    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
    }
    

    您必须使用您的 collectionView/tableView

    更改 pageMenu.view

    【讨论】:

    • 什么是 CAPSPageMenu?
    【解决方案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
        }
    

    【讨论】:

    • 高速滚动时设计中断
    • @Dvole yourConstraint 绑定到哪个约束?
    【解决方案5】:

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

    相反,我们可以使用 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;
    }
    

    这里lastContentOffset是定义为属性的CGFloat

    视图层次结构如下: ViewController --> View 包含 ScrollView(其委托方法在上面定义)--> 包含 TableView。

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

    记得禁用TableView的滚动。

    【讨论】:

      【解决方案6】:

      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
       }
      

      【讨论】:

      • 为什么let topConstraintRange = (CGFloat(120)..&lt;CGFloat(300))从120变成300?不应该是 64 到 300,因为顶视图从导航栏的正下方开始,即 64?
      • 这个答案是天才。我不得不为 Swift 3 做一些改变,但效果很好
      • 我们从哪里得到topConstraint
      • 当我们快速滚动时它会弄乱视图!这个问题有什么解决办法吗??
      • @george 滚动到顶部时很好,但向下滚动时不像滚动顶部那样平滑。我们可以像顶部一样平滑滚动吗?
      【解决方案7】:

      更简单快速的方法

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

      【讨论】:

      • 它比NobodyBada 的答案更简单、更快捷。您不需要添加和维护以前的偏移量。
      【解决方案8】:

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

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

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

      scrollUserInfo 是一个NSDictionary,我将UITableView 与通知一起传递(我在viewDidLoad 中执行此操作,因此我只需执行一次):

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

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

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

      最后我有了方法:

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

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

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

      【讨论】:

      • viewHeight 从何而来?
      • viewHeight是问题所示图片中table view上方view的高度。
      【解决方案9】:

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

      在您的表格视图的委托中:

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

      【讨论】:

      • @NobleK 您介意与所有检查人员共享代码吗?
      • 我已添加我的解决方案作为答案。
      • 如果这3个tableView的行数不一样怎么办?在表格移动到顶部并将选项卡切换到仅包含几行的表格视图之后,一些非常长的列表和一些只有几行。你是怎么处理的?
      猜你喜欢
      • 1970-01-01
      • 2021-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-03
      • 2020-01-07
      相关资源
      最近更新 更多