I am using this piece of code to detect a change in orientation and switch views in consequence:
-(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
[self performSegueWithIdentifier:#"PORTADA-PORTADAH" sender:self];
}
else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
[self performSegueWithIdentifier:#"PORTADA-PORTADAH" sender:self];
}
else if (toInterfaceOrientation == UIInterfaceOrientationPortrait) {
nil;
}
else if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
nil;
}
}
It works fine, but when the device is flat on a table, it doesn't work at all. It is just a question of lifting the device (an ipad) like 15 or 20 degrees, but my employer is not happy with this.
Any thoughts please?
It will not call any of the 4 orientations when device is parallel with ground. or flat on table. It will call face up and face down orientations. by using previous orientations we need manage the origination using latest orientations of face up and face down.
Interface orientation in iOS is changed only when the gyroscope detects a difference in the gravity vector. When the device is laid flat, even if you rotate it around the y axis, there is no way to know where the user is in relation to the device. When the gravity vector changes, it is assumed that the user is parallel with that vector so it's easy to guess.
TL;DR - not really possible.
Related
I am trying to identify iPhones with the notch programmatically. I am aware of the screen.size method, but when you have a universal app supporting all interface orientations, it becomes a mess (counting all the possible variations). So I am looking for a simpler and more elegant way to detect the newer iPhones X models.
I have stumbled upon a method online that is supposed to work. In it, you measure the bottom inset of the safe area and if it is not zero, you have an iPhone X model. This makes sense in theory since, the safe area does not reach the bottom of the screen on the iPhone X handsets but does on all other devices. I do the check this way:
if (#available( iOS 11.0, * )) {
if ([[[UIApplication sharedApplication] keyWindow] safeAreaInsets].bottom > 0) {
// iPhone with notch
}
} else {
// Regular iPhone
}
This, however, does not work. Can someone point out my mistake in the implementation or confirm if this method is even viable?
I had to dig around quite a bit but found the answer (I'd like to shout out to user6788419 whose right answer was berried deep in another thread).
First of all, the above code is correct. Checking if the safe area's bottom inset is larger than zero will accurately identify iPhones with a notch (as of this writing). So, this is correct:
if (#available( iOS 11.0, * )) {
if ([[[UIApplication sharedApplication] keyWindow] safeAreaInsets].bottom > 0) {
// iPhone with notch
}
}
However, it matters where you put the above statement in your code because UIWindow is not available until the first run loop has concluded. That means, if you check the notch in your viewDidLoad or before it concluded (in your init for example), the bottom inset will always be zero.
If you, similarly to me, need this check to setup your main view, you can just move all your setup into separate function (such as postViewDidLoad) and call it once viewDidLoad has concluded:
[self performSelector:#selector(postViewDidLoad) withObject:nil afterDelay:0.0f];
Or, alternatively, you enclose it:
dispatch_async(dispatch_get_main_queue(), ^{
// Check notch here
});
UPDATE: code for iOS 13 and up (does the same thing). "areaPosition" is a string argument when you call the function. "top" checks for the notch, everything else checks for the presence of the bottom home indicator.
- (UIWindow *) keyWindow {
UIWindow *foundWindow = nil;
NSArray *windows = [[UIApplication sharedApplication]windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
foundWindow = window;
break;
}
}
return foundWindow;
}
- (BOOL) checkSafeArea:(NSString *)areaPosition {
if (#available(iOS 13.0, *)) {
if ([areaPosition isEqualToString:#"top"]) {
return [self keyWindow].safeAreaInsets.top > 20.0f;
} else {
return [self keyWindow].safeAreaInsets.bottom > 0.0f;
}
} else {
if ([areaPosition isEqualToString:#"top"]) {
return [[[UIApplication sharedApplication] delegate] window].safeAreaInsets.top > 20.0f;
} else {
return [[[UIApplication sharedApplication] delegate] window].safeAreaInsets.bottom > 0.0f;
}
}
return NO;
}
Updated for Swift 5 and iOS14
This solves the keyWindow deprecation warning.
var hasNotch: Bool {
let bottom = UIApplication.shared.windows.first{ $0.isKeyWindow }?.safeAreaInsets.bottom ?? 0
return bottom > 0
}
Swift 5 and (iOS12 or Above)
var isNotch: Bool {
return (UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0) > 0
}
I'm developing a universal app.
When my device is ipad I have 2 designs: Portrait and landscape.
I use this method: -(void)orientationDidChanged:(NSNotification *)notification
It works perfectly when I rotated my device. But I have a problem, when first load the view and I have not rotated the device.
For that reason, I put this code in the viewdidload:
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone){ //
something.
}
else if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
NSLog(#"IPAD ** ");
if (UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation)){
NSLog(#" vertical ** ");
}
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)){
NSLog(#"Horizontal ** ");
}
}
My problems is this: sometimes it works , sometimes not :(. This method: orientationDidChanged, when start to work? only if I rotate my device or immediately with the viewDidLoad , is necessary ask one more time the orientation when I have that method? Thanks for some advice.
I don't have problem when I rotated my device, it works perfectly :D .. My problem is when I recently run the application and I haven't turned the device
You can use this:
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if(orientation == 0) //Default orientation
//UI is in Default orientation (Portrait)
else if(orientation == UIInterfaceOrientationPortrait)
//UI is in Portrait orientation
else if(orientation == UIInterfaceOrientationLandscapeLeft)
//UI is in Landscape-left orientation
else if(orientation == UIInterfaceOrientationLandscapeRight)
//UI is in Landscape-right orientation
I have an app that has some buttons and a text view and a label. I want to rotate the device and have them redrawn on the screen to fit. I want to do it programatically. Currently I have these methods:
-(void)willRotateToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:toInterfaceOrientation
duration:duration];
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight ||
toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
[self reOrderSideways];
} else {
[self reOrderUpDown];
}
}
-(void)reOrderSideways{
self.viewLabel.frame=CGRectMake(175.0,140.0,130.0,20.0);
self.buttonOne.frame=CGRectMake(20.0,20.0,440.0,100.0);
self.buttonTwo.frame=CGRectMake(20.0,180.0,440.0,100.0);
self.textContent.frame=CGRectMake(20.0, 290.0, 440.0, 100.0);
self.theScroller.contentSize = CGSizeMake(480.0, 350.0);
}
-(void)reOrderUpDown{
self.viewLabel.frame=CGRectMake(95.0,15.0,130.0,20.0);
self.buttonOne.frame=CGRectMake(20.0,50.0,280.0,190.0);
self.buttonTwo.frame=CGRectMake(20.0,250.0,280.0,190.0);
self.textContent.frame=CGRectMake(20.0, 450.0, 280.0, 190.0);
self.theScroller.contentSize = CGSizeMake(320.0, 460.0);
}
It doesn't quite work because when i rotate sideways, the buttons and labels and textview get cutoff the right side. I checked it and it looks like its using the coordinates I gave it but it is still using the portrait frame or bounds. How can i fix this?
Try using willAnimateRotationToInterfaceOrientation: instead.
Currently I'm workign on a drawing app for the iPad. I need to reposition and rotate the toolbar in the app when it is put into a different orientation while keeping the the drawing area in the same place.
I found a method here for doing this. It uses the NSNotificationCenter to monitor for rotation changes. This calls a custom didRotate: method that will rotate and reposition my toolbar based on the UIDeviceOrientation.
This part works fine. However, whenever the side switch on the iPad is engaged to lock the orientation, the toolbar repositions to the location is was at launch.
For example: If I start the application in landscape left and rotate it to portrait, the toolbar will reposition to the bottom of the screen. However as soon as I engage the slide switch, it moves to the side of the screen for the landscape left orientation.
The methods I'm using for this are all below.
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
- (void)didRotate:(NSNotification *)notification {
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
UIInterfaceOrientation interfaceOrientation;
bool orientationFound = YES;
if (deviceOrientation == UIDeviceOrientationPortrait) {
interfaceOrientation = UIInterfaceOrientationPortrait;
} else if (deviceOrientation == UIDeviceOrientationPortraitUpsideDown) {
interfaceOrientation = UIInterfaceOrientationPortraitUpsideDown;
} else if (deviceOrientation == UIDeviceOrientationLandscapeLeft) {
interfaceOrientation = UIInterfaceOrientationLandscapeRight;
} else if (deviceOrientation == UIDeviceOrientationLandscapeRight) {
interfaceOrientation = UIInterfaceOrientationLandscapeLeft;
} else {
orientationFound = NO;
}
if (orientationFound) {
[self.toolbar changeToOrientation:interfaceOrientation withDuration:.25];
[self.tutorialOverlay changeToOrientation:interfaceOrientation];
}
}
- (void)changeToOrientation:(UIInterfaceOrientation)orientation withDuration:(float)duration {
float angle;
CGPoint origin;
if (orientation == UIInterfaceOrientationPortrait) {
angle = portraitAngle;
origin = self.portraitOrigin;
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
angle = portraitUpsideDownAngle;
origin = self.portraitUpsideDownOrigin;
} else if (orientation == UIInterfaceOrientationLandscapeLeft) {
angle = landscapeLeftAngle;
origin = self.landscapeLeftOrigin;
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
angle = landscapeRightAngle;
origin = self.landscapeRightOrigin;
}
[UIView animateWithDuration:duration animations:^{
self.transform = CGAffineTransformMakeRotation(angle);
CGRect rect = self.frame;
rect.origin = origin;
self.frame = rect;
}];
}
I'd strongly recommend against using this notification-based approach. Note that the name of the notification is UIDeviceOrientationDidChangeNotification; the device orientation is not the same as the interface orientation, and it leads to lots of little issues like this. (The device orientation, for example, includes UIDeviceOrientationFaceDown, which is never associated with an interface orientation.)
I'd suggest letting your view controller automatically rotate itself; this will place the toolbar, etc, for you. You can then override - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration to put the drawing area back to the orientation you want to maintain. (You'd use similar code to your changeToOrientation method above, but for the drawing area instead, and you don't need to create your own animation block. Also, the angles would all be negated, because you're undoing the change the view controller made.)
I just answered a similar question here.
Basically just allow the interface to rotate, but rotate the view you don't want to rotate 'back' in willRotateToInterfaceOrientation:duration:.
I'm developing this application on the iPad. My MainWindow contains a Split View Controller which loads the RootViewController and DetailViewController.
I have this image that is placed at the bottom-right of the DetailViewController.
My application allows different orientations, therefore i want the image to appear at different positions for different orientations.
This is my code in DetailViewController:
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
myImage.frame = CGRectMake(183.0f, 257.0f, 548.0f, 447.0f);
}
else if(toInterfaceOrientation == UIInterfaceOrientationPortrait || toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
myImage.frame = CGRectMake(244.0f, 518.0f, 548.0f, 447.0f);
}
}
When i launch my application and my iPad is at portrait orientation, the image will be correctly placed at (X:244, Y:518) which is at the bottom right.
But when i launch my application and my iPad is at landscape orientation, the image will not be shown and i suspect that it is still at the position for portrait orientation. Only when i rotate my iPad to portrait orientation and then back to landscape orientation, then the image will appear at (X:183, Y:257) which is correct.
What i tried to do is to place these codes in my DetailViewController's 'viewWillAppear' method:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft || self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
myImage.frame = CGRectMake(183.0f, 257.0f, 548.0f, 447.0f);
}
else if(self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
myImage.frame = CGRectMake(244.0f, 518.0f, 548.0f, 447.0f);
}
}
But when i launch my application, it still has the same problem.
What can i do so that when i first launch my application in landscape orientation, it will be correctly placed at (X:244, Y:518) ?
You might want to try putting your resizing logic in didRotateFromInterfaceOrientation instead of willRotateToInterfaceOrientation.
My custom layout code only worked when I called it after the rotation had completed it, rather than before.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
NSLog(#"didRotateFromInterfaceOrientation");
float width;
float height;
if ((fromInterfaceOrientation == UIInterfaceOrientationLandscapeRight) ||
(fromInterfaceOrientation == UIInterfaceOrientationLandscapeLeft))
{
width = [[UIScreen mainScreen] applicationFrame].size.width;
height = [[UIScreen mainScreen] applicationFrame].size.height;
NSLog(#"Changing to portrait: %f x %f", width, height);
}
else
{
width = [[UIScreen mainScreen] applicationFrame].size.height;
height = [[UIScreen mainScreen] applicationFrame].size.width;
NSLog(#"Changing to landscape: %f x %f", width, height);
}
NSLog(#"Changed orientation: %f x %f", width, height);
// Call my custom layout function to setFrame on all my UI controls
[self doLayout:width height:height];
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}
If your app shows the status bar then you probably need to call self.view.frame.size.width instead of getting the size from the applicationFrame.
Make sure that you're overriding
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation; // Override to allow rotation. Default returns YES only for UIInterfaceOrientationPortrait
To return YES for each UIInterfaceOrentation that you want to support, otherwise willRotateToInterfaceOrientation and didRotateToInterfaceOrientation wont get called.
Try calling this early in your applicationDidFinishLauching:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
Since the detailViewController wouldn't sometimes know its orientation, I solved this issue by having a shared variable in the appDelegate that would store the device orientation throughout the app. You can initialize it in the splitViewController with its orientation so when the detailViewController loads it can read it from there.