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 inviewDidLoad
, 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:
Scrolled to the bottom:
and rotated (so we see the footerView height change) scrolled all the way to the bottom:
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
Convert an iOS Objective C Object to a JSON String
Synchronous Url Request on Swift 2
App Not Sized Properly iOS 8 iPhone Simulator
Uitableview Dynamic Cell Heights Only Correct After Some Scrolling
Creating a Navigationcontroller Programmatically (Swift)
Mkmapview Mkpointannotation Tap Event
How to Authenticate the Gklocalplayer on My 'Third Party Server'
How to Change Uitableviewrowaction Title Color
What Does the Text Inside Parentheses in @Interface and @Implementation Directives Mean
App Does Not Have Access to Your Photos or Videos iOS 9
How to Use Core Location/Gps Without Any Internet Connection/Disabled Cellular Network
Does This App Use the Advertising Identifier (IDFA)? - Admob 6.8.0
Use Didselectrowatindexpath or Prepareforsegue Method for Uitableview
How to Switch to Different Storyboard for iPhone 5
How to Get Uiscrollview Vertical Direction in Swift