My code is very simple. I want to change the positioning of an SKSpriteNode to always remain in the bottom right corner of the device screen. I call my UIDeviceOrientationDidChangeNotification method "rotated" on device rotation and it should simply move the node, but for some reason it always flips the desired location so the landscape position is where the portrait should be and vice-versa.
My code is as follows:
UI
- (void)rotated:(NSNotification *)notification {
DeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !_isShowingLandscapeView)
[self.node setPosition:bottom_right];
_isShowingLandscapeView = YES;
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) && _isShowingLandscapeView)
{
[self.node setPosition:bottom_right];
_isShowingLandscapeView = NO;
}
}
Ok so what I'm doing is simple. Just use UIButtons over your SKScene. Go back to the UIViewController where you call your skscene from and add buttons. You'll have to use NSNotification center to pass information to the buttons, labels, etc.
http://www.raywenderlich.com/forums/viewtopic.php?f=2&t=8843
Related
In the UIViewController rotation method(s) I have a problem when performing the device rotation
inside of the method
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
I am getting incorrect values
According to the documentation:
Sent to the view controller just before the user interface begins
rotating. Subclasses may override this method to perform additional
actions immediately prior to the rotation. For example, you might use
this method to disable view interactions, stop media playback, or
temporarily turn off expensive drawing or live updates. You might also
use it to swap the current view for one that reflects the new
interface orientation. When this method is called, the
interfaceOrientation property still contains the view’s original
orientation. Your implementation of this method must call super at
some point during its execution. This method is called regardless of
whether your code performs one-step or two-step rotations.
I am holding the device strait up (right after launching the app the device is physically in portrait mode) - all the views are correctly aligned for portrait mode
But in the method I am getting wrong values:
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
// getting the wrong interface orientation here!!!!
// just checking the current orientation for debug
UIInterfaceOrientation currentOrientation = self.interfaceOrientation;
// the first time the device is rotated getting this value for current orientation:
// currentOrientation == UIInterfaceOrientationLandscapeRight
}
even worst than this strange case of wrong orientation is in this same method for 'will rotate to interface orientation' the current interface orientation and the destination interface orientation are identical i.e:
self.interfaceOrientation === toInterfaceOrientation
So whats the point of this method being called by the framework???
This results in all my UIView placement code not being calculating correctly on initial rotation of the device.
Why is this flag not set correctly?!?!
P.S
After rotating the device for the second time - the flags are set correctly and the views "align" themselves properly.
u will get correct value, suppose u are holding the device in port rite , then u turn it t landscape mode, then this method is called
//called first
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
//check which orientation hear
if(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
NSLog(#"turning t landscape");
}
else
{
NSLog(#"turning t portrite");
}
}
method called hear toInterfaceOrientation contains (for our example) UIInterfaceOrientationLandscapeRightorUIInterfaceOrientationLandscapeLeft`, this indicates that device will going to turn to landscape left or rite
after this, below methods called in order
- (void)viewWillLayoutSubviews
- (void)willAnimateRotationToInterfaceOrientation:duration:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
get called, hear in last method fromInterfaceOrientation contains (for our example) old value or previous orientation flag, that is UIInterfaceOrientationPortraitUpsideDown or UIInterfaceOrientationPortrait
u want t know the current orientation then u can use
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if(UIInterfaceOrientationIsLandscape(orientation))
{
NSLog(#"landscape");
}
else
{
NSLog(#"portrite");
}
for more information about this u can see this docs
My Storybuilder is designed with a portrait layout. When I start the app with my iPad already turned to horizontal, it's able to correctly detect it's in a horizontal position. But when I start the app with my iPad in a portrait position, it thinks it's in horizontal. However, every time I rotate it, the code is able to detect the correct orientation properly.
- (void) viewDidLoad
{
[self updateForOrientation];
}
- (void)updateForOrientation
{
if (UIInterfaceOrientationIsPortrait([[UIDevice currentDevice] orientation])) // became portrait
{
NSLog(#"is portrait");
//code for changing layout to portrait position
}
else //became horiztontal
{
NSLog(#"is horizontal");
//code for changing layout to horizontal position
}
}
Output: is horizontal (this is the output whether it starts up as portrait or landscape)
The problem is that you're sending the devices orientation in terms of the UIDeviceOrientation enum to a function that's expecting a UIInterfaceOrientation value.
If you command click on UIInterfaceOrientationIsPortrait(), you can see that it is defined as follows.
#define UIInterfaceOrientationIsPortrait(orientation) ((orientation) == UIInterfaceOrientationPortrait || (orientation) == UIInterfaceOrientationPortraitUpsideDown)
And if you look at the enum declarations for the two orientation types (documentation links below), you can see that there is a misalignment in value due to the device orientation containing a value for "none". Anyway, changing your code to use UIInterfaceOrientation should sort this out. Example:
- (void)updateForOrientation
{
UIInterfaceOrientation currentOrientation = self.interfaceOrientation;
if (UIInterfaceOrientationIsPortrait(currentOrientation)) {
NSLog(#"is portrait");
}else{
NSLog(#"is horizontal");
}
}
https://developer.apple.com/library/ios/documentation/uikit/reference/UIApplication_Class/Reference/Reference.html#//apple_ref/doc/c_ref/UIInterfaceOrientation
https://developer.apple.com/library/ios/documentation/uikit/reference/UIDevice_Class/Reference/UIDevice.html#//apple_ref/doc/c_ref/UIDeviceOrientation
I am writing this code to make some changes to how my app is laid out depending on if it is in landscape or portrait mode. I have set up the app to get a notification on the orientation changing like this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedRotate:) name:UIDeviceOrientationDidChangeNotification object:NULL];
And then defining receivedRotate:
-(void) receivedRotate: (NSNotification*) notification {
UIDeviceOrientation interfaceOrientation = [[UIDevice currentDevice] orientation];
// ... do something
}
However, UIDeviceOrientationDidChangeNotification is called whenever the device is moved slightly. E.g., the UIDeviceOrientation can be any of the following:
UIDeviceOrientationPortrait,
UIDeviceOrientationPortraitUpsideDown,
UIDeviceOrientationLandscapeLeft,
UIDeviceOrientationLandscapeRight,
UIDeviceOrientationFaceUp,
UIDeviceOrientationFaceDown
This is great but I just want to know if the device orientation is in portrait or landscape, and I also only want to run this code when the orientation actually changes - from portrait to landscape or vice-versa. If they turn the phone all the way around, it is still in landscape mode - just a different form of landscape mode. What's the ideal way to make sure that my rotation code only runs when it is necessary?
DeviceOrientation vs. ScreenSize vs StatusBar.isLandscape?
iOS 11, Swift 4 and Xcode 9.X
Regardless of using AutoLayout or not, there are several ways to get the right device orientation, and they could be used to detect rotation changes while using the app, as well as getting the right orientation at app launch or after resuming from background.
This solutions work fine in iOS 11 and Xcode 9.X
1. UIScreen.main.bounds.size:
If you only want to know if the app is in landscape or portrait mode, the best point to start is in viewDidLoad in the rootViewController at launch time and in viewWillTransition(toSize:) in the rootViewController if you want to detect rotation changes while the app is in background, and should resume the UI in the right orientation.
let size = UIScreen.main.bounds.size
if size.width < size.height {
print("Portrait: \(size.width) X \(size.height)")
} else {
print("Landscape: \(size.width) X \(size.height)")
}
This also happens early during the app/viewController life cycles.
2. NotificationCenter
If you need to get the actual device orientation (including faceDown, faceUp, etc). you want to add an observer as follows (even if you do it in the application:didFinishLaunchingWithOptions method in the AppDelegate, the first notifications will likely be triggered after the viewDidLoad is executed
device = UIDevice.current
device?.beginGeneratingDeviceOrientationNotifications()
notificationCenter = Notifi`enter code here`cationCenter.default
notificationCenter?.addObserver(self, selector: #selector(deviceOrientationChanged),
name: Notification.Name("UIDeviceOrientationDidChangeNotification"),
object: nil)
And add the selector as follows. I split it in 2 parts to be able to run inspectDeviceOrientation() in viewWillTransition(toSize:)
#objc func deviceOrientationChanged() {
print("Orientation changed")
inspectDeviceOrientation()
}
func inspectDeviceOrientation() {
let orientation = UIDevice.current.orientation
switch UIDevice.current.orientation {
case .portrait:
print("portrait")
case .landscapeLeft:
print("landscapeLeft")
case .landscapeRight:
print("landscapeRight")
case .portraitUpsideDown:
print("portraitUpsideDown")
case .faceUp:
print("faceUp")
case .faceDown:
print("faceDown")
default: // .unknown
print("unknown")
}
if orientation.isPortrait { print("isPortrait") }
if orientation.isLandscape { print("isLandscape") }
if orientation.isFlat { print("isFlat") }
}
Note that the UIDeviceOrientationDidChangeNotification may be posted several times during launch, and in some cases it may be .unknown. What I have seen is that the first correct orientation notification is received after the viewDidLoad and viewWillAppear methods, and right before viewDidAppear, or even applicationDidBecomeActive
The orientation object will give you all 7 possible scenarios(from the enum UIDeviceOrientation definition):
public enum UIDeviceOrientation : Int {
case unknown
case portrait // Device oriented vertically, home button on the bottom
case portraitUpsideDown // Device oriented vertically, home button on the top
case landscapeLeft // Device oriented horizontally, home button on the right
case landscapeRight // Device oriented horizontally, home button on the left
case faceUp // Device oriented flat, face up
case faceDown // Device oriented flat, face down
}
Interestingly, the isPortrait read-only Bool variable is defined in an extension to UIDeviceOrientation as follows:
extension UIDeviceOrientation {
public var isLandscape: Bool { get }
public var isPortrait: Bool { get }
public var isFlat: Bool { get }
public var isValidInterfaceOrientation: Bool { get }
}
3. StatusBarOrientation
UIApplication.shared.statusBarOrientation.isLandscape
This also works fine to determine if orientation is portrait or landscape orientation and gives the same results as point 1. You can evaluate it in viewDidLoad (for App launch) and in viewWillTransition(toSize:) if coming from Background. But it won't give you the details of top/bottom, left/right, up/down you get with the notifications (Point 2)
I believe what you're looking for is UIInterfaceOrientation, not UIDeviceOrientation. To use this, just implement the following function in your view controller.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
UIInterfaceOrientation currentOrientation = self.interfaceOrientation;
//
}
This enum is declared as the following
typedef enum : NSInteger {
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} UIInterfaceOrientation;
Save the previous orientation in a property and compare it with current one.
#0x7ffffff's and #Kunal Balani's answers are down the right path, but I'd recommend you take a look at UIViewController's documentation for rotation handling on iOS 6+ applications vs how it's handled in iOS 5 and older, since you don't mention whether or not you're supporting iOS 5 (which at the time of writing of this answer is still supported). This will help resolve any issues you could run into down the road. I'd recommend specifically taking a look at willRotateToInterfaceOrientation:duration: / willAnimateRotationToInterfaceOrientation:duration: / didRotateFromInterfaceOrientation: and when they are called during the lifecycle of a rotation.
You will definitely want to store the previous orientation as mentioned in the other answers so you don't run your code more than once.
As for your specific question on more "agnostic" as to what version of landscape vs portrait, UIKit's function reference contains a number of pertinent macros, specifically UIInterfaceOrientationIsPortrait() and UIInterfaceOrientationIsLandscape().
I am building an iPad app in Xcode 4. The app is suposed to always show in Landscape view. to achieve this I have tried the following:
In the Target summary screen I selecte only Landscape Left as a Supported Device Orentation.
In the Target Info screen / Info.plist set the Supported interface orientations(iPad) to Landscape (left home button)
This leads the app the to start in landscape mode, but if I rotate the device it still changes its orientation. Also, when I have a UIViewController presented with presentationStyle UIPresentationFormSheet it rotates to portrait the moment it shows.
In some other threads / forums it was adviced to create a category for UIViewController and rewrite
-(UIDeviceOrientation)interfaceOrientation;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
To always rotate to the Device Orientation (LandscapeLeft) or specifically LandscapeLeft, also to no AutoRotate unless you rotate to LandscapeLeft.
When I set these functions like this (Or for example allow no rotation at all) the app always appears in portrait mode, and wont rotate, not even to LandscapeLeft. The only way to have the app start in Landscape mode is when I allow for rotation no matter what the interfaceOrientaton is.
Does anybody know how I can fix this?
The category I implemented:
#implementation UIViewController(Extends)
-(UIDeviceOrientation)interfaceOrientation
{
return [[UIDevice currentDevice] orientation];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
return YES;
else
return NO;
}
#end
The only place that I can find a Portrait Orientation to be defined is the original window on the MainWindow.xib, but this cannot be altered, and every thread/forum says that that particular setting is/should not be the issue.
As far as I can tell the steps you took should prevent rotation of the interface.
You can always try to override the calls that do the orientation in every viewcontroller of your app. That should at least give you a clue where the rotation is happening. After which a breakpoint can possibly tell you more.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
NSLog( #"will rotate to orientation %d in %#", interfaceOrientation, NSStringFromClass([self class])
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
NSLog( #"did rotate from orientation %d to %d in %#", fromInterfaceOrientation, [self interfaceOrientation], NSStringFromClass([self class])
}
I created a UIViewController (based on How to switch views when rotating) to switch between 2 views when the device rotates. Each view is "specialized" for a particular orientation.
It uses the UIDeviceOrientationDidChangeNotification notification to switch views:
-(void) deviceDidRotate: (NSNotification *) aNotification{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
NSLog(#"Device rotated to %d!", orientation);
if ((orientation == UIDeviceOrientationPortrait) ||
(orientation == UIDeviceOrientationPortraitUpsideDown)) {
[self displayView:self.portraitViewController.view];
}else if ((orientation == UIDeviceOrientationLandscapeLeft) ||
(orientation == UIDeviceOrientationLandscapeRight)) {
[self displayView:self.landscapeViewController.view];
}
}
and sort of works. The problems shows up when I rotate to Landscape and then back to Portrait. When going back to portrait the subviews aren't displayed in the right place, specially the UIPickerView:
First Time Portrait:
Rotate to Landscape:
Back to Portrait:
If I repeat the rotation process, things just get worse. What am I doing wrong?
The source code is here: http://dl.dropbox.com/u/3978473/forums/Rotator.zip
Thanks in advance!
To solve your offset problems, rewrite the displayView: method as below.
-(void) displayView: (UIView *)aView{
self.view = aView;
}
Rotations however are strange. you should review that part of code.
Use the UIViewController rotation methods
(void)willRotateToInterfaceOrientation:duration:
(void)didRotateFromInterfaceOrientation:
instead of -(void)deviceDidRotate:
Much simpler, you will avoid that strange bouncing, and you don't need notifications any more.
Do some reading on the apple documentation on the methods i specified above.
Hope this helps.
OK, I found the error. It's pretty simple and stupid: I mixed frame and bounds.
In the displayView: code I was setting the frame of the child view to the frame of the parent view, when it should be the bounds of the parent.