Ios – Properly preventing orientation change in Flex Mobile app

airapache-flexiosmobileorientation

Is anyone able to actually make it work properly in Flex SDK 4.6?

Here's a short snippet :

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009" 
        xmlns:s="library://ns.adobe.com/flex/spark" 
        addedToStage="onAddedToStage(event)"
        title="Title">
    <fx:Script>
        <![CDATA[  
            private function onAddedToStage(event:Event):void { 
                if (stage.autoOrients) {
                    stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGING, orientationChanging, false, 0, true);
                }
            }

            private function orientationChanging(event:StageOrientationEvent):void {
                if (event.afterOrientation == StageOrientation.DEFAULT || event.afterOrientation == StageOrientation.UPSIDE_DOWN) {
                    event.preventDefault(); 
                }     
            } 
        ]]>
    </fx:Script>
</s:View>

What I'm trying to achieve is to support Landscape mode in both orientations, so if user turns the device 180 degress, the screen should also rotate. But there should be no action at all, when user rotates the device to one of portrait orientations. Instead, I'm seeing width changes to navigator action bar and sometimes content in portrait orientations, so apparently preventing the event is not enough. I'm using the "official" way Adobe suggests, but the problem is that it's not working very well. Granted, the stage does not change, but it seems that there's something firing in navigator anyway, since you can see the action bar width changing.

I had some success with explicitly setting layoutbounds to fixed width in handler method – this prevents changing the actionbar width, but it's only a temporary solution – if the view is a subject to a transition, or some other redraw – it will again render with bad sizes. As if there was something below that was telling it that it's in portrait mode, even though I'm trying to prevent it.

Before you detonate with some silly ideas like "autoOrient = false", don't. It's clearly not a solution for this problem. Obviously it's a bug with Flex SDK – did anyone find a way to fix it or a stable workaround?

EDIT: apparently others bumped into similar issue:
http://forums.adobe.com/message/3969531 (the main topic is about something else, but read magic robots's comment)
http://forums.adobe.com/message/4130972

Best Answer

I'm not sure if this is the right one, did I do something wrong in the end, but after a lot of struggle, I've found this one to be stable solution:

private function onAddedToStage(event:Event):void { 
    if (stage.autoOrients) {
        stage.removeEventListener(StageOrientationEvent.ORIENTATION_CHANGING, orientationChanging);
        stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGING, orientationChanging, false, 100, true);
    }
}      

private function orientationChanging(event:StageOrientationEvent):void {
    event.stopImmediatePropagation();
    if (event.afterOrientation == StageOrientation.DEFAULT || event.afterOrientation == StageOrientation.UPSIDE_DOWN) {
        event.preventDefault(); 
    }   
}  

First thing to note is that addedToStage fires few times (2-3) in mobile application. I don't know why, there's no addChild in my code, obviously. Maybe the AIR runtime does something else. So, to avoid adding unnecessary amount of handlers, the common technique is to remove handler first - it won't do anything, if such handler is not yet registered, but if there is, it will remove it, which will maintain the handler count on 1.

Second thing is the priority of the event - it won't work on 0, it has to be set on something big, to launch before stuff in AIR runtime.

Last thing - event.stopImmediatePropagation() - now, that we're the first to handle the event, we cant prevent this event to be sent further up in this particular scenario.

This together makes the orientation preventing working perfectly - for me the landscape and the reversed landscape (rotated_left, rotated_right) were working and transitioning, while portrait modes did not affect the view at all.

Now, there's danger here - you might want to remove the listener upon leaving the view (on transition animation end, view deactivate or something), because stopImmediatePropagation will prevent the event to be handled in other parts of your application.

I hope Adobe (or Apache now, actually) will take a closer look at this problem and trace my solution.

EDIT

There remaisn a last issue with this solution, which is if application starts while device is in DEFAULT or UPSIDE_DOWN orientation, in this case application will be in portrait mode.

To fix this, a solution is to change Aspect Ratio within addedToStage handler:

   if(Stage.supportsOrientationChange) {
         stage.setAspectRatio(StageAspectRatio.LANDSCAPE);
    }
Related Topic