How is the contentOffset changed in this storyboard project?

Viewed 61

I am trying to understand how the below project changes the scrollView contentOffset to be at 356.

The sample project is here: https://github.com/samshiffman321/CollapsingHeaderExample/

Based on the article https://www.willowtreeapps.com/craft/how-to-create-a-simple-collapsing-header-with-uiscrollview

Below is a picture of the storyboard, and the constant constraints are:

Header View Height = 456

Header View Top = 44

Container View Top = 100

enter image description here

The code is fairly simple, and in viewDidLoad it changes the consentInset

if let scrollView = containerView.subviews.first as? UIScrollView {
    // adjust the scroll view's top inset to account for scrolling the header offscreen
    scrollView.contentInset = UIEdgeInsets(top: maxScrollAmount, left: 0, bottom: 0, right: 0)
}

The maxScrollAmount being:

var maxScrollAmount: CGFloat {
    let expandedHeight = headerViewHeight.constant
    let collapsedHeight = containerViewTop.constant
    return expandedHeight - collapsedHeight
}

Autolayout generates the following constraints:

enter image description here

If I print the description of the table, you can see that the offset has changed to 356

<UITableView: 0x127013a00; frame = (0 0; 390 710); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x60000257b660>; layer = <CALayer: 0x600002b4c480>; contentOffset: {0, -356}; contentSize: {390, 4400}; adjustedContentInset: {356, 0, 0, 0}; dataSource: <TestCollapsingHeader.TableViewController: 0x13ae0cc00>>

The reason for the question, is I am trying to replicate this project programmatically, which works, but only if I change the contentOffset as below.

if let scrollView = containerView.subviews.first as? UIScrollView {
    // adjust the scroll view's top inset to account for scrolling the header offscreen
    scrollView.contentInset = UIEdgeInsets(top: maxScrollAmount, left: 0, bottom: 0, right: 0)

    scrollView.contentOffset.y = -maxScrollAmount
}

But the sample project above doesn't do that, in fact, I can't see it directly manipulates the contentOffset at all.

I have created a sample of my project here: https://github.com/mickey1980/SampleHeaderCollapse.git

appreciate any help.

1 Answers

Your "fix" of adding scrollView.contentOffset.y = -maxScrollAmount is a valid option.

But, to explain why it's needed:

The example project you've based your code-only work on loads a UITableViewController from Storyboard, embedding it in the container view.

UIKit does a bunch of work "behind the scenes" when loading UI elements from a Storyboard.

If you modify that project to dynamically load the table view controller as a child view controller via storyboard?.instantiateViewController(withIdentifier: "tableViewController") and then add its .tableView into the container view, everything works as designed.

However, if you create a table view controller via code-only as a child view controller, adding its .tableView into the container view, you'll see the same issue you're hitting,

The big difference (based on examining what's going on), is that when UIKit loads the table view controller from Storyboard, it gives the table view an initial frame.

If you change this section of your code:

self.headerViewHeight = self.headerView.heightAnchor.constraint(equalToConstant: 456)
self.headerViewHeight.isActive = true

self.tableView = UITableView(frame: .zero, style: .plain)

self.tableView.translatesAutoresizingMaskIntoConstraints = false
self.tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

to:

let maxHeaderHeight: CGFloat = 456

self.headerViewHeight = self.headerView.heightAnchor.constraint(equalToConstant: maxHeaderHeight)
self.headerViewHeight.isActive = true

// width is inconsequential
let f: CGRect = CGRect(x: 0, y: 0, width: 100, height: maxHeaderHeight)

// create a table view ** with a frame **
self.tableView = UITableView(frame: f, style: .plain)

self.tableView.translatesAutoresizingMaskIntoConstraints = false
self.tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

you no longer need the scrollView.contentOffset.y = -maxScrollAmount

Related