【问题标题】:How do I auto size a UIScrollView to fit its content如何自动调整 UIScrollView 的大小以适合其内容
【发布时间】:2011-02-26 00:31:47
【问题描述】:

有没有办法让UIScrollView 自动调整到它正在滚动的内容的高度(或宽度)?

类似:

[scrollView setContentSize:(CGSizeMake(320, content.height))];

【问题讨论】:

    标签: iphone ios objective-c cocoa-touch uiscrollview


    【解决方案1】:
    import UIKit
    
    class DynamicSizeScrollView: UIScrollView {
    
        var maxHeight: CGFloat = UIScreen.main.bounds.size.height
        var maxWidth: CGFloat = UIScreen.main.bounds.size.width
    
        override func layoutSubviews() {
            super.layoutSubviews()
            if !__CGSizeEqualToSize(bounds.size,self.intrinsicContentSize){
                self.invalidateIntrinsicContentSize()
            }
        }
    
        override var intrinsicContentSize: CGSize {
            let height = min(contentSize.height, maxHeight)
            let width = min(contentSize.height, maxWidth)
            return CGSize(width: width, height: height)
        }
    
    }
    

    【讨论】:

      【解决方案2】:

      我遇到过的根据包含的子视图更新UIScrollView 的内容大小的最佳方法:

      Objective-C

      CGRect contentRect = CGRectZero;
      
      for (UIView *view in self.scrollView.subviews) {
          contentRect = CGRectUnion(contentRect, view.frame);
      }
      self.scrollView.contentSize = contentRect.size;
      

      斯威夫特

      let contentRect: CGRect = scrollView.subviews.reduce(into: .zero) { rect, view in
          rect = rect.union(view.frame)
      }
      scrollView.contentSize = contentRect.size
      

      【讨论】:

      • 我在viewDidLayoutSubviews 中运行它,所以自动布局会完成,在 iOS7 中它运行良好,但是由于某种原因测试 os iOS6 自动布局没有完成工作,所以我的高度有些错误值,所以我切换到viewDidAppear 现在工作正常.. 只是指出也许有人需要这个。谢谢
      • iOS6 iPad 的右下角有神秘的 UIImageView (7x7)。我通过只接受(可见的 && alpha)UI 组件来过滤掉它,然后它就起作用了。想知道该 imageView 是否与滚动条有关,绝对不是我的代码。
      • 启用滚动指示器时请小心! SDK 会自动为每一个创建一个 UIImageView。您必须考虑到它,否则您的内容大小将是错误的。 (见stackoverflow.com/questions/5388703/…
      • 可以确认这个解决方案在 Swift 4 中非常适合我
      • 实际上,我们不能从 CGRect.zero 开始联合,它只有在你将一些子视图置于负坐标时才有效。您应该从第一个子视图开始联合子视图帧,而不是从零开始。例如,您只有一个子视图,它是一个带有框架 (50, 50, 100, 100) 的 UIView。您的内容大小将是 (0, 0, 150, 150),而不是 (50, 50, 100, 100)。
      【解决方案3】:

      以下扩展将有助于 Swift

      extension UIScrollView{
          func setContentViewSize(offset:CGFloat = 0.0) {
              // dont show scroll indicators
              showsHorizontalScrollIndicator = false
              showsVerticalScrollIndicator = false
      
              var maxHeight : CGFloat = 0
              for view in subviews {
                  if view.isHidden {
                      continue
                  }
                  let newHeight = view.frame.origin.y + view.frame.height
                  if newHeight > maxHeight {
                      maxHeight = newHeight
                  }
              }
              // set content size
              contentSize = CGSize(width: contentSize.width, height: maxHeight + offset)
              // show scroll indicators
              showsHorizontalScrollIndicator = true
              showsVerticalScrollIndicator = true
          }
      }
      

      逻辑与给出的答案相同。但是,它忽略了UIScrollView 中的隐藏视图,并且在滚动指示器设置为隐藏后执行计算。

      此外,还有一个可选的函数参数,您可以通过将参数传递给函数来添加偏移值。

      【讨论】:

      • 这个解决方案包含太多假设,你为什么要在设置内容大小的方法中显示滚动指示器?
      【解决方案4】:

      对于使用 reduce 的 swift4:

      self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect.zero, {
         return $0.union($1.frame)
      }).size
      

      【讨论】:

      • 也值得检查是否至少一个子视图的原点 == CGPoint.zero 或者只是将特定(例如最大)子视图的原点分配为零。
      • 这仅适用于使用 CGRect() 放置视图的人。如果您使用约束,这将不起作用。
      【解决方案5】:

      这是对@leviatan 答案的 Swift 3 改编:

      扩展

      import UIKit
      
      
      extension UIScrollView {
      
          func resizeScrollViewContentSize() {
      
              var contentRect = CGRect.zero
      
              for view in self.subviews {
      
                  contentRect = contentRect.union(view.frame)
      
              }
      
              self.contentSize = contentRect.size
      
          }
      
      }
      

      用法

      scrollView.resizeScrollViewContentSize()
      

      非常容易使用!

      【讨论】:

      • 非常有用。经过这么多次迭代,我找到了这个解决方案,它是完美的。
      • 可爱!刚刚实现了。
      【解决方案6】:

      因为一个scrollView可以有其他scrollViews或者不同的inDepth subViews树,递归深度运行是更可取的。

      斯威夫特 2

      extension UIScrollView {
          //it will block the mainThread
          func recalculateVerticalContentSize_synchronous () {
              let unionCalculatedTotalRect = recursiveUnionInDepthFor(self)
              self.contentSize = CGRectMake(0, 0, self.frame.width, unionCalculatedTotalRect.height).size;
          }
      
          private func recursiveUnionInDepthFor (view: UIView) -> CGRect {
              var totalRect = CGRectZero
              //calculate recursevly for every subView
              for subView in view.subviews {
                  totalRect =  CGRectUnion(totalRect, recursiveUnionInDepthFor(subView))
              }
              //return the totalCalculated for all in depth subViews.
              return CGRectUnion(totalRect, view.frame)
          }
      }
      

      用法

      scrollView.recalculateVerticalContentSize_synchronous()
      

      【讨论】:

        【解决方案7】:

        来自@leviathan 的出色和最佳解决方案。只需使用 FP(函数式编程)方法翻译成 swift。

        self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect(), { 
          CGRectUnion($0, $1.frame) 
        }.size
        

        【讨论】:

        • 如果你想忽略隐藏的视图,你可以在传递给reduce之前过滤子视图。
        • 为 Swift 3 更新:self.contentSize = self.subviews.reduce(CGRect(), { $0.union($1.frame) }).size
        【解决方案8】:

        像这样设置动态内容大小。

         self.scroll_view.contentSize = CGSizeMake(screen_width,CGRectGetMaxY(self.controlname.frame)+20);
        

        【讨论】:

          【解决方案9】:

          对于那些懒得转换它的人来说,这是迅速接受的答案:)

          var contentRect = CGRectZero
          for view in self.scrollView.subviews {
              contentRect = CGRectUnion(contentRect, view.frame)
          }
          self.scrollView.contentSize = contentRect.size
          

          【讨论】:

          • 并针对 Swift3 进行了更新: var contentRect = CGRect.zero for view in self.scrollView.subviews { contentRect = contentRect.union(view.frame) } self.scrollView.contentSize = contentRect.size跨度>
          • 这必须在主线程上调用吗?在 didLayoutSubViews 中?
          【解决方案10】:

          为什么不是单行代码??

          _yourScrollView.contentSize = CGSizeMake(0, _lastView.frame.origin.y + _lastView.frame.size.height);
          

          【讨论】:

            【解决方案11】:

            或者干脆做:

            int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];
            

            (此解决方案是我在此页面中添加的评论。在获得 19 票赞成此评论后,我决定将此解决方案添加为正式答案,以造福社区!)

            【讨论】:

              【解决方案12】:

              我认为这可能是一种更新 UIScrollView 内容视图大小的好方法。

              extension UIScrollView {
                  func updateContentViewSize() {
                      var newHeight: CGFloat = 0
                      for view in subviews {
                          let ref = view.frame.origin.y + view.frame.height
                          if ref > newHeight {
                              newHeight = ref
                          }
                      }
                      let oldSize = contentSize
                      let newSize = CGSize(width: oldSize.width, height: newHeight + 20)
                      contentSize = newSize
                  }
              }
              

              【讨论】:

                【解决方案13】:

                我还发现 leviathan 的回答效果最好。然而,它正在计算一个奇怪的高度。循环遍历子视图时,如果将滚动视图设置为显示滚动指示器,则它们将位于子视图数组中。在这种情况下,解决方案是在循环之前暂时禁用滚动指示器,然后重新建立它们之前的可见性设置。

                -(void)adjustContentSizeToFit 是 UIScrollView 的自定义子类上的公共方法。

                -(void)awakeFromNib {    
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self adjustContentSizeToFit];
                    });
                }
                
                -(void)adjustContentSizeToFit {
                
                    BOOL showsVerticalScrollIndicator = self.showsVerticalScrollIndicator;
                    BOOL showsHorizontalScrollIndicator = self.showsHorizontalScrollIndicator;
                
                    self.showsVerticalScrollIndicator = NO;
                    self.showsHorizontalScrollIndicator = NO;
                
                    CGRect contentRect = CGRectZero;
                    for (UIView *view in self.subviews) {
                        contentRect = CGRectUnion(contentRect, view.frame);
                    }
                    self.contentSize = contentRect.size;
                
                    self.showsVerticalScrollIndicator = showsVerticalScrollIndicator;
                    self.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
                }
                

                【讨论】:

                  【解决方案14】:

                  我根据@emenegro 的解决方案提出了另一个解决方案

                  NSInteger maxY = 0;
                  for (UIView* subview in scrollView.subviews)
                  {
                      if (CGRectGetMaxY(subview.frame) > maxY)
                      {
                          maxY = CGRectGetMaxY(subview.frame);
                      }
                  }
                  maxY += 10;
                  [scrollView setContentSize:CGSizeMake(scrollView.frame.size.width, maxY)];
                  

                  基本上,我们会找出视图中最靠下的元素,并在底部添加 10px 的内边距

                  【讨论】:

                    【解决方案15】:

                    使用自动布局时的解决方案:

                    • 在所有涉及的视图上将translatesAutoresizingMaskIntoConstraints 设置为NO

                    • 使用滚动视图外部的约束来定位和调整滚动视图的大小。

                    • 使用约束在滚动视图中布置子视图,确保约束绑定到滚动视图的所有四个边缘,并且不要依赖滚动视图来获取它们大小。

                    来源: https://developer.apple.com/library/ios/technotes/tn2154/_index.html

                    【讨论】:

                    • 恕我直言,这是世界上真正的解决方案,一切都通过自动布局发生。关于其他解决方案:你在计算每个视图的高度和宽度时是认真的吗?
                    • 感谢您分享这个!这应该是公认的答案。
                    • 当您希望滚动视图的高度基于其内容高度时,这不起作用。 (例如,屏幕居中的弹出窗口,您不想滚动到一定数量的内容)。
                    • 这不能回答问题
                    • 很好的解决方案。但是,我会再添加一个项目符号...“如果子堆栈视图应该垂直增长,请不要限制它的高度。只需将其固定到滚动视图的边缘即可。”
                    【解决方案16】:

                    您可以通过计算哪个孩子“到达更远”来获取 UIScrollView 内内容的高度。要计算这一点,您必须考虑原点 Y(开始)和项目高度。

                    float maxHeight = 0;
                    for (UIView *child in scrollView.subviews) {
                        float childHeight = child.frame.origin.y + child.frame.size.height;
                        //if child spans more than current maxHeight then make it a new maxHeight
                        if (childHeight > maxHeight)
                            maxHeight = childHeight;
                    }
                    //set content size
                    [scrollView setContentSize:(CGSizeMake(320, maxHeight))];
                    

                    通过这种方式,项目(子视图)不必直接堆叠在另一个之下。

                    【讨论】:

                    • 你也可以在循环中使用这一行:maxHeight = MAX(maxHeight, child.frame.origin.y + child.frame.size.height) ;)
                    【解决方案17】:

                    我将此添加到 Espuz 和 JCC 的答案中。它使用子视图的 y 位置并且不包括滚动条。 编辑使用可见的最低子视图的底部。

                    + (CGFloat) bottomOfLowestContent:(UIView*) view
                    {
                        CGFloat lowestPoint = 0.0;
                    
                        BOOL restoreHorizontal = NO;
                        BOOL restoreVertical = NO;
                    
                        if ([view respondsToSelector:@selector(setShowsHorizontalScrollIndicator:)] && [view respondsToSelector:@selector(setShowsVerticalScrollIndicator:)])
                        {
                            if ([(UIScrollView*)view showsHorizontalScrollIndicator])
                            {
                                restoreHorizontal = YES;
                                [(UIScrollView*)view setShowsHorizontalScrollIndicator:NO];
                            }
                            if ([(UIScrollView*)view showsVerticalScrollIndicator])
                            {
                                restoreVertical = YES;
                                [(UIScrollView*)view setShowsVerticalScrollIndicator:NO];
                            }
                        }
                        for (UIView *subView in view.subviews)
                        {
                            if (!subView.hidden)
                            {
                                CGFloat maxY = CGRectGetMaxY(subView.frame);
                                if (maxY > lowestPoint)
                                {
                                    lowestPoint = maxY;
                                }
                            }
                        }
                        if ([view respondsToSelector:@selector(setShowsHorizontalScrollIndicator:)] && [view respondsToSelector:@selector(setShowsVerticalScrollIndicator:)])
                        {
                            if (restoreHorizontal)
                            {
                                [(UIScrollView*)view setShowsHorizontalScrollIndicator:YES];
                            }
                            if (restoreVertical)
                            {
                                [(UIScrollView*)view setShowsVerticalScrollIndicator:YES];
                            }
                        }
                    
                        return lowestPoint;
                    }
                    

                    【讨论】:

                    • 太棒了!正是我需要的。
                    • 我很惊讶像这样的方法不是类的一部分。
                    • 为什么你在方法的开头做了self.scrollView.showsHorizontalScrollIndicator = NO;self.scrollView.showsVerticalScrollIndicator = NO;self.scrollView.showsHorizontalScrollIndicator = YES;self.scrollView.showsVerticalScrollIndicator = YES;的结尾?
                    • 感谢 Richy :) +1 的回答。
                    • @richy 你说得对,滚动指示器是可见的子视图,但显示/隐藏逻辑是错误的。您需要添加两个 BOOL 局部变量:restoreHorizo​​ntal=NO,restoreVertical=NO。如果显示Horizo​​ntal,隐藏它,将restoreHorizo​​ntal 设置为YES。垂直也一样。接下来,在for/in循环之后插入if语句:如果restoreHorizo​​ntal,设置为显示它,垂直相同。您的代码将强制显示两个指标
                    【解决方案18】:

                    包装 Richy 的代码我创建了一个自定义 UIScrollView 类,它可以自动 内容完全调整大小!

                    SBScrollView.h

                    @interface SBScrollView : UIScrollView
                    @end
                    

                    SBScrollView.m:

                    @implementation SBScrollView
                    - (void) layoutSubviews
                    {
                        CGFloat scrollViewHeight = 0.0f;
                        self.showsHorizontalScrollIndicator = NO;
                        self.showsVerticalScrollIndicator = NO;
                        for (UIView* view in self.subviews)
                        {
                            if (!view.hidden)
                            {
                                CGFloat y = view.frame.origin.y;
                                CGFloat h = view.frame.size.height;
                                if (y + h > scrollViewHeight)
                                {
                                    scrollViewHeight = h + y;
                                }
                            }
                        }
                        self.showsHorizontalScrollIndicator = YES;
                        self.showsVerticalScrollIndicator = YES;
                        [self setContentSize:(CGSizeMake(self.frame.size.width, scrollViewHeight))];
                    }
                    @end
                    

                    使用方法:
                    只需将 .h 文件导入您的视图控制器,然后 声明一个 SBScrollView 实例而不是普通的 UIScrollView 实例。

                    【讨论】:

                    • layoutSubviews 看起来是此类布局相关代码的正确位置。但是在 UIScrollView 中,每次滚动时更新内容偏移量时都会调用 layoutSubview 。而且由于滚动时内容大小通常不会改变,这相当浪费。
                    • 确实如此。避免这种低效率的一种方法可能是检查 [self isDragging] 和 [self isDecelerating],并且只有在两者都为 false 时才执行布局。
                    【解决方案19】:

                    大小取决于其中加载的内容和剪辑选项。如果它是一个文本视图,那么它还取决于换行、文本行数、字体大小等等。你几乎不可能自己计算。好消息是,它是在视图加载后在 viewWillAppear 中计算的。在此之前,一切都是未知的,并且内容大小将与帧大小相同。但是,在 viewWillAppear 方法和之后(例如 viewDidAppear),内容大小将是实际的。

                    【讨论】:

                      【解决方案20】:

                      UIScrollView 不会自动知道其内容的高度。你必须自己计算高度和宽度

                      用类似的东西来做

                      CGFloat scrollViewHeight = 0.0f;
                      for (UIView* view in scrollView.subviews)
                      {
                         scrollViewHeight += view.frame.size.height;
                      }
                      
                      [scrollView setContentSize:(CGSizeMake(320, scrollViewHeight))];
                      

                      但这只有在视图低于另一个时才有效。如果您有一个相邻的视图,并且您不想将滚动条的内容设置为大于实际值,则只需添加一个的高度。

                      【讨论】:

                      • 你认为这样的事情也可以吗? CGFloat scrollViewHeight = scrollView.contentSize.height; [scrollView setContentSize:(CGSizeMake(320, scrollViewHeight))];顺便说一句,别忘了问尺寸然后是高度。 scrollViewHeight += view.frame.size.height
                      • 将 scrollViewHeight 初始化为高度会使滚动条大于其内容。如果这样做,请不要在滚动条内容的初始高度上添加可见视图的高度。虽然也许你需要一个初始差距或其他东西。
                      • 或者直接做:int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];
                      • @saintjab,谢谢,我只是将其添加为正式答案:)
                      【解决方案21】:

                      这真的取决于内容: content.frame.height 可能会给你你想要的?取决于内容是单个事物还是事物的集合。

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2015-05-16
                        • 1970-01-01
                        • 1970-01-01
                        • 2015-12-27
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2019-01-20
                        相关资源
                        最近更新 更多