【问题标题】:How to Make the scroll of a TableView inside ScrollView behave naturally如何使 ScrollView 中的 TableView 的滚动行为自然
【发布时间】:2016-01-22 09:43:06
【问题描述】:

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

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

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

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

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

你们知道如何实现这种滚动吗?

我真的很迷茫。任何帮助将不胜感激:(

谢谢!

【问题讨论】:

  • 如果您的用户想要在滚动视图中向上滚动会发生什么?他们必须一直滚动到表格视图的顶部吗?
  • 没有@DanielZhang。在这种情况下,滚动视图应该向上滚动,直到它不能再滚动。然后是时候让 tableview 向上滚动了。
  • 我添加了一个答案,我意识到我在表格视图中向上滚动时所做的与您所描述的相反。对我来说这似乎很自然。您能否进一步描述行为应该有何不同?

标签: swift uitableview uiscrollview uikit uipageviewcontroller


【解决方案1】:

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

【讨论】:

  • 嘿丹尼尔,非常感谢您的回答。有用!尽管每次启用/禁用一个滚动时,用户都需要滚动两次。我的意思是,滚动不会在那一刻从一个滚动转移到另一个滚动。您知道如何实现这一目标吗?再次感谢;)
  • 不客气,马塞拉。要回答您的问题,我不知道有任何方便的方法可以将滚动从一个滚动视图转移到另一个滚动视图。 stackoverflow.com/questions/21554327/… 中有有用的相关信息。有时不值得追求需要过度定制的东西,因为所涉及的工作以及它将在未来的操作系统版本中中断的风险。
  • @DanielZhang 转移滚动只需在调用委托方法 scrollViewDidScroll 时在要同步的视图之间共享 .contentOffset。
  • @Daniel Zhang 很好的工作和演示,但就我而言,存在与我在视图、滚动视图和表格视图上设置的约束相关的问题。一旦我修复它,它就会在 1 秒内工作。
  • @DanielZhang 这段代码的tableview高度是多少?
【解决方案2】:

修改了 Daniel 的答案,使其更高效且无错误。

@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

【讨论】:

  • @Vinit Kumar Singh 很好,它解决了这个问题。非常感谢
  • @Vineet Kumar 如果我没有导航栏,那该怎么办?在您的代码中,如果我们删除导航栏,它只会滚动到第 11 行
  • 64 是导航栏的默认高度。尝试在 viewDidLoad() 方法中将其减少为零。
  • 这是可行的解决方案。 @Marcela 请接受这个答案
  • 工作解决方案,为我工作。
【解决方案3】:

经过多次尝试和错误,这对我来说是最有效的。该解决方案必须解决两个需求 1)确定应该使用谁的滚动属性;表视图或滚动视图? 2) 确保 tableView 在到达其表格/内容的顶部之前不会授予 scrollView 权限。

为了查看滚动视图是否应该用于滚动与表格视图,我检查了我的表格视图正上方的 UIView 是否在框架内。如果 UIView 在框架内,则可以肯定地说 scrollView 应该有权滚动。如果 UIView 不在 frame 内,则表示 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")

    }

}

希望这对某人有所帮助!

【讨论】:

  • UIView.frame 应该是什么?
  • @AndyObusek 靠近您的 tableview 上方的任何 UIView。
  • 你能否上传一个这个项目,因为很多人都在努力了解它是如何工作的。我已经在下面的 Vineet 的演示项目中实现了您的解决方案(因为可以随时进行测试),但是里面的 tableview 被阻止了,并且滚动没有被转移。
  • 您的解决方案不会同时传输卷轴。您必须点击并滚动两次才能从滚动视图滚动到表格视图滚动。
【解决方案4】:

这里没有一个答案对我来说是完美的。每个人都有自己的细微问题(当一个滚动视图到达底部时需要重复滑动,或者滚动指示器看起来不正确等),所以我想我会提出另一个答案。

Ole Begemann 写了一篇关于这样做的精彩文章 https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/

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

有一个维护的(Xcode 9 兼容)Objective-C 实现

