iOS 11 prefersLargeTitles not expanding after orientation change - ios

I've implemented the iOS 11 feature prefersLargeTitles and it works just fine. Portrait mode is working as expected:
I understand the large title will always stay collapsed (small) in landscape mode and that's fine to me. The problem is when I try to change to landscape and then again to portrait, the large title should be expanded (big) by default back in portrait mode, but it won't until I scroll down a bit:
My code looks quite simple:
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
}
I also tried using different values on tableView.contentInsetAdjustmentBehavior, nothing changed. I'm kind of solving it by now scrolling down the table programmatically after orientation changes, but I think that's just a (not very nice) workaround.
Is that supposed to be working as expected? Is it something left in my implementation? Is there a better workaround to this?

I faced the same issue. This worked for me.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
navigationItem.largeTitleDisplayMode = .always
coordinator.animate(alongsideTransition: { (_) in
self.coordinator?.navigationController.navigationBar.sizeToFit()
}, completion: nil)
}

One approach could be save the maximum navigation bar height, and set it during rotation.
Something like this:
var maximumHeight: CGFloat = 0
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
guard let navigationController = navigationController else {
return
}
if maximumHeight < navigationController.navigationBar.frame.height {
maximumHeight = navigationController.navigationBar.frame.height
}
coordinator.animate(alongsideTransition: { (_) in
navigationController.navigationBar.frame.size.height = self.maximumHeight
}, completion: nil)
}
In landscape, the system knows that it must change its size, so you don't have to worry about it.
#rassar #twofish

iOS 16
First set the PrefersLargeTitles to true either on ViewDidLoad() or on the NavigationBar in the storyboard.
Then on the Navigation Controller's rootController add this:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate { (_) in
self.navigationController?.navigationBar.sizeToFit()
}
}

Related

UITableViewCell actions in iOS 11 after device rotation are broken

I use standard UITableView and UITableViewRowAction classes. In iOS 11 after device rotation I have weird behaviour, here is the sample:
Basically, it breaks the table view. In real app with more complicated cells and table it even worse (overlapping cells, etc.) I already tried to call SetNeedsDisplay() on UITableView after rotation, doesn't help.
In iOS 10 actions looked slightly different and work perfectly.
Some of the comments mention this, but the clear winner is to cancel editing the cell on rotation:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self.tableView setEditing:NO animated:NO];
}
Swift version:
You can reset the edit state of the cells so that the actions no longer appear using setEditing(_:animated:) inside viewWillTransition(to:with:), which is called when the device is rotated.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
tableView.setEditing(false, animated: true)
}
This only works when you call setEditing(_:animated:) outside the coordinator's animate method. Setting editing to false inside the animation block will have no effect.
I have a custom image in the as UIContextualAction, and I was still facing some issues just setting the tableView.setEditing(false, animated: true) after the super.viewWillTransition(to: size, with: coordinator), I had to embed it in perform without animations, and now it works fine :)
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
UIView.performWithoutAnimation {
self.tableView.setEditing(false, animated: false)
}
}
The accepted answer has, in my case, the problem that after rotation the table is not editable anymore. I fixed it with setting editing to true directly after setting it to false:
tableView.setEditing(false, animated: false)
tableView.setEditing(true, animated: false)
I admit it's a kind of hacky, better solutions welcome ;)

iOS - Wrong UIScreen bounds in viewWillTransition for iPad

I have to check if my device has changed orientation in iOS 8+.
My approach is:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let isLand = UIScreen.main.bounds.width > UIScreen.main.bounds.height
coordinator.animate(alongsideTransition: nil) { _ in
let isLand2 = UIScreen.main.bounds.width > UIScreen.main.bounds.height
print("\(isLand) -> \(isLand2)")
}
}
it works fine in iPhone but in iPad isLand has already the new value which should be after the orientation completion, so:
Portrait > Landscape: true -> true
Landscape > Portrait: false -> false
According to the documentation the bounds should change with the orientation so it should have a before/after bounds, shouldn't it?
UIScreen main bounds:
This rectangle is specified in the current coordinate space, which
takes into account any interface rotations in effect for the device.
Therefore, the value of this property may change when the device
rotates between portrait and landscape orientations.
Whereas it works fine both iPhone and iPad if I use the bounds of the current root view controller like this:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let isLand = UIApplication.shared.keyWindow!.rootViewController!.view.bounds.width > UIApplication.shared.keyWindow!.rootViewController!.view.bounds.height
coordinator.animate(alongsideTransition: nil) { _ in
let isLand2 = UIApplication.shared.keyWindow!.rootViewController!.view.bounds.width > UIApplication.shared.keyWindow!.rootViewController!.view.bounds.height
print("\(isLand) -> \(isLand2)")
}
}
Portrait > Landscape: false -> true
Landscape > Portrait: true -> false
You should try using the containerView of the coordinator context instead.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let isLand = coordinator.containerView.bounds.width > coordinator.containerView.bounds.height
coordinator.animate(alongsideTransition: nil) { _ in
let isLand2 = coordinator.containerView.bounds.width > coordinator.containerView.bounds.height
print("\(isLand) -> \(isLand2)")
}
}
If you want to get further information regarding the transition you can use the func view(forKey: UITransitionContextViewKey) and func viewController(forKey: UITransitionContextViewControllerKey) using the .from key.

Swift viewWillTransition not called

