It's very possible (likely, I'd say) that this is not begin done in a scroll view.
Consider this example:
class TestSwitcherViewController: UIViewController {
var leadConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
let colors: [UIColor] = [
// light red
UIColor(red: 1.0, green: 0.5, blue: 0.5, alpha: 1.0),
// light green
UIColor(red: 0.5, green: 1.0, blue: 0.5, alpha: 1.0),
// light blue
UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0),
// light orange
UIColor(red: 0.9, green: 0.7, blue: 0.5, alpha: 1.0),
// yellow
UIColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0),
]
var prevView: UIView!
// create a view for each color (a label with the view number as its text)
// for each of the views
// if it's the first one,
// constrain leading to view leading
// else
// constrain leading to previous view leading, constant 1.0, multiplier 2.0
var i = 1
colors.forEach { c in
let v = UILabel()
v.backgroundColor = c
v.text = "\(i)"
v.textAlignment = .center
v.layer.cornerRadius = 20
v.layer.masksToBounds = true
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
v.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
v.widthAnchor.constraint(equalToConstant: 200.0).isActive = true
v.heightAnchor.constraint(equalToConstant: 400.0).isActive = true
if i == 1 {
leadConstraint = v.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0)
leadConstraint.isActive = true
} else {
NSLayoutConstraint(item: v, attribute: .leading, relatedBy: .equal, toItem: prevView, attribute: .leading, multiplier: 2.0, constant: 1.0).isActive = true
}
prevView = v
i += 1
}
// add a pan gesture recognizer to the view
let pan = UIPanGestureRecognizer(target: self, action: #selector(self.didPan(_:)))
view.addGestureRecognizer(pan)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateScales()
}
@objc func didPan(_ gesture: UIPanGestureRecognizer) -> Void {
let translation = gesture.translation(in: view)
// increment or decrement the leading anchor constant
let tmpX = leadConstraint.constant + (translation.x * 0.25)
// don't let it go past either side
leadConstraint.constant = min(max(tmpX, 0.0), view.frame.width - 200.0)
gesture.setTranslation(.zero, in: view)
updateScales()
}
func updateScales() -> Void {
view.subviews.forEach { v in
// percentage of distance from leading edge of label to 1/5th width of view
let pct = min(v.frame.origin.x / (view.frame.width * 0.2), 1.0)
let scale = 0.8 + 0.2 * pct
v.transform = .identity
v.transform = CGAffineTransform(scaleX: scale, y: scale)
}
}
}
It creates 5 colored views (numbered labels), and constrains them to each other using Leading anchors with both a Constant and a Multiplier. It adds a Pan Gesture to the view, so when you pan left or right it modifies the Constant of the Leading constraint of the "bottom" view to move it left / right. This in turn moves the other views... and since we're using a multiplier for the constraint, the movement "grows" as we slide to the right.
Here's how it looks on launch:

and here's how it looks after dragging a bit to the right:

Obviously, this would take a lot more work to replicate all the functionality of the App Switcher, but it might get you on your way.