Get the height of the UIScrollView after layout in

2020-03-08 06:43发布

问题:

I have a situation where I'm using a UIScrollView to help users with smaller phones see the full text inside a window. And I was wanting to show an image icon if they needed to scroll (just so that they know there is more inside the window) but I'm having a problem finding the height of the scrollview using Swift.

This is the hierarchy of my objects

Basically, those 4 labels fit inside a 4.7" iPhone but not the 4" iPhone. I tried setting some logic to check if the UIScrollView content is larger than the window, but it doesn't seem to be working. I keep getting that the height is zero.

I'm using scrollHeight = scrollView.contentSize.height and have tried putting that inside of viewDidLoad(), viewWillAppear(), and viewDidLayoutSubviews() but every time it ends up being 0.

Is contentSize the right thing I should be using to check the height?

回答1:

TL;DR;

As a general guide, Auto Layout does not finish giving views their size by the time viewDidLoad is called (neither is it correct inside viewDidLayoutSubviews). To make sure all views and their subviews have their correct sizes before querying their bounds or frame, do:

self.view.setNeedsLayout()
self.view.layoutIfNeeded()
print(self.someSubview.frame) // gives correct frame

Explanation

Somewhat counterintuitively, putting your code inside viewDidLayoutSubviews does not mean all sub-views have been given their correct size by Auto Layout. To quote the docs for this method:

However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted.

What this means is that: inside viewDidLayoutSubviews, only the first hierarchical-level of sub-views will have their correct size (i.e. child views but not "grandchild views").


Here is an example layout I created (the red view is the child, the blue view is the grandchild).

A few important points about this example:

  • All views are positioned using Auto Layout constraints to their parent view (fixed margins, but not fixed widths), this allows them to grow or shrink depending on the screen size of the simulator I run this on.
  • I've used an iPhone 7 sized view controller in my storyboard, and ran the example on an iPhone 7 Plus Simulator. This means I can be sure the views will grow somewhat and use this to determine whether or not a view has its original size or its final size.

Note that in Interface Builder, the original width of the child view (red) is 240 points and the original width of the grandchild view (blue) is 200 points.

And here is the code:

class ViewController: UIViewController {

    @IBOutlet weak var childView: UIView!
    @IBOutlet weak var grandchildView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        print("child     ", #function, childView.frame.size.width)
        print("grandchild", #function, grandchildView.frame.size.width)
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        print("child     ", #function, childView.frame.size.width)
        print("grandchild", #function, grandchildView.frame.size.width)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print("child     ", #function, childView.frame.size.width)
        print("grandchild", #function, grandchildView.frame.size.width)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        print("child     ", #function, childView.frame.size.width)
        print("grandchild", #function, grandchildView.frame.size.width)
    }
}

And here is the output:

child      viewDidLoad() 240.0
grandchild viewDidLoad() 200.0
child      viewWillAppear 240.0
grandchild viewWillAppear 200.0
child      viewDidLayoutSubviews() 271.0 // `child` has it's final width now
grandchild viewDidLayoutSubviews() 200.0 // `grandchild` still has it's original width!

// By now, in MOST cases, all views will have their correct sizes.
child      viewDidAppear 271.0
grandchild viewDidAppear 231.0

What you can see from this output is that viewDidLayoutSubviews does not guarantee that anything past the immediate sub-views of the View Controller's principal view have their correct size.

Note: Usually by the time viewDidAppear is called, views have their correct size, but I'd say this is by no means guaranteed.