【讨论】:

    【解决方案5】:

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

    在界面生成器中:

    1. 创建简单的 ViewController
    2. 添加一个简单的View,它将作为我们的标题,并将其约束到superview
      • 这是下面示例中的红色视图
      • 我已经从上、左、右添加了12px,并将固定高度设置为128px
    3. 嵌入一个PageViewController,确保它被限制在superview,而不是header

    现在,有趣的部分来了:对于您添加的每个页面,确保其 tableView 与顶部有一个偏移量。而已。例如,您可以使用此代码执行 if(假设您使用 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 下看到标题。

    【讨论】:

    • 使用此解决方案不可能与标题视图进行用户交互,对吧?
    • 我无法完成这项工作。表格的部分标题在 contentInset/offset 上方不可见,并且标题中的按钮不可点击。不过,滚动很流畅,感觉很好。
    【解决方案6】:

    如果您遇到嵌套滚动问题,这是最简单的解决方案。

    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

    【讨论】:

      【解决方案7】:

      我认为有两种选择。

      由于您知道滚动视图和主视图的大小,因此您无法判断滚动视图是否到达底部。

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

      所以当它击中时;你基本上设置了

      [contentScrollView setScrollEnabled:NO];
      

      以及其他方式为您的 tableView。

      我认为更准确的另一件事是将手势添加到您的视图中。

      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
      

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

      【讨论】:

        【解决方案8】:

        我发现了一个很棒的图书馆 MXParallaxHeader

        在 Storyboard 中,只需将 UIScrollView 类设置为 MXScrollView,然后魔法就会发生。

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

        此外,该库还提供CocoapodsCarthage

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

        【讨论】:

          【解决方案9】:

          SWIFT 5

          由于各种不同的屏幕尺寸,当我无法保证滚动视图内容偏移 (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 的可见内容中。我将其设置为 tableView 下方的按钮。如果您的唯一条件是您的 tableView 完全可见,则可以将其设置为 tableVIew 的框架。

          我保留了何时禁用 tableView 的滚动的原始实现,它工作得很好。

          【讨论】:

            【解决方案10】:

            我尝试了标记为正确答案的解决方案,但它无法正常工作。用户需要在表格视图上单击两次才能滚动,之后我无法再次滚动整个屏幕。所以我只是在 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
            }
            

            【讨论】:

              【解决方案11】:

              也许是蛮力,但工作完美:顺便说一下,我使用自动布局。

              对于tableView(或collectionView 或其他),在storyboard 中设置任意高度,并为类创建一个出口。在适当的情况下,(viewDidLoad() 或...)将 tableView 的高度设置得足够大,以便 tableView 不需要滚动。 (需要提前知道行数)那么只有外层的scrollView会很好的滚动。

              【讨论】:

              • 这不适用于具有不同单元格高度的表格视图
              【解决方案12】:

              一个简单的技巧,如果你想实现它是用普通容器视图替换父滚动视图。 在容器视图上添加平移手势,您可以使用第一个视图的顶部约束来分配负值。您可以检查页面视图的来源,如果它达到顶部,您可以开始在页面视图的子视图的内容偏移量上分配该值。直到用户在容器视图中以最顶层视图的状态实现表格视图,您可以保持页面 tableView 的滚动禁用并允许通过设置内容偏移量手动滚动。 所以最初页面视图高度将在底部折叠(或说屏幕外)或更小。稍后向下滚动它将扩大以占用更多空间。

              如果导航栏或容器视图外的其他视图显示帧外,手势将自动停止响应。

              手势是许多应用中使用的用户交互过渡的关键。你可以用它模仿滚动一段时间。

              【讨论】:

                【解决方案13】:

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

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

                【讨论】:

                  【解决方案14】:
                  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);
                  

                  【讨论】:

                  • 嗨。这个问题被标记为“迅速”。请根据标签写出你的答案。谢谢! :)
                  • 您还应该在代码中添加一些解释。这个sn-p是如何解决OP的问题的?
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-05-13
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-10-15
                  相关资源
                  最近更新 更多