viewWillTransitionToSize: vs willTransitionToTraitCollection: - ios

Can anyone explain the differences between these 2 methods? The docs for UIViewController explicitly state that viewWillTransitionToSize should be used for managing rotations, but clicking through to the UIContentContainer page, the willTransitionToTraitCollection method makes a confusing entrance.
I think I understand the conceptual difference between a size class change ( trait collection change ) and a size change, but I'm not sure which method to implement in which circumstances. Clarification from a UIKit wizard would be helpful!

Whenever you want to do something aa a response to a user rotating their device you should use viewWillTransitionToSize, if you do that you know for sure that your actions are executed as this is called every time your app's window changes size.
If you only want to take action when the trait collection changes, for instance if you have a certain collectionViewLayout set for a Compact size class and another you want to use for Regular you use willTransitionToTraitCollection.
If the trait collection changes then the size also changes. But it doesn't work the other way around. A portrait iPad and a landscape iPad have the same traits but are different sizes. Add multitasking to the mix and you have a whole variety of sizes that would map to just two traitCollection size classes.

Related

Obtaining safeAreaInsets for interface orientations?

I am using safeAreaInsets to respect iPhone X's notch and reserved menu areas.
This works great for when loading the view since the view is in the current interface orientation.
However, I need to know what the future values will be to handle animation in my willRotate method. UIViewController has a viewSafeAreaInsetsDidChange method, but that isn't updated until after the device has rotated. I need something like viewSafeAreaInsetsWillChange, but of course does not exist.
So, it seems that the only way to handle the animations in my willRotate method is by hard coding the safe area values, which is certainly far from ideal.
Is there anyway to obtain safe areas for a given orientation (portrait or landscape)?

Xcode traits views not nil

As known different views can be added for each screen unit class(Regular, Compact) By picking the desired unit classing then clicking the Vary for Traits button.
Then, connect the views to your controller twice using outlets. one for general and one for that unit class.
In viewDidLoad I printed the values of both views and noticed that both
of them are not nil.
Shouldn't I have one of the views equal to nil since this is not its unit class? How does view traits realy work?
All views (and constraints) are instantiated and referenced with their corresponding outlets when your storyboard scene (or nib) is loaded — no matter what size class they are installed on.
From Apple's Auto Layout Guide:
When the system loads a scene, it instantiates all the views, controls, and constraints, and assigns these items to the appropriate outlet in the view controller (if any). You can access any of these items through their outlets, regardless of the scene’s current size class. However, the system adds these items to the view hierarchy only if they are installed for the current size class.
This is because your view's size class does not only vary depending on the device, it may also change when you rotate your device or do multitasking on an iPad. When you change your layout as a response to a user action (like a tap) you really wanna make sure that you update the layouts for all your size classes.
Example:
Say you have a simple "register view" with a username text field where users can claim their unique username. You want to give the user immediate visual feedback if the username he typed in is already taken or not.
You decide that on a compact size class (iPhone in portrait mode) there is just enough space to show a litte red cross (❌) on the right edge of the text field. On the iPhone 6/7 Plus in landscape mode however, there is enough space to show the user a little more information and so you choose to show a label "Username taken, please try another one!" next to the red cross on the regular size class.
Now every time the user has entered a new character in the text field you can validate the input and then update the UI like this:
func updateUI(isUsernameAvailable: Bool) {
redCross.hidden = isUsernameAvailable
usernameTakenLabel.hidden = isUsernameAvailable
}
At this point you can be sure that the UI is up-to-date no matter how often the user rotates the devices. If the usernameTakenLabel was not instantiated here you would have to somehow remember that the label should be hidden/visible once it shows up with an extra instance variable and then use that variable to actually hide/reveal the label once it is instantiated i.e. when the size class width changes which bloats your view controller and is pretty error prone.

Size class initially unknown

I am trying to setup my UI on my universal app. I have a storyboard setup with size classes, a fairly simple UI. I have my view controller with a view in. Inside this view, I draw a chart so this can only be updated using setFrame.
This is where my problem begins. I set my graph to be the screen width. However, when the view initially runs, the size class seems to be unknown.
As the default 'Any' size in my storyboard is 600x600. My view thinks it should draw 600 wide on my iPhone, which clearly isn't this wide.
It is only after I physically move the iPhone to toggle an orientation change, that it updates and recognises the correct size.
So my question is, how do I prevent this problem? I need my UI to know what size to be from the get go, not just after the user rotates their iPhone.
However, when the view initially runs, the size class seems to be unknown.
It is unknown to the view and the view controller, because at that time the view controller is not yet part of the interface and has no environment. But it is not unknown to UIScreen.mainScreen(). So if you need this information very early, that is who to ask.
However, as you've been advised in a comment, it also sounds like you may simply be doing this too early. Nothing in a view controller's view, including the view itself, has achieved its actual size until viewDidLoad or later.
There are two to prevent this problem
(1) Load your entire method in
-(void)viewDidAppear:(BOOL)animated
{
}
(2) Do the following step
Go to file Inspector
Uncheck "Use size classed

Who should manage the UI Elements, the view or the view controller?

I am currently developing an iOS app and I was wondering how to manage the UI elements. I am using a Storyboard to place my views and Autolayout to make everything resolution independent. If it helps, here's a bit of background on how I came up with this question.
Some Backgroud
I have buttons the represent piano keys and I later in code add a subview to these that draws the actual keys on them. I did this by creating #IBOutlets on the ViewController and susbscribed to the UIDeviceOrientationDidChangeNotification. This called a method that adds the subviews based on the current button frames to get the appropriate sized keys.
This worked fine when running in iOS 8.1, but if I ran the app on iOS 7, the frames of the buttons weren't updated to the new orientation by the time I got the UIDeviceOrientationDidChangeNotification, so everything was messed up in landscape. I did a little research (UIInterfaceOrientation not yet updated when UIDeviceOrientationDidChangeNotification caught in UIView) and it seems that a good solution would be to override the layoutSubviews method. Problem is, layoutSubviewsis a UIView method, and I am managing my buttons and UI in my UIViewController.
Impulsively I wanted to subclass my UIView, override layoutSubviews and from there call a method on my Controller to add the keys to the buttons correctly, but that doesn't sound right.
The Questions
My UI is managed by the UIViewController. But I need to update my views based on a method proper of a UIView. I assume, to respect the MVC principles, that my view should't now a thing about my controller, but then this brings up a few questions:
Who should be managing the UI?
Is my strategy wrong and should the
UIView hold the #IBOutlets to the buttons so that it can later
apply the subviews I need them to have?
If you read the background, do you have a suggestion for this particular situation?
I assume the complexity of this problem is relatively small and I don't need to setup notifications in NSNotificationCenter, but I may be wrong. I would really like to hear this is not the only solution.
Thanks in advance. I hope I was clear enough, but if you want/need any additional details on my particular situation, let me know and I will gladly elaborate.
if I understood the question correctly, your ViewController should be laying out the keys within it's view. A good, but not the only, place to do this is in viewDidLayoutSubviews which a method you can override on UIViewController.
Now if your views should maintain their internal layout. i.e if you buttons/keys have any subviews, you should update those in layoutSubviews on UIView as you mentioned.
Both methods will be called in response to changes in the bounds or center of your parent view.
As far as rotation changes are concerned, in iOS 8, you should use viewWillTransitionToSize:withTransitionCoordinator: or willTransitionToTraitCollection:withTransitionCoordinator:
depending on your needs.
I highly recommend that you write your layout independent of orientation. You should just use the containing view's bounds for reference and never hard code in any frames. For example perhaps one of your keys should be 1/56 the width of it's superview instead of a magical number for portrait and landscape.

Using size classes programmatically

I (hopefully) watched all the relevant WWDC2014 session videos and read the docs, so this question is mostly to confirm my suspicions, but please educate me.
What I want to do is animate views using Auto Layout. That in itself is not a problem. But these animations' endpoints change with different orientations. I thought I might be able to use size classes to move the views automatically on rotation, but Apple's developer guide says that animations have to be done programmatically, and from what I can gather, size classes are an Interface-Builder-only thing.
Another idea I had was using custom layout guides like the top/bottom ones IB provides, but those seem to be hardcoded.
The last thing I could do is update constraints by hand after listening to rotation events, but that is nothing new, and I feel like size classes should be useable for more than just static interfaces. Am I overestimating their purpose?
TLDR: Given two points A and B that a view can have its origin at (due to animations), how can I move both points using size classes or something similar?
After some more digging in the docs I have finally found something useable. The UIContentContainer protocol defines willTransitionToTraitCollection(:withTransitionCoordinator:), and that method's first parameter (a UITraitCollection) contains horizontal and vertical size classes as well as a UIUserInterfaceIdiom (that can be used to know whether the app is running on a iPhone or iPad, although size classes should be used for most things).
Additionally, since iOS 8 hides the status bar in landscape view, traitCollectionDidChange(previousTraitCollection:) is the corresponding method that gets called after the change happened, so the value of UIApplication.sharedApplication().statusBarHidden has changed when this method is called. Can be useful for UIScrollView's contentInset for example.
Lastly, if you need the exact screen sizes (in points, of course, but the above mentioned trait collection also knows about pixel density), there is viewWillTransitionToSize(:withTransitionCoordinator:).
Hope this helps someone else as well.

Resources