What's the Uiscrollview Contentinset Property For

What's the UIScrollView contentInset property for?

It sets the distance of the inset between the content view and the enclosing scroll view.

Obj-C

aScrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 7.0);

Swift 5.0

aScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 7.0)

Here's a good iOS Reference Library article on scroll views that has an informative screenshot (fig 1-3) - I'll replicate it via text here:

  _|←_cW_→_|_↓_
| |
---------------
|content| ↑
↑ |content| contentInset.top
cH |content|
↓ |content| contentInset.bottom
|content| ↓
---------------
_|_______|___



(cH = contentSize.height; cW = contentSize.width)

The scroll view encloses the content view plus whatever padding is provided by the specified content insets.

What exactly are contentOffset& contentInset of ScrollView

A few observations:

  • The contentOffset is where the user has currently scrolled within the scroll view. This obviously changes as the user scrolls. You don't generally change this programmatically (unless you want to programmatically scroll somewhere, e.g. have a button to scroll to the top).

  • The contentInset is how much the content is visually inset inside the scroll view (i.e. what the "margins" within the scrollview are). You generally set this once in IB or in viewDidLoad, as the scroll view is instantiated.

  • The contentSize is how big the scrollable content is. Note, with autolayout, you don't have to specify this manually, but rather is calculated by the constraints that you specified between the scroll view and its subviews (and the constraints for the subviews and between the subviews).

To get scrolling to work correctly, it is a combination of (a) the bounds of the scroll view, less any contentInset; and (b) the contentSize, as calculated for you from the constraints of the subviews.

UIScrollView content inset defined by a height of a view outside of the subtree

OK - one approach...

As of iOS 11 (I'm assuming you don't need to target earlier than that), a subview of a UIScrollView can be constrained to the scroll view's Frame Layout Guide. This made it easy to add non-scrolling UI elements to the scroll view hierarchy.

Based on this hierarchy:

- view
- scrollView
- contentView
- element1
- element2
- element3
- UILayoutGuide
- footerView

What we'll do is:

  • add all the "scrollable" elements to the contentView
  • plus add a UILayoutGuide to the contentView which will serve as or "bottom" scrollable element
  • add the footerView to the scrollView last so it is at the top of the z-order
  • constrain the footerView to the scrollView's Frame Layout Guide so it stays put
  • constrain the heightAnchor of our UILayoutGuide equal to the heightAnchor of the footerView

Because a UILayoutGuide is a non-rendering view, it will not be visible but it will create the space from the bottom of our last viewable element to the bottom of the contentView -- and it will automatically change height if/when the footerView changes height.

Here's a complete example - scrollView / contentView / 3 imageViews / layout guide / translucent footerView:

class ExampleViewController: UIViewController {

let scrollView: UIScrollView = {
let v = UIScrollView()
v.backgroundColor = .lightGray
return v
}()

let contentView: UIView = {
let v = UIView()
v.backgroundColor = .cyan
return v
}()

let footerView: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.textColor = .white
v.font = UIFont.systemFont(ofSize: 24.0, weight: .bold)
v.text = "Footer View"
v.backgroundColor = UIColor.black.withAlphaComponent(0.65)
return v
}()

var imgView1: UIImageView = {
let v = UIImageView()
v.backgroundColor = .red
v.image = UIImage(systemName: "1.circle")
v.tintColor = .white
return v
}()

var imgView2: UIImageView = {
let v = UIImageView()
v.backgroundColor = .green
v.image = UIImage(systemName: "2.circle")
v.tintColor = .white
return v
}()

var imgView3: UIImageView = {
let v = UIImageView()
v.backgroundColor = .blue
v.image = UIImage(systemName: "3.circle")
v.tintColor = .white
return v
}()

override func viewDidLoad() {
super.viewDidLoad()

// add 3 image views as the content we want to see
contentView.addSubview(imgView1)
contentView.addSubview(imgView2)
contentView.addSubview(imgView3)

// add contentView to srollView
scrollView.addSubview(contentView)

// add footer view to scrollView last so it's at the top of the z-order
scrollView.addSubview(footerView)

view.addSubview(scrollView)

[scrollView, contentView, footerView, imgView1, imgView2, imgView3].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}

// "spacer" for bottom of scroll content
// we'll constrain it to the height of the footer view
let spacerGuide = UILayoutGuide()
contentView.addLayoutGuide(spacerGuide)

let g = view.safeAreaLayoutGuide
let svCLG = scrollView.contentLayoutGuide
let scFLG = scrollView.frameLayoutGuide

NSLayoutConstraint.activate([

// constrain scrollView view 40-pts on all 4 sides to view (safe-area)
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),

// contentView view 0-pts top / leading / trailing / bottom to scrollView contentLayoutGuide
contentView.topAnchor.constraint(equalTo: svCLG.topAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: svCLG.leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: svCLG.trailingAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: svCLG.bottomAnchor, constant: 0.0),

// contentView width == scrollView frameLayoutGuide width
contentView.widthAnchor.constraint(equalTo: scFLG.widthAnchor, constant: 0.0),


// imgView1 to top of contentView
imgView1.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),

// imgView1 width / height
imgView1.widthAnchor.constraint(equalToConstant: 240.0),
imgView1.heightAnchor.constraint(equalToConstant: 240.0),

// imgView1 centerX to contentView centerX
imgView1.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),


// imgView2 top to bottom of imgView1 + 20-pt spacing
imgView2.topAnchor.constraint(equalTo: imgView1.bottomAnchor, constant: 20.0),

// imgView2 width / height
imgView2.widthAnchor.constraint(equalToConstant: 200.0),
imgView2.heightAnchor.constraint(equalToConstant: 280.0),

// imgView2 centerX to contentView centerX
imgView2.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),


// imgView3 top to bottom of imgView2 + 20-pt spacing
imgView3.topAnchor.constraint(equalTo: imgView2.bottomAnchor, constant: 20.0),

// imgView3 width / height
imgView3.widthAnchor.constraint(equalToConstant: 280.0),
imgView3.heightAnchor.constraint(equalToConstant: 320.0),

// imgView3 centerX to contentView centerX
imgView3.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),


// spacerGuide top to bottom of actual content
// spacerGuide top to imgView3 bottom
spacerGuide.topAnchor.constraint(equalTo: imgView3.bottomAnchor, constant: 0.0),

// spacerGuide to leading / trailing / bottom of contentView
spacerGuide.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0.0),
spacerGuide.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0.0),
spacerGuide.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),

// footerView to leading / trailing / bottom of scrollView frameLayoutGuide
// (constrained to frameLayoutGuide so it won't scroll)
footerView.leadingAnchor.constraint(equalTo: scFLG.leadingAnchor, constant: 0.0),
footerView.trailingAnchor.constraint(equalTo: scFLG.trailingAnchor, constant: 0.0),
footerView.bottomAnchor.constraint(equalTo: scFLG.bottomAnchor, constant: 0.0),

// footerView height == scrollView height with 0.25 multiplier
// (so it will change height when scrollView changes height, such as device rotation)
footerView.heightAnchor.constraint(equalTo: scFLG.heightAnchor, multiplier: 0.25),

// finally, spacerGuide height equal to footerView height
spacerGuide.heightAnchor.constraint(equalTo: footerView.heightAnchor),

])

}
}

Result:

Sample Image

Scrolled to the bottom:

Sample Image

and rotated (so we see the footerView height change) scrolled all the way to the bottom:

Sample Image


Edit

The answer to the specific question is: you can't.

A scroll view's contentInset is not an object to which you can add constraints... it's a Property of the scroll view. Much like you could not constrain a scroll view's .backgroundColor to an auto-layout constraint.

UIScrollView contentInset not working

Check that self.scrollView.contentSize is set properly. If contentSize.height is 0 (as will be the case following your steps), then a large inset is required.

Try adding [self.scrollView setContentSize: CGSizeMake(320, 568)]; to the button method and you'll notice that your inset will now behave as expected.

What do I have to do after setting contentInset or automaticallyAdjustsScrollViewInsets for it to actually beeing applied?

If you want your initial display of your scrollview to include an initial content offset, then set the contentInset value in viewDidLoad.
If you wish to dynamically change the contentInset and have the contentOffset change as well, then you need to adjust the contentOffset at the same time as the contentInset. For example:

// Adjust content inset and initial offset to account for the header
UIEdgeInsets contentInset = self.collectionView.contentInset;
contentInset.top += self.stickyHeaderView.bounds.size.height;
self.collectionView.contentInset = contentInset;
if (-1 * self.collectionView.contentOffset.y < contentInset.top) {
CGPoint contentOffset = self.collectionView.contentOffset;
contentOffset.y = -1*contentInset.top;
[self.collectionView setContentOffset:contentOffset animated:YES];
}

contentInset of the UIScrollView isn't working

After much struggle, I was able to accomplish what I wanted. What I did was, in the viewDidLoad I registered for the keyboard notifications. And when the keyboard appears, I got its frame in to a variable and the login form's (which is a table view) frame into another variable.

By using CGRectIntersection I got the frame of the intersected portion when the keyboard overlaps the login form and go its height. Then I simply set it in scroll view's setContentOffset method to automatically move the scroll view up. To move it back down to the original position I just set scroll view's contentOffset property again to CGPointZero.

func registerForKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
}

func keyboardWasShown(notification: NSNotification) {
var info: NSDictionary = notification.userInfo as NSDictionary
var keyboardFrame: CGRect = info.objectForKey(UIKeyboardFrameEndUserInfoKey).CGRectValue()
var loginFormFrame: CGRect = self.view.convertRect(self.tableView.frame, fromView: nil)
var coveredFrame: CGRect = CGRectIntersection(loginFormFrame, keyboardFrame)

self.scrollView.setContentOffset(CGPointMake(0, coveredFrame.height + 20), animated: true)
}

func keyboardWillBeHidden(notification: NSNotification) {
self.scrollView.contentOffset = CGPointZero
}


Related Topics



Leave a reply



Submit