I'm presenting a simple view controller. To follow MVC, I moved my programmatic view code into a separate UIView subclass. I followed the advice here of how to set up the custom view.
This works ok in iPhone. But, on iPad, the view controller is automatically presented with the new post-iOS 13 default modal style .pageSheet
, which causes one of my buttons to be too wide. I looked into the view debugger and it's because the width constraint is set to self.bounds.width * 0.7
, and self.bounds
returns the full width of the iPad (1024 points) at the time the constraint is set, not the actual final view (which is only 704 points wide).
iPad 12.9" simulator screenshot
Three questions:
In general, am I setting up the custom view + view controller correctly? Or is there a best practice I'm not following?
How do I force an update to the constraints so that the view recognizes it's being presented in a .pageSheet
modal mode on iPad, and automatically update the width of self.bounds
? I tried self.view.setNeedsLayout() and self.view.layoutIfNeeded()
but it didn't do anything.
Why is it that the Snooze button is laid out correctly on iPad, but not the Turn Off Alarm button... the Snooze button relies on constraints pinned to self.leadingAnchor
and self.trailingAnchor
. Why do those constraints correctly recognize the modal view they're being presented in, but self.bounds.width
doesn't?
Code:
Xcode project posted here
View Controller using a custom view:
class CustomViewController: UIViewController {
var customView: CustomView {
return self.view as! CustomView
}
override func loadView() {
let customView = CustomView(frame: UIScreen.main.bounds)
self.view = customView // Set view to our custom view
}
override func viewDidLoad() {
super.viewDidLoad()
print("View's upon viewDidLoad is: \(self.view.bounds)") // <-- This gives incorrect bounds
// Add actions for buttons
customView.snoozeButton.addTarget(self, action: #selector(snoozeButtonPressed), for: .touchUpInside)
customView.dismissButton.addTarget(self, action: #selector(dismissButtonPressed), for: .touchUpInside)
}
override func viewDidAppear(_ animated: Bool) {
print("View's bounds upon viewDidAppear is: \(self.view.bounds)") // <-- This gives correct bounds
// This doesn't work
// //self.view.setNeedsLayout()
// // self.view.layoutIfNeeded()
}
@objc func snoozeButtonPressed() {
// Do something here
}
@objc func dismissButtonPressed() {
self.dismiss(animated: true, completion: nil)
}
}
Custom view code:
class CustomView: UIView {
public var snoozeButton: UIButton = {
let button = UIButton(type: .system)
button.isUserInteractionEnabled = true
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Snooze", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
button.setTitleColor(UIColor.white, for: .normal)
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.white.cgColor
button.layer.cornerRadius = 14
return button
}()
public var dismissButton: UIButton = {
let button = UIButton(type: .system)
button.isUserInteractionEnabled = true
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Turn off alarm", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
button.setTitleColor(UIColor.white, for: .normal)
button.backgroundColor = UIColor.clear
button.layer.cornerRadius = 14
button.layer.borderWidth = 2
button.layer.borderColor = UIColor.white.cgColor
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupUI() {
self.backgroundColor = .systemPurple
// Add subviews
self.addSubview(snoozeButton)
self.addSubview(dismissButton)
}
func setupConstraints() {
print("Self.bounds when setting up custom view constraints is: \(self.bounds)") // <-- This gives incorrect bounds
NSLayoutConstraint.activate([
dismissButton.heightAnchor.constraint(equalToConstant: 60),
dismissButton.widthAnchor.constraint(equalToConstant: self.bounds.width * 0.7), // <-- This is the constraint that's wrong
dismissButton.centerXAnchor.constraint(equalTo: self.centerXAnchor),
dismissButton.centerYAnchor.constraint(equalTo: self.centerYAnchor),
snoozeButton.heightAnchor.constraint(equalToConstant: 60),
snoozeButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),
snoozeButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20),
snoozeButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -40)
])
}
}
.. and finally, the underlying view controller doing the presenting:
class BlankViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
}
override func viewDidAppear(_ animated: Bool) {
let customVC = CustomViewController()
self.present(customVC, animated: true, completion: nil)
}
}
Thanks.
There's no need to reference bounds
-- just use a relative width constraint.
Change this line:
dismissButton.widthAnchor.constraint(equalToConstant: self.bounds.width * 0.7), // <-- This gives incorrect bounds
to this:
dismissButton.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.7),
Now, the button will be 70% of the view's width. As an added benefit, it will adjust itself if/when the view width changes, instead of being stuck at a calculated Constant value.