I'm working on an adopting multitasking to support split view for app, but I find the traitCollectionDidChange not called when app is on the right.
Does anyone have idea about this?
You may override traitCollectionDidChange in your ViewController.
However.
traitCollectionDidChange called when you change from one split view mode to another. For example from 50/50 view to 33/66. It so NOT called when you enter multitasking mode or exit it.
If you need to handle all events including entering and exiting multitasking mode, use viewWillTransitionToSize:withTransitionCoordinator: instead:
// put this in your ViewController code
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
// TODO: put your code here (runs BEFORE transition complete)
}
If you want your code called AFTER the transition compelete:
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// TODO: put your code here (runs AFTER transition complete)
}];
}
Have you tried the viewWillTransitionToSize method? This is used to notify the container that the size of its view is about to change.
Objective-C
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
Swift
func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil, completion: {
if UIDevice.currentDevice().orientation.isLandscape.boolValue {
print("landscape")
} else {
print("portrait")
}
}
If anyone still doubt that is the point:
// This method called every time user changes separator position or when user rotates device
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
// Always call super in those methods
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
// Before this call your layout is old, status bar orientation will return value before rotation
[coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
// Code here will be executed during transform. Status bar orientation is new, your view size already changed (in general).
// Setup required animations or custom views transitions
}
completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// Split view transition complete or device rotated
}];
}
Also there is a method traitCollectionDidChange:, but it will be called only when horizontal size class actually changed. For example, if your app presenting from right side in split view mode, traitCollectionDidChange: will not be called when user changed separator position. But if your app presenting from left, it will be called always in portrait mode, and in landscape for transitions (50/50) <-> (66/33)
In view controller, we should call
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
to get notified when the orientation/multi window view changes.
As per documentation of UITraitEnvironment:
The system calls this method when the iOS interface environment changes. Implement this method in view controllers and views, according to your app’s needs, to respond to such changes. For example, you might adjust the layout of the subviews of a view controller when an iPhone is rotated from portrait to landscape orientation. The default implementation of this method is empty.
At the beginning of your implementation, call super to ensure that interface elements higher in the view hierarchy have an opportunity to adjust their layout first.
Related
I am implementing the new feature in iOS 9 for new iPads of having multitasking of 2 apps with split screen.
I can't seem to find a completion method of when the split screen adjustment has finished after user has dragged the app view size handle.
How exactly do I notify my app that my app is being run in a split screen?
NOTE: This is different from UISplitViewController. I am referring to the multitasking of 2 apps in split screen, not UISplitViewController
There are multiple ways of responding to changes to the Split Screen view configuration. Depending on how you want to respond, you may have to use different approaches.
First of all, preferably use Autolayout and Storyboard constraints that allow your views to naturally resize appropriately to most, or all, view size changes.
In some situations, you may need to make layout changes when the Split Screen changes significantly and moves your available screen space from full screen, or almost full screen, to what iOS calls a Compact presentation. If the view handle is dragged enough to cross this boundary, you can get a callback via this view controller method:
Obj-C
- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
NSLog(#"split screen area now compact!");
} else if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) {
NSLog(#"split screen area now regular!");
}
}
Swift:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if self.traitCollection.horizontalSizeClass == .compact {
print("split screen area now compact!")
}
}
These methods are called when changing from Compact to Regular size, or vice versa. Not for every adjustment to the Split Screen view handle. In other situations, different code is needed.
As is the case with handling rotations programmatically, you may want to implement this method, which will provide you the final view size:
viewWillTransitionToSize:withTransitionCoordinator:
This method will get called even if the change is not large enough to cross the Regular-Compact boundary. Inspect the size parameter passed into your method, and see the example, which shows where you can place code that is called before, during, and after the transition.
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
// Code here will execute before the rotation begins.
// Equivalent to placing it in the deprecated method -[willRotateToInterfaceOrientation:duration:]
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Place code here to perform animations during the rotation.
// You can pass nil or leave this block empty if not necessary.
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Code here will execute after the rotation has finished.
// Equivalent to placing it in the deprecated method -[didRotateFromInterfaceOrientation:]
}];
}
More Reading
I think it's also useful to make use of trait variations and size classes in your storyboards, although that's not a specific answer to this question.
When the user alters the split screen sizes the root view will layout it's subviews for the new size, so you can implement viewDidlayoutSubviews in your view controller.
-(void)viewDidLayoutSubviews
{
if(!CGSizeEqualToSize(self.lastFrameSize,self.view.frame.size))
{
NSLog(#"Size changed:(%f x %f)",self.view.frame.size.width, self.view.frame.size.height);
}
self.lastFrameSize=self.view.frame.size;
}
I'm having a problem adjusting a parent view's layout when orientation changes for it's child view. I have a collection view controller that, when one of the cells are tapped, pushes a child view on top. If an orientation change occurs while the child view is visible and it is dismissed, the parent view's collection view cells haven't adjusted for the new width.
I should note that this works fine if the parent view is visible.
The only thing that has fixed this for me is in the viewDidAppear method of the parent view controller invalidates the collection view layout, but for me it's too late as the user sees the animation of the collection view cells snap into place.
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView reloadData];
}
I would have preferred to use viewWillAppear, but that doesn't seem to do anything. It sounds like it can only adjust the cells when the parent view is visible.
Is there a way around this?
Referring to this answer, iOS does not send orientation change events to offscreen view controllers, making them an unreliable way to determine whether the view has been resized.
viewWillAppear: isn't working in your case because iOS doesn't resize the offscreen view controller's view until after it calls the method, so your invalidate and reload are being pulled off the wrong values.
I believe the iOS8+ viewWillTransitionToSize:withTransitionCoordinator: method fires even when offscreen, but I'm not positive. In my experience, the size it provides does not reflect the actual size of the view. What I personally like to hook into is viewWillLayoutSubviews, usually guarded with a width check:
#property (nonatomic) CGFloat lastWidth;
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
if (self.lastWidth != CGRectGetWidth(self.view.bounds)) {
self.lastWidth = CGRectGetWidth(self.view.bounds);
// Update your collection view here.
}
}
This way, whenever your view is going to resize (on display, inside an orientation change animation) you can update the size information.
try overriding -(void)viewWillLayoutSubviews: method in your parent view controller. In your case,it goes like this
-(void)viewWillLayoutSubviews {
[self.collectionView.collectionViewLayout invalidateLayout];[self.collectionView reloadData];
}
You could try invalidating your layout and reloading in the rotation handler methods.
For pre-iOS 8
willRotateToInterfaceOrientation:duration:
And for iOS 8+
viewWillTransitionToSize:withTransitionCoordinator:
I currently have a UiViewController called Page2
Now if the orientation of the Iphone is changed to landscape the following method is called which is well and good
-(void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
/*The orientation of the phone just changed*/
}
Now My issue is that if the user is already in landscape mode in ViewController page1 and goes to UIViewController Page2 the above method is not called. I know I can check the current orientation of the phone using the following code
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if(orientation == UIInterfaceOrientationLandscapeLeft)
{
//Its in landscape mode
}
However I do not know what parameters to pass for size to the method (that gets called automatically only when the orientation changes) I would like to call this method manually if I detect its in landscape mode. Any suggestions on how to obtain the current size to pass to the method below in which the width would be greater than the height (since its in landscape mode)
-(void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator ;
I tried doing this
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if(orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight)
{
[self viewWillTransitionToSize:self.view.frame.size withTransitionCoordinator:nil];
}
However the height was greater than the width indicating it was still in portrait mode ... What am i doing wrong ? How can I call this method manually and pass the current size in which width is greater than the height ?
Update:
I would like to give a little more background. I currently have two UIViewControllers PageOne and PageTwo. Now PageTwo has something like this in it for orientation control. The control flow goes from PageOne to PageTwo
//When device rotates
-(void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
DKTuner* sensitivityTuner;
if (size.width > size.height)
{ //Yes this is the landscape mode - Realign some controls to a different position.
.....
}
else
{
// this is portrait mode
.....
}
}
Now the logic of my code in PageTwo assumes that the user entered it in Portrait Mode. However in reality that will not always be true because the user could enter pageTwo while in LandscapeMode. In that case the controls dont readjust to landscape mode as the method of reorientation does not get called in pageTwo. My question is how can I call the reorientation method viewWillTransitionToSize manually and pass a size parameter in which the size width is greater than the height ?
Whatever you're doing in viewWillTransitionToSize:withTransitionCoordinator:, you should probably do in viewWillLayoutSubviews or viewDidLayoutSubviews instead. Those are sent both on a rotation and when the view controller's view first comes onto the screen, and UIKit updates the view's frame before calling viewWillLayoutSubviews.
On iPad, I have perfectly working UISplitViewController.
I can hide and show its primaryViewController, and splitViewController:willChangeToDisplayMode: is called in appropriate way.
But on iPhone, something is wrong.
I can show primaryViewController, but cannot hide it, because the primaryViewController appears in full screen size. It's so full that I can't touch the secondary view, in that way I can hide the primaryViewController on iPad.
splitViewController:willChangeToDisplayMode: is not called either.
I have a viewDidLoad below, in my custom UISplitViewController class.
// UISplitViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;
self.preferredPrimaryColumnWidthFraction = .1;
CGRect mainScreen = [[UIScreen mainScreen] bounds];
self.minimumPrimaryColumnWidth = 270;
self.maximumPrimaryColumnWidth = mainScreen.size.width - 5;
}
On iPhone, any of these property seems not to be working: preferredPrimaryColumnWidthFraction or minimum/maximumPrimaryColumnWidth
I am adding this splitViewController as rootViewController in AppDelegate.m by the code below.
// AppDelegate.m
[_splitViewCon addChildViewController: tagNaviCon];
[_splitViewCon addChildViewController: mainNaviCon];
self.window.rootViewController = _splitViewCon;
I searched web and found some keywords like "container view".
Is this something I have to do with, when I want to use a UISplitViewController on iPhone ?
I also watched WWDC Video, but I didn't understand "how to code it exactly".
Currently, I do not use any Interface Builder. So I'd be rather glad if someone gives programmaticaly way to code it.
Thank you !
You can have side-by-side UISplitViewController on iPhones 4S, 5, 5S and 6 as well.
To do it you have to embed its view in another view controller (addChildViewController:...didMoveToParentViewController:)
After that you'll be able to control split's behaviour by overriding its trait collection (setOverrideTraitCollection:forChildViewController:). Basically here you have to inspect your current trait collection and change the horizontal size class to regular. This way the UISplitController will be able to show both master and detail views (primary and secondary now called) by setting split's preferredDisplayMode
Then on rotation you can make the same observations about your trait collection and change the preferredDisplayMode and override again if necessary the split's trait collection. This can be done in viewWillTransitionToSize:withTransitionCoordinator: or willTransitionToTraitCollection:withTransitionCoordinator:. The second one won't be called on an iPad as its size classes are alway regular on both orientations.
One note about a problem I'm still not able to resolve. On iPhone 5S for example when rotating in portrait I'm hiding the master controller so to have only one view on the screen and the UISplitViewController should adapt itself to a UINavigationController. That works fine however during the rotation animation the master view is disappearing and you can see a blank ugly space.
One other note as well.
You have to implement UISplitViewControllerDelegate and use methods in order to set which view controller should be visible on app launch and when split is used as a navigation.
Here is a thread about this.
Hope it helps and if I find solution about the problem I have I'll update my answer
The #user1006806 answer worked for me. Here's how I got rid of the ugly blank space during the rotation from within my UISplitViewController's rotation method (iOS 8):
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
UIInterfaceOrientation theOrientation = [[UIApplication sharedApplication] statusBarOrientation];
if (UIInterfaceOrientationIsPortrait(theOrientation)) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
} else {
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
}
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
}];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
I have been developing custom keyboard extension and I need to update few constraints programatically after device rotates. I have been trying to detect user interface rotation in my UIInputViewController subclass but without success. These methods are not called when device rotates:
-(void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>) coordinator {
NSLog(#"Orientation changed");
}
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
NSLog(#"Orientation changed");
}
I've also tried to observe UIDeviceOrientationDidChangeNotification but it doesn't work either.
Does anyone know how to detect rotation in UIInputViewController?
You're correct - those methods are not called in the UIInputViewController subclass for Keyboard Extensions (not sure about other extension types).
You need to override viewDidLayoutSubviews.
For example, if you wanted to update the layout of a UICollectionView subview of your keyboard when the device rotates, you'd do something like this:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self.keyboard.collectionView performBatchUpdates:nil completion:nil];
}
In your case you can check the orientation of the device in viewDidLayoutSubviews, and then apply/modify constraints appropriately.
Swift 4.2
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
let screen = UIScreen.main.bounds
if screen.width < screen.height {
print("!!! portrait")
} else {
print("!!! landspace")
}
}
It looks like Apple didn't provide this API method to the UIInputViewController.
You can infer the device orientation if the device width is greater than device height in viewWillAppear