UIDeviceOrientationDidChangeNotification calling multiple times - ios

UIDeviceOrientationDidChangeNotification is calling multiple times when the device changes orientation. I am unsure why this happens or how to fix it.
What I am trying to do is to keep the contentoffset of the scrollview the same, so when the user rotates the screen the app keeps the page they were on.
The odd thing is when I rotate the screen the first time the code executes like I would want. But every time after that the code executes multiple times and eventually the contentoffset is set o 0.
Here's what I have.
- (void)loadView {
//some code that sizes itself depending on the current orientation
//WILL BE CALLED AFTER EVERY ORIENTATION CHANGE
}
- (void)viewDidLoad {
//begin generating messages
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:[UIDevice currentDevice]];
//if is portrait and was landscape
if (orientation==1 && temp==2) {
int cal = offsetHolder.x/screenframe.size.height;
offsetHolder.x = cal * screenframe.size.width;
ScrollView.contentOffset = offsetHolder;
}
//if is landscape and was portrait
if (orientation==2 && temp==1) {
int cal = offsetHolder.x/screenframe.size.width;
offsetHolder.x = cal * screenframe.size.height;
ScrollView.contentOffset = offsetHolder;
}
}
On orientation change I change the value of 'int orientation' then call loadview to change the sizing of the view. Then I call viewdidload to get the proper contentoffset
- (void) orientationChanged:(NSNotification *)note {
CGRect screenframe = [[UIScreen mainScreen] bounds];
//holding the current offset
offsetHolder = ScrollView.contentOffset;
if ([[UIDevice currentDevice] orientation] == 1 || [[UIDevice currentDevice] orientation] == 0 || [[UIDevice currentDevice] orientation] == UIDeviceOrientationPortraitUpsideDown) {
temp=orientation;
orientation = 1;
[self loadView];
[self viewDidLoad];
}
else if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationFaceDown || [[UIDevice currentDevice] orientation] == UIDeviceOrientationFaceUp){
temp=orientation;
}
else{
temp=orientation;
orientation = 2;
[self loadView];
[self viewDidLoad];
}
}
EDIT:
I have found the problem. What I am doing is creating another instance of self.view instead of overwriting this one. Is there an easy way to destroy this view and re-initialize it?
EDIT2:
Have found a fix. I stopped calling loadview and viewdidload as per jsds' instructions. And instead moved all code in my loadview to another function that I called from loadview. All this code does is instantiate the UI (initview) objects and places them in the correct places depending upon orientation.
Then I create another function that removes all subviews from the view. Then on orientation change I call this function and my initview to destroy all subviews and then recreate them on orientation change.

UIDeviceOrientation basically has 6 different states, namely two portrait, two landscape, and face up and face down. So lets say when you pick up your device from flat position to vertical position, the notification will be triggered.
You can filter the faceup, facedown, and unknown states by using the macro UIDeviceOrientationIsValidInterfaceOrientation
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
// Ignore changes in device orientation if unknown, face up, or face down.
if (!UIDeviceOrientationIsValidInterfaceOrientation(currentOrientation)) {
return;
}
If you still find the notification getting triggered multiple times, I am afraid you may need to use your custom logic with flags to check the previous and current value.
In my case, since I use reactive cocoa framework, the following code works for me :
if (IS_IPAD) {
#weakify(self);
[[[[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIDeviceOrientationDidChangeNotification object:nil]
takeUntil:[self rac_willDeallocSignal]]
map:^id (NSNotification *notification) {
return #([[UIDevice currentDevice] orientation]);
}]
filter:^BOOL(NSNumber *deviceOrientationNumber) {
UIDeviceOrientation currentOrientation = [deviceOrientationNumber integerValue];
//We ignore the UIDeviceOrientationUnknown, UIDeviceOrientationFaceUp and UIDeviceOrientationFaceDown
return UIDeviceOrientationIsValidInterfaceOrientation(currentOrientation);
}]
distinctUntilChanged]
subscribeNext:^(id x) {
#strongify(self);
//do something here...
}];
}
Here the distinctUntilChanged method makes sure the code gets triggered only when the orientation changes to a new valid value.

You should never call loadView or viewDidLoad yourself. That's up to the framework to manage.
If you are positioning or resizing views based on the view bounds in viewDidLoad, don't. Move that to viewWillLayoutSubviews. That will be called automatically when the device orientation changes.
Alternatively, use autolayout constraints and then you won't have to do anything at all.