I'm creating a full screen image gallery using a UICollectionView. When the user rotates the device, I perform updates to the UICollectionView within
func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
I present this UIViewController modally and have a UICollectionView taking up the full screen. Within viewDidLoad, I create the flow layout as:
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
flowLayout.minimumInteritemSpacing = 0
flowLayout.minimumLineSpacing = 0
photosCollectionView.isPagingEnabled = true
photosCollectionView.setCollectionViewLayout(flowLayout, animated: true)
I also have the size as:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return photosCollectionView.frame.size
}
When I rotate my device, viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) is never called, which causes the UICollectionViewLayout to not update. While I rotate the device, I do get the message:
The behavior of the UICollectionViewFlowLayout is not defined because: the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
I've read online that I can add:
self.automaticallyAdjustsScrollViewInsets = false
to the UIViewController, but that had no affect. There are no content or section insets with the UICollectionView.
I also have the super.viewWillTransition called within the function as well. Can anyone assist me on what could be causing this issue?
If you are just concern about the layout when the device rotate then please use:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
collectionView.layoutIfNeeded()
}
From apple docs:
public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
This method is called when the view controller's view's size is
changed by its parent (i.e. for the root view controller when its
window rotates or is resized).
If you override this method, you should either call super to propagate the change to children or manually forward the change to
children.
I guess you might called this function on a parent of that view without calling super
A work around would also be to register for the device rotation:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
#objc func deviceOrientationDidChange(_ notification: Notification) {
let orientation = UIDevice.current.orientation
print(orientation)
}
I'd like to repost an answer which i left in another thread which describes the same issue.
People have already explained that you have to call super. I'd like to add a piece of information that might help people who would have faced what i faced.
Scenario: Parent -> Child (viewWillTransition not called in child)
If your view controller is a child view controller, then check if the parent view controller delegate is called and if super is called there. Otherwise it won't be propagated to the child view controller!
class ParentViewController: UIViewController {
func presentChild() {
let child = ChildViewController()
present(child, animated: false, compeltion: nil)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator) // If this line is missing your child will not get the delegate call in it's viewWillTransition
// Do something
}
}
class ChildViewController: UIViewController {
// This method will not get called if presented from parent view controller and super is not called inside the viewViewWillTransition available there.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
//Do something
}
}
P.S - This happened to me because the code for the parent was written by someone else and they forgot to call super.
I replaced
func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
by
func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
and it works for me.
This is how I fixed that:
Go to Project Target - General - Deployment Info
Tick Device Orientations the app supports
Also you need (change UIInterfaceOrientationMask.all for whatever is appropriate):
override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
get {
return UIInterfaceOrientationMask.all;
}
}
for ViewController.
If your ViewController is a child ViewController and you have added it’s view to a parent ViewController’s view, do the following:
In parent ViewController where you added
self.view.addSubview(childViewController.view)
Also add
self.addChild(childViewController)
Maybe you need to set Supported interface orientations in Info.plist:
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>

Prevent animation on rotation of one specific UIView

I have a full-screen UIView. When I rotate the phone the view shrinks a little (and you see the black background) and then expands again to be full-screen. It basically animates as expected.
The other views on the screen also animate accordingly.
Can I prevent this from happening for one specific view?
I would like the full-screen view to just stay full-screen without animation revealing the black background, but maintain that the other views animate their rotation.
Kind of like how Apple does it in the camera app. The "viewfinder" does not animate its rotation, but the buttons do.
I have the following code in my ViewController. liveView is the the full-screen UIView mentioned.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animateAlongsideTransition(in: self.liveView, animation: { (context) in
if let connection = (self.liveView.layer as! AVCaptureVideoPreviewLayer).connection {
connection.videoOrientation = AVCaptureVideoOrientation(ui: UIApplication.shared.statusBarOrientation)
}
}, completion: nil)
}
AVCaptureVideoOrentation is an extension which basically translates UIInterfaceOrientation to AVCaptureVideoOrientation with a switch statement.
Thanks
- Joseph
Did you try self.liveView.layer.removeAllAnimations():
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animateAlongsideTransition(in: self.liveView, animation: { (context) in
if let connection = (self.liveView.layer as! AVCaptureVideoPreviewLayer).connection {
connection.videoOrientation = AVCaptureVideoOrientation(ui: UIApplication.shared.statusBarOrientation)
}
self.liveView.layer.removeAllAnimations()
}, completion: nil)
}

Resize UICollectionView after view transition

I've built detail view in Interface Builder showing informations and photos about some object. Because lenght of informations and number of photos will vary, all is nested in UIScrollView.
Photos are shown in UICollectionView, but I need to always show all contained photos, so I disabled scrolling and dynamically changing Height constraint of UICollectionView by this function (called when finishing rendering cells):
func resizePhotosCollectionView() {
photosCollectionViewHeightConstraint.constant = photosCollectionView.collectionViewLayout.collectionViewContentSize()
}
It works great until this UICollectionView needs resize (typically by device orientation change). I am trying to use UIViewControllerTransitionCoordinator in function:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil) { context in
self.resizePhotosCollectionView()
}
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}
but result is jerky because Height constraint changed after transition is complete.
Is there any way how to automatically resize UICollectionView after view transition? If not, how to animate Height constraint change during transition?
Using Xcode 6.1 for target IOS 8.
If I recall correctly, you need to layout the view immediately.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil) { context in
self.resizePhotosCollectionView()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}
Have you tried wrapping your constraint update into a view animation?
photosCollectionViewHeightConstraint.constant =
photosCollectionView.collectionViewLayout.collectionViewContentSize()
photosCollectionView.setNeedsUpdateConstraints()
UIView.animateWithDuration(0.3, animations: { () -> Void in
photosCollectionView.layoutIfNeeded()
})

Resources