Issue #371
Scrolling UIScrollView
is used in common scenarios like steps, onboarding.
From iOS 11, UIScrollView has contentLayoutGuide
and frameLayoutGuide
Docs
https://developer.apple.com/documentation/uikit/uiscrollview/2865870-contentlayoutguide
Use this layout guide when you want to create Auto Layout constraints related to the content area of a scroll view.
https://developer.apple.com/documentation/uikit/uiscrollview/2865772-framelayoutguide
Use this layout guide when you want to create Auto Layout constraints that explicitly involve the frame rectangle of the scroll view itself, as opposed to its content rectangle.
Code
I found out that using contentLayoutGuide
and frameLayoutGuide
does not work in iOS 11, when swiping to the next page, it breaks the constraints. iOS 12 works well, so we have to check iOS version
Let the contentView
drives the contentSize
of scrollView
import UIKit
final class PagerView: UIView {
let scrollView = UIScrollView()
private(set) var pages: [UIView] = []
private let contentView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
private func setup() {
scrollView.isPagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
addSubview(scrollView)
scrollView.addSubview(contentView)
if #available(iOS 12.0, *) {
scrollView.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.on([
scrollView.frameLayoutGuide.pinEdges(view: self)
])
NSLayoutConstraint.on([
scrollView.contentLayoutGuide.pinEdges(view: contentView),
[scrollView.contentLayoutGuide.heightAnchor.constraint(
equalTo: scrollView.frameLayoutGuide.heightAnchor
)]
])
} else {
NSLayoutConstraint.on([
scrollView.pinEdges(view: self),
scrollView.pinEdges(view: contentView)
])
NSLayoutConstraint.on([
contentView.heightAnchor.constraint(equalTo: heightAnchor)
])
}
}
func update(pages: [UIView]) {
clearExistingViews()
self.pages = pages
setupConstraints()
}
private func setupConstraints() {
pages.enumerated().forEach { tuple in
let index = tuple.offset
let page = tuple.element
contentView.addSubview(page)
NSLayoutConstraint.on([
page.topAnchor.constraint(equalTo: scrollView.topAnchor),
page.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
page.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
if index == 0 {
NSLayoutConstraint.on([
page.leftAnchor.constraint(equalTo: contentView.leftAnchor)
])
} else {
NSLayoutConstraint.on([
page.leftAnchor.constraint(equalTo: pages[index - 1].rightAnchor)
])
}
if index == pages.count - 1 {
NSLayoutConstraint.on([
page.rightAnchor.constraint(equalTo: contentView.rightAnchor)
])
}
}
}
private func clearExistingViews() {
pages.forEach {
$0.removeFromSuperview()
}
}
}
extension UILayoutGuide {
func pinEdges(view: UIView, inset: UIEdgeInsets = UIEdgeInsets.zero) -> [NSLayoutConstraint] {
return [
leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: inset.left),
trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: inset.right),
topAnchor.constraint(equalTo: view.topAnchor, constant: inset.top),
bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: inset.bottom)
]
}
}