Related

Setting screen orientation from inside SKScene

My game is implemented in a single SKScene object. A button (SKSpriteNode) lets the user switch from landscape to portrait mode. Well, it should. But I can't make this work. There are many discussions on this but they are all rather complicated and give suggestions that are completely different.
So.... What is the easiest way to make a button in an SKScene that changes the orientation to portrait or landscape?
Any suggestions are appreciated.
Here is a short, concise, complete answer to the question I posted.
In xcode, select File->New->Project->IOS->Application->Game to generate a default app framework for a game for IPad using SpriteKit with ObjectiveC. This is the classic HelloWorldApp where spinning airplanes appear whenever you touch the screen.
In Targets->DEploymentInfo->DeviceOrientation, checkmark Portrait and LandscapeLeft
Modify shouldAutorotate for the view controller like so...
- (BOOL)shouldAutorotate
{
int currentOrientation = (int)[[UIDevice currentDevice] orientation];
if (scene.orientation != currentOrientation) {
// Rotate the device back to orginial orientation
[[UIDevice currentDevice] setValue:
[NSNumber numberWithInteger: scene.orientation]
forKey:#"orientation"];
return NO;
}
return YES;
}
change the body of touchesBegan in your gameScene class like so...
-(void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
/* Called when a touch begins */
NSLog(#"%d", self.orientation);
if (!self.orientation || self.orientation == UIInterfaceOrientationPortrait) {
self.orientation = UIInterfaceOrientationLandscapeLeft;
} else {
self.orientation = UIInterfaceOrientationPortrait;
}
[[UIDevice currentDevice] setValue:
[NSNumber numberWithInteger: self.orientation]
forKey:#"orientation"];
the app will then hange orientation on each tap.

Is it possible to Rotate/Affine Transform UIMenuController

Without going into the 'why' of it all, I have UITextField that is part of a view hierarchy that is forcibly 'upside down' on screen via an affine transform.
CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(180))
This works fine for all of the subviews -- including the UITextField -- until the user wants to use the copy/paste features of UIMenuController on the text field content. When the UIMenuController is shown, it is 'right side up' rather than 'upside down' like the UITextField.
Is there anyway to get a hold of the UIMenuController's view to apply the same transform when it is shown?
Currently, I am listening for the UIMenuControllerWillShowMenuNotification notification and then getting the UIMenuController. But I can't seem to find a way to apply the transform to it. Any ideas?
- (void)textFieldDidBeginEditing:(UITextField *)textField {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(menuControllerWillShow:) name:UIMenuControllerWillShowMenuNotification object:nil];
}
- (void)menuControllerWillShow:(NSNotification*)aNotification {
UIMenuController* menuController = [UIMenuController sharedMenuController];
CGAffineTransform xform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(180));
CGRect oldRect = menuController.menuFrame;
CGRect newRect = CGRectApplyAffineTransform(oldRect, xform);
// eh... now what?
}
I don't think you can modify the UIMenuController so it's upside down.
But if it's pretty much your entire app that's altered, you could either check the device orientation or the app orientation (by checking where the status bar is) and force it to be the opposite.
To get the device orientation you could do something like this:
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]
Note from the docs:
The value of this property always returns 0 unless orientation notifications have been enabled by calling beginGeneratingDeviceOrientationNotifications.
Or get the app orientation with this:
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if(orientation == 0) //Default orientation
{
//UI is in Default (Portrait) -- this is really a just a failsafe.
}
else if(orientation == UIInterfaceOrientationPortrait)
{
//Do something if the orientation is in Portrait
}
else if(orientation == UIInterfaceOrientationLandscapeLeft)
{
// Do something if Left
}
else if(orientation == UIInterfaceOrientationLandscapeRight)
{
//Do something if right
}
and then set it with something like this:
Objective-C:
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
[[UIDevice currentDevice] setValue:value forKey:#"orientation"];
Swift:
let value = UIInterfaceOrientation.LandscapeLeft.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")

In my iOS app, how can I know the device orientation when viewDidLoad is called?

I'm trying this :
- (void)viewDidLoad {
[super viewDidLoad];
if (UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
NSLog(#"PORTRAIT");
} else {
NSLog(#"LANDSCAPE");
}
}
but it seems that [[UIDevice currentDevice] orientation] returns 0 so I can't know in which orientation the device is at that moment. How can I know it ?
I am using
UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication]statusBarOrientation]
This from:
iOS Developer Library: Responding to Orientation Changes in a Visible View Controller
The window adjusts the bounds of the view controller’s view. This causes the view to layout its subviews, triggering the view controller’s viewWillLayoutSubviews method. When this method runs, you can query the app object’s statusBarOrientation property to determine the current user interface layout.
You can check the orientation of device like this
if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
{
NSLog (#"Device is in Portrait Mode");
}
else
{
NSLog (#"Device is in Landscape Mode");
}

Orientation : portrait & landscape in Xcode 4.2

I wanted to make my project support full orientation.
I'm on xcode 4.2
My implementation gives me one warning:
that's the code :
#import "OrientationTutorialViewController.h"
#implementation OrientationTutorialViewController
#synthesize portraitView, landscapeView;
- (void)viewDidLoad
{
[super viewDidLoad];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
}
- (void) orientationChanged:(id)object
{
UIInterfaceOrientation interfaceOrientation = [[object object] orientation];
if (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
self.view = self.portraitView;
}
else
{
self.view = self.landscapeView;
}
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
- (void)dealloc
{
[super dealloc];
}
#end
Is there a way to fix this warning?
I'm guessing you copied this code from this tutorial. This shows the danger of just copying and pasting code from some random person on the Internet.
There are a few problems with this code. First, there's the issue you describe here, where the UIDeviceOrientationDidChangeNotification notification passes back a UIDevice, whose -orientation method returns a UIDeviceOrientation enum. For some reason, the author of this code is assigning that value to a UIInterfaceOrientation enum, instead of dealing with it as a UIDeviceOrientation value. This could be fixed by using the appropriate enum type and comparing against those values.
Second, why are they using a notification for orientation changes, when they just as easily could be using the UIViewController delegate method -didRotateFromInterfaceOrientation:? That does pass in a UIInterfaceOrientation enum. I recommend replacing the notification and the responder method above with -didRotateFromInterfaceOrientation:. See Apple's many examples of view controller autorotation, as well as their copious documentation, for how to do this.
Third, if they're going to have a method respond to a notification, like in -orientationChanged: above, it should take an NSNotification object, not just a generic id.
I have tried so many of these alternatives, then I found out that you also have to be sure to change the variabel Initial interface orientation to what you want in addition to adding
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{
return (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
somewhere in your implementation file. Just the snippet worked in the beginning, but when adding more views and controllers, it all got messed up until I changed the .plist.

Get current orientation of iPad?

In a given event handler (not the "shouldAutorotateToInterfaceOrientation" method) how do I detect the current iPad orientation? I have a text field I have to animate up (when keyboard appears) in the Landscape view, but not in the portrait view and want to know which orientation I'm in to see if the animation is necessary.
Orientation information isn't very consistent, and there are several approaches. If in a view controller, you can use the interfaceOrientation property. From other places you can call:
[[UIDevice currentDevice] orientation]
Alternatively, you can request to receive orientation change notifications:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
Some people also like to check the status bar orientation:
[UIApplication sharedApplication].statusBarOrientation
I think
[[UIDevice currentDevice] orientation];
is not really reliable. Sometimes it works, sometimes not... In my apps, I use
[[UIApplication sharedApplication]statusBarOrientation];
and it works great!
One of:
Check the interfaceOrientation property of the active view controller.
[UIApplication sharedApplication].statusBarOrientation.
[UIDevice currentDevice].orientation. (You may need to call -beginGeneratingDeviceOrientationNotifications.)
I found a trick to solve the FaceUp orientation issue!!!
Delay the orientation check till AFTER the app has started running, then set variables, view sizes, etc.!!!
//CODE
- (void)viewDidLoad {
[super viewDidLoad];
//DELAY
[NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(delayedCheck)
userInfo:nil
repeats:NO];
}
-(void)delayedCheck{
//DETERMINE ORIENTATION
if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait ){
FACING = #"PU";
}
if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown ){
FACING = #"PD";
}
if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft ){
FACING = #"LL";
}
if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight ){
FACING = #"LR";
}
//DETERMINE ORIENTATION
//START
[self setStuff];
//START
}
-(void)setStuff{
if( FACING == #"PU" ){
//logic for Portrait
}
else
if( FACING == #"PD" ){
//logic for PortraitUpsideDown
}
else{
if( FACING == #"LL"){
//logic for LandscapeLeft
}
else
if( FACING == #"LR" ){
//logic for LandscapeRight
}
}
//CODE
You can addSubviews, position elements, etc. in the 'setStuff' function ... anything that would initially depend on the orientation!!!
:D
-Chris Allinson
You can achieve this by two ways:
1- By using the following method:
**Put the following line in the -(void)viewDidLoad Method:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(deviceRotated:) name:UIDeviceOrientationDidChangeNotification object:nil];
then put this method inside your class
-(void)deviceRotated:(NSNotification*)notification
{
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if(orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight)
{
//Do your textField animation here
}
}
The above method will check the orientation when the device will be rotated
2- The second way is by inserting the following notification inside -(void)viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(checkRotation:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
then put the following method inside your class
-(void)checkRotation:(NSNotification*)notification
{
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if(orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight)
{
//Do your textField animation here
}
}
The above method will check the orientation of the status bar of the ipad or iPhone and according to it you make do your animation in the required orientation.
For determining landscape vs portrait, there is a built-in function:
UIInterfaceOrientation orientation = [[UIDevice currentDevice] orientation];
BOOL inLandscape = UIDeviceOrientationIsLandscape(orientation);
[UIApplication sharedApplication].statusBarOrientation returns portrait when it's landscape, and landscape when it's portrait at launch, in iPad
I don't know why, but every time my app starts, the first 4 are right, but subsequently I get the opposite orientation. I use a static variable to count this, then have a BOOL to flip how I manually send this to subviews.
So while I'm not adding a new stand-alone answer, I'm saying use the above and keep this in mind. Note: I'm receiving the status bar orientation, as it's the only thing that gets called when the app starts and is "right enough" to help me move stuff.
The main problem with using this is the views being lazily loaded. Be sure to call the view property of your contained and subviews "Before" you set their positions in response to their orientation. Thank Apple for not crashing when we set variables that don't exist, forcing us to remember they break OO and force us to do it, too... gah, such an elegant system yet so broken! Seriously, I love Native, but it's just not good, encourages poor OO design. Not our fault, just reminding that your resize function might be working, but Apple's Way requires you load the view by use, not by creating and initializing it
In your view controller, get the read-only value of self.interfaceOrientation (the current orientation of the interface).
I've tried many of the above methods, but nothing seemed to work 100% for me.
My solution was to make an iVar called orientation of type UIInterfaceOrientation in the Root View Controller.
- (void)viewDidLoad {
[super viewDidLoad];
orientation = self.interfaceOrientation; // this is accurate in iOS 6 at this point but not iOS 5; iOS 5 always returns portrait on app launch through viewDidLoad and viewWillAppear no matter which technique you use.
}
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{
return YES;
}
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
orientation = toInterfaceOrientation;
}
Then, any place where you need to check the orientation you can do something like this:
if(UIInterfaceOrientationIsPortrait(orientation)){
// portrait
}else{
// landscape
}
There may still be a better way, but this seems to work 98% of the time (iOS5 notwithstanding) and isn't too hard. Note that iOS5 always launches iPad in portrait view, then sends a device the willRotateTo- and didRotateFromInterfaceOrientation: messages, so the value will still be inaccurate briefly.
[UIDevice currentDevice].orientation works great.
BUT!!!
... the trick is to add it to - (void)viewWillAppear:(BOOL)animated
exp:
(void)viewWillAppear:(BOOL)animated{
...
BOOL isLandscape = UIInterfaceOrientationIsLandscape(self.interfaceOrientation);
...
}
If you call it at - (void)viewDidLoad, it does not work reliable, especially if you use multiple threads (main UI thread, background thread to access massive external data, ...).
Comments:
1) Even if your app sets default orientation portrait, user can lock it at landscape. Thus setting the default is not really a solution to work around it.
2) There are other tasks like hiding the navigation bar, to be placed at viewWillAppear to make it work and at the same time prevent flickering. Same applies to other views like UITableView willDisplayCell -> use it to set cell.selected and cell.accessoryType.

Resources