I just saw this post, and went back and took another look at it. The code in the book does have an issue with the landscape view. It is making the image bigger because of the much bigger width, but that also moves it up higher. With some of the taller phones, it goes too high.
The best solution I found is to set the size of the image based on the smaller of the height and width, so it is the same size in either rotation. This means using “optional” constraints, meaning the priority is lower than 1000. You can then set one for height and width, and auto layout will use which ever one works best. You also need a required constraint that the height and width of the image be equal.
Here is my revised version of showItem, tested on the simulator for SE, 8, and XR:
func showItem(_ index: Int) {
removeLastItemView()
let imageView = UIImageView(image: UIImage(named: "summericons_100px_0\(index).png"))
imageView.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5)
imageView.layer.cornerRadius = 5.0
imageView.layer.masksToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
lastItemView = imageView
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.didTapItemImage(_:)))
//required constraints (required is the default)
let conX = imageView.centerXAnchor.constraint(
equalTo: view.centerXAnchor)
let conAspect = imageView.heightAnchor.constraint(
equalTo: imageView.widthAnchor)
let conBottom = imageView.bottomAnchor.constraint(
equalTo: view.bottomAnchor)
conBottom.identifier = "ConBottom"
//lower priority optional constraints
//orientation will determine which one can be fullfilled
let conHeight = imageView.heightAnchor.constraint(
equalTo: view.heightAnchor, multiplier: 0.33)
conHeight.priority = UILayoutPriority(750)
let conWidth = imageView.widthAnchor.constraint(
equalTo: view.widthAnchor, multiplier: 0.33)
conWidth.priority = UILayoutPriority(750)
conWidth.identifier = "ConWidth"
//activate and lay it all out
NSLayoutConstraint.activate([conX, conAspect, conBottom, conWidth, conHeight])
view.layoutIfNeeded()
//capture target value for conBottom (after doing layout)
let imgOffset = -imageView.frame.height/2
//then setup for animation
conWidth.constant = -view.frame.width // large enough to get minimum size
conBottom.constant = imageView.frame.height // off bottom of screen
view.layoutIfNeeded()
// do the animation
UIView.animate(
withDuration: 0.8, delay: 0.0,
usingSpringWithDamping: 0.4, initialSpringVelocity: 0.0,
animations: {
conBottom.constant = imgOffset
conWidth.constant = 0.0
self.view.layoutIfNeeded()
},
completion: { _ in
imageView.addGestureRecognizer(tap)
self.lastItemView = imageView
})
}
You also need to tweak removeLastItemView, if you want the width shrinking animation to work in landscape rotation. It needs the large ending value for the width constant to get a consistent result:
func removeLastItemView() {
guard
let lastView = lastItemView,
let conBottom = view.constraints.first(where:{$0.identifier == "ConBottom"}),
let conWidth = view.constraints.first(where:{$0.identifier == "ConWidth"})
else { return }
lastItemView = nil
let widthConst = -view.frame.width
UIView.animate(
withDuration: 0.5, delay: 0.0,
animations: {
conBottom.constant = lastView.frame.height
conWidth.constant = widthConst
lastView.alpha = 0.0
self.view.layoutIfNeeded()
},
completion: { _ in
lastView.removeFromSuperview()
})
}
Then you are in business!