I have built an app that uses no interface builder using Snapkit to create my Auto-Layout constraints. Everything looks fine in portrait, however a few screen's need some landscape specific constraints.
I've searched here and Google in general for a quick intro on doing this, but couldn't really find anything that was applicable (everything I found was based on using IB or used size classes instead of orientation - I specifically want landscape, not compact vs regular).
So, all my auto-layout constraints are set up in viewDidLoad at the moment. No doubt at very least, the ones that will be orientation dependant need moving to some kind of delegate/callback method on UIViewController, but I don't know know what that is...
How do I detect an orientation change in order to change my constraints?
How do I get the current orientation (so when I first load the view controller I can set the right constraints... Or is the function from my above question always called at least once for each VC on load?)
Outside of a ViewController, such as custom UIView's, how do I detect the orientation change? Should i send out a custom notification event? I'd rather not have my UIViewController tell every subview it has that orientation has changed.
Thanks for any help :)
I would personally use this function:
override func viewWillTransitionToSize(size: CGSize,
withTransitionCoordinator coordinator:
UIViewControllerTransitionCoordinator) {}
And then when those changes are detected call setNeedsLayout and layoutIfNeeded which should trigger a redraw of all of your subviews which can then handle setting constraints for specific orientations
Related
I was googling for a while and I found similar problems but when the custom View is inside a ScrollView, but that is not my case.
I have a custom view that consists of a UILabel behind a UITextField, so I can animate that label later.
The problem is that when I add a View in my ViewController and in the Identity Inspector I set the Class as my custom class, when I use the application the UITextField within my custom view does not receive the touches well and it takes time to gain focus and therefore to open the keyboard. The strange thing is that if I move that same arrangement of views to my main ViewController in Storyboard everything works fine. Why doesn't it do it when I place it using the described method?
I plans to reuse this custom view a lot, so putting logic and views in each ViewController is not an option.
Thanks in advance
Well, the problem was in the constraints of the container UIView. That means, the UIView in my main ViewController. The Height of the UIView was a little bit smaller than the space required for my custom view, so although my custom view seemed to draw correctly, it was not receiving the gestures correctly. The solution was simply increase the height to the correct value occupied by my custom View. Thanks a lot!
I have view that have many subviews, and each have it's own graphics, different for portrait and landscape orientations. When should I load the new graphics when the orientation changes?
If shouldn't be layoutSubviews, I don't also like the idea of reloading resources in view from UIViewController, because of complicated view hierarchy that I have (it would require to pass this information down).
There is also option to register in NSNotificationCenter for orientation changes in each view, and load new resources when it changes. But I don't quite like it either, because I believe that there should be mechanism in iOS that enables that. Or, maybe I should think in a different way, and build different views for portrait and landscape...
What do you suggest?
I would have your subviews all extend a custom view class that has a -setOrientation: method that swaps between the portrait and landscape graphics. Then in your view controller I would override -willRotateToInterfaceOrientation:duration: and add a loop that sets the orientation for all of the children, thereby causing the graphics to swap when the orientation changes.
I think that's the most straighforward way to do it. Using NSNotificationCenter could be tricky and you're left without a guarantee that every child will get the notification.
Anyway, that's how I'd do it. Let me know if you need examples.
The way my app currently deals with orientation is it repositions all of the items in the view, the layout of which can change as the user interacts with it. I've had numerous problems, such as view's not appearing, views changing before the screen rotates etc. I'm wondering the best way to deal with orientation?
If the landscape-layout is completely different from the portrait-layout I just load all subviews in the init-method of my UIView-subclass and added them as subviews.
The whole magic is done in the layoutSubviews-method where I only check in which orientation I am at that moment. Never call alloc, addSubview, removeFromSuperview, ... methods in layoutSubviews. The layoutSubviews should only contain code that sets the frame-properties of subviews.
Referring to your problems:
view not appearing: maybe forgot an addSubview-call
views changing before the screen rotates: you probably update some frame-properties of subviews outside the layoutSubviews-method
One possibility - if your app only works with one orientation, disallow orientation changes. This is a reasonable response, some apps are only usable in one view.
Someone else asked a similar question previously, but I am interested in knowing whether the autorotate feature of the iPhone SDK will allow us to replace the rotation animation with another transition, such as a fade-in/fade-out. For a modal view, we can set the modalTransitionStyle but there is no such property for autorotate.
If I can't leverage the built-in functionality, how else can I implement this functionality?
There's the willAnimateRotationToInterfaceOrientation:duration: method, from the docs:
The default implementation
of this method does nothing. If you
override this method, you should not
override either the
willAnimateFirstHalfOfRotationToInterfaceOrientation:duration:
or
willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:
method.
This method is called from within the
animation block that is used to rotate
the view. You can override this method
and use it to configure additional
animations that should occur during
the view rotation. For example, you
could use it to adjust the zoom level
of your content, change the scroller
position, or modify other animatable
properties of your view.
I guess you can modify it from here, and before and after with – willRotateToInterfaceOrientation:duration: and – didRotateFromInterfaceOrientation:.
This seems inefficient and sub-par to me, as it does not actually replace the transition at all.
I would make a category on UIView, that includes methods such as -willRotateUsingTransition: and pass a parameter that will tell it to fade, then set the view's alpha to zero. In each subclass override this to include any subviews that need their alpha changed, if that is applicable. Call this method when the views are about to be rotated (with the methods above) and then a clean-up method that restore the alphas when they will appear again.
Edit: Docs Docs Docs. A quick look, again, reveals this method: - (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration, which I thought to be deprecated. As part of the discussion it has the text from the second paragraph I posted from the docs earlier.
I achieved something similar by attaching the auto-orientation behaviour to a hidden view, and responding the relevant events by implementing my own custom effect. It was quite fiddly, and I don't remember all the details, but I did manage to exercise complete control over the behaviour.
I should say, though, that in my case, I wanted to have control over how the rotation was implemented. I wanted the primary layout to remain fixed, while rotating several subviews in concert. I can't think of a good reason to completely replace the rotation with a fade-out-then-in; it'll just confuse users for no good reason. The only reason I can think of for using a fade effect is that you want to swap to a substantially different UI, but even then, there's no reason to suppress the rotation effect.
What are the consequences of overriding internal UIViewController methods?
[UIViewController viewDidMoveToWindow:shouldAppearOrDisappear:] is giving me some problems. It resizes my frame to values I do not desire sometimes. I do not even know where it picks off the new frame's values (it is close to the size of the superview to where I am adding it, but is off by 2px). Reference: this question, which I also need some help with.
I tried defining an empty - (void)viewDidMoveToWindow:(UIWindow *)window shouldAppearOrDisappear:(BOOL)flag method in my view controller. Bug is gone. >.<
Does anyone know if overriding -viewDidMoveToWindow:shouldAppearOrDisappear: is ok to do? Or some other workaround?
Or, does anyone know when and why -viewDidMoveToWindow:shouldAppearOrDisappear: likes to change my frame dimensions sometimes?
Thanks in advance.
If you override that method you run the risk of getting rejected, it is undocumented. An approach that isn't against the rules is to override the setFrame: method of the view in the view controller though.
If you believe this is a bug, put together a minimal test case and submit it to http://bugreport.apple.com and potentially upload it somewhere that we can see exactly what is happening as well.
For the "why" part of your question; the frame can automatically change in two situations:
Your ViewController is part of a view controller hierarchy (Navigation or TabBar). In this case your parent ViewControllers do as they please with your views frame.
Your view allows autosizing and the parent view changes size. The thing to note here is that the size of the root view is changed to fit the screen, causing autosizing to happen for all subviews.
To find the exact culprit in your case you could set a breakpoint in the overridden setFrame:.