Device orientation change support is something Delphi (or rather iOS) does for you. And you generally don’t question how it’s done since all apps look the same.
I am here to suggest you something different. Since we are talking about animation, video will explain better:
Now, if you like how it looks, this article will explain how to do that.
The Wrong Way
First of all let me point out how NOT to do this. Your first instinct might be to lock the device in portrait (for example) orientation and detect device rotation with a sensor, then compensating for rotation.
While you can do this, you will encounter at least two issues with this approach:
- If the device is flat on the table when the app starts, you don’t know which layout to choose.
- If your rotation is not synchronized with what iOS thinks your rotation is, any interstitial ads will open the wrong way (it will think that you are locked in portrait). By the way, this is the best spot to remind you that my JVEsoft Components Suite has out of the box support for Chartboost, RevMob, AppFlood and AdMob interstitials, for you to best monetize your app.
The Right Way
Generally what you need to do are two simple steps: disable default animation and implement your own animation.
To do that you would need to copy the FMX.Platform.iOS.pas file from the Delphi’s source\fmx folder to your project folder and add it to your project. Upfront, if you are uncomfortable with changing Embarcadero code, you shouldn’t start: this single file has about 5,200 line of code and you are going to change less than 10 of them.
1. Find the definitions of the FMXViewController interface and the TFMXViewController class and add this declaration to both:
procedure willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation; duration: NSTimeInterval); cdecl;
Delphi doesn’t do anything before rotation, so it does not override it. But we have to.
2. Find the TPlatformCocoaTouch.CalculateStatusBarHeight procedure and change it as follows:
FStatusBarHeight := 0;
Everything will work much cleaner, if Delphi does not take the status bar height into account. And if you want it visible, you would need to compensate for it differently in anyway.
3. Find the TFMXViewController.BeforeOrientationChange procedure and remove both calls to TUIView.OCClass.setAnimationsEnabled.
During the rotation Delphi replaces the portrait drawing with a landscape drawing (or vice versa). Nothing wrong with that, but the way Delphi disables animation to do that interferes with our code.
4. Add the body for the function, we have declared above:
procedure TFMXViewController.willRotateToInterfaceOrientation( toInterfaceOrientation: UIInterfaceOrientation; duration: NSTimeInterval); begin TUIView.OCClass.setAnimationsEnabled(False); end;
This will disable the usual animation, just before it would have started. This code is for pre iOS 8 support.
5. Find the TFMXViewController.didRotateFromInterfaceOrientation procedure and add this line at the top:
TUIView.OCClass.setAnimationsEnabled(True);
This will re-enable the animation after the rotation.
6. Find the TFMXViewController.viewWillTransitionToSize procedure and add this line at the top:
TUIView.OCClass.setAnimationsEnabled(False);
This is for iOS 8 support.
At this point we have disabled the default animation and all we have to do is create our own animation.
7. Add your main form (for example) to the uses clause of FMX.Platform.iOS.pas (you should add it to the implementation side, not the interface side).
8. Find the TFMXViewController.OrientationChanged procedure and add a call to your own animation function at the end of the procedure:
MainWindow.Rotated;
9. The last step is implementing your own animation. The attached example. gives you the exact code, but here is the main extract:
procedure TMainWindow.Rotated; begin // This is the new orientation case TUIApplication.wrap(TUIApplication.OCClass. SharedApplication).statusBarOrientation of UIDeviceOrientationPortrait: Angle := 0; UIDeviceOrientationLandscapeLeft: Angle := 1; UIDeviceOrientationLandscapeRight: Angle := 3; UIDeviceOrientationPortraitUpsideDown: Angle := 2; end; // Here we keep the main layout on the full screen // (Align does not work with rotation) if Angle in [1, 3] then Layout1.SetBounds((FLastWidth - FLastHeight) / 2, (FLastHeight - FLastWidth) / 2, FLastHeight, FLastWidth) else Layout1.SetBounds(0, 0, FLastWidth, FLastHeight); // First rotate the main layout, then animate children rotation Layout1.RotationAngle := 360 - Angle * 90; FloatAnimation1.StopValue := Angle * 90; FloatAnimation1.Start; end;
Here the Layout1 is the top-level layout (it’s the parent of all visual entities on the screen). FloatAnimation1 is defined with StartFromCurrent set to True and is responsible for rotating all the child controls.
To setup the initial state call this Rotated function from your OnCreate event handler.
Working Example
Attached is a working demo code. The GUI was built for iPad, but it would be pretty easy to update it for iPhone.
Notice, due to the bug in Delphi, this code will not work correctly in iOS 8 Simulator. You can run it on the actual device or in an earlier simulator.
You can also look at the some of my apps, which use this approach, for example, here: https://itunes.apple.com/us/app/id592960107
The Question
By the way, can anyone tell me how to do this for Android? (Serious question)
PS
To make sure your users get a consistent feel from the start, include all 4 orientations of the splash screen, instead of the regular portrait and landscape.
For example, the app I’ve given a link to uses these 4: