Jump to content
gioma

[Firemonkey ]Change iOS screen rotation at runtime

Recommended Posts

Hi,

I need to rotate the screen at runtime when I open a form.
Until the release of IOS 13 this code worked perfectly:

 

procedure ChangeOrientation(toOrientation: UIInterfaceOrientation; possibleOrientations: TScreenOrientations);
var
  win : UIWindow;
  App : UIApplication;
  viewController : UIViewController;
  oucon: UIViewController;
begin

  Application.FormFactor.Orientations := []; //Change supported orientations
  App := TUIApplication.Wrap(TUIApplication.OCClass.sharedApplication);
  win := TUIWindow.Wrap(App.windows.objectAtIndex(0)); //The first Windows is always the main Window

  App.setStatusBarOrientation(toOrientation);

  {After you have changed your statusbar orientation set the
  Supported orientation/orientations to whatever you need}
  Application.FormFactor.Orientations := possibleOrientations;

  viewController := TUIViewController.Wrap(TUIViewController.alloc.init);//dummy ViewController
  oucon := TUIViewController.Wrap(TUIViewController.alloc.init);
  {Now we are creating a new Viewcontroller now when it is created
   it will have to check what is the supported orientations}
  oucon := win.rootViewController;//we store all our current content to the new ViewController
  Win.setRootViewController(viewController);
  Win.makeKeyAndVisible;// We display the Dummy viewcontroller

  win.setRootViewController(oucon);
  win.makeKeyAndVisible;

  {And now we Display our original Content in a new Viewcontroller
   with our new Supported orientations}
end;

However, since the release of IOS 13 the screen locks on the set orientation but does not rotate. So if I set Landscape orientation the screen remains in Portrait orientation until I physically turn the phone around, at which point it rightly remains locked on that orientation.
This is a real disaster because then, until the phone is turned, all the controls ( finger position, object position,..) are wrong.

 

Share this post


Link to post

Looking for a solution too, since I used same code as yours.
Seems that SetStatusBar was deprecated, and now in iOS 13 its probably gone.
https://stackoverflow.com/questions/7030682/ios-iphone-ipad-sdk-alternative-for-uiapplication-sharedapplication-setst

https://developer.apple.com/documentation/uikit/uiapplication/1623026-statusbarorientation

 

 

Edited by Rollo62

Share this post


Link to post

Yes, I know that the SetStatusBar function is obsolete and now there is no other function to do it.
I found a solution, to open the module that I would like to show only in Landscape, I show a message to the user to inform him that he must rotate the phone to open it.
Then, when the user rotates the phone, the "onResize" event is activated. In this event the orientation is set only to Landscape and then the form is opened.

Share this post


Link to post

Ok, that is a workaround.
But I hope that there is a better way ... Apple strikes back on iOS13 again ... I'm just still fixing other similar issues.

Share this post


Link to post

I still have a "manual" solution implemented, which gives advice to the user howto set-up the right orientation.

Not perfect, but no time to search for a better one right now.

Since its happening only in a remore place in one app, its acceptable for me.

 

Share this post


Link to post
24 minutes ago, Rollo62 said:

I still have a "manual" solution implemented, which gives advice to the user howto set-up the right orientation.

In my case, it's not about advising the user how to set up anything: Application.FormFactor.Orientations takes care of which orientations the app will support. The issue is with re-orienting the app once Application.FormFactor.Orientations has been changed at runtime. Currently, the user has to rotate the device themselves.

Share this post


Link to post

Thats what I meant too:
- if device is currently in landscape,
- the user switches a button to show a  new page which requires Portrait mode
- give the user an advice that turning the device is necessary before, or
- possible too that the new mode appears automatically, when the user has turned the device into portrait

 

  • Like 1

Share this post


Link to post

Thanks to a message from @Stewag, I've been prompted to revisit this issue. The following code works on iOS 17. I expect it'll work on iOS 16 - not sure about earlier:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  Macapi.ObjectiveC, Macapi.Helpers,
  iOSapi.Foundation, iOSapi.UIKit, iOSapi.Helpers;

type
  UIViewControllerEx = interface(UIViewController)
    ['{3E6C5FB2-20F2-4B42-BFD8-33968A80E809}']
    procedure setNeedsUpdateOfSupportedInterfaceOrientations; cdecl;
  end;
  TUIViewControllerEx = class(TOCGenericImport<UIViewControllerClass, UIViewControllerEx>)  end;


procedure TForm1.Button1Click(Sender: TObject);
var
  LID: Pointer;
begin
  Application.FormFactor.Orientations := [TScreenOrientation.Portrait, TScreenOrientation.InvertedPortrait];
  if TOSVersion.Check(16) then
  begin
    LID := NSObjectToID(TiOSHelper.SharedApplication.keyWindow.rootViewController);
    TUIViewControllerEx.Wrap(LID).setNeedsUpdateOfSupportedInterfaceOrientations;
  end
  else
    TUIViewController.OCClass.attemptRotationToDeviceOrientation;
end;

If you run the app, then turn the device so that it is in landscape orientation, then click the button, it'll reorient the app into portrait.

Share this post


Link to post
Posted (edited)
procedure TfMain.IOSTurn(Portrait: boolean);
// Source: https://en.delphipraxis.net/topic/1796-firemonkey-change-ios-screen-rotation-at-runtime/
var
  LID: Pointer;
begin
  if Portrait then
  begin
    Application.FormFactor.Orientations := [TScreenOrientation.Portrait, TScreenOrientation.InvertedPortrait];

    if not TOSVersion.Check(16) then    // iOS lower than 16
    begin
      showmessage('Please turn phone back to portrait');
      TurnIOStoPortrait := False;  // turn off message to prevent multiple display
    end;
  end
  else
  begin
    Application.FormFactor.Orientations := [TScreenOrientation.Landscape, TScreenOrientation.InvertedLandscape];
  end;

  if TOSVersion.Check(16) then // iOS Version 16 and up
  begin
    LID := NSObjectToID(TiOSHelper.sharedApplication.keyWindow.rootViewController);
    TUIViewControllerEx.Wrap(LID).setNeedsUpdateOfSupportedInterfaceOrientations;
  end
  else // iOS 13: no automatic rotation. Not tested for iOS<13  
  begin
    TUIViewController.OCClass.attemptRotationToDeviceOrientation;

    if TabControl1.ActiveTab.Name = 'XXX' then // limit message to specific tab
      showmessage('Please turn phone to landscape');
  end
end;

Dave's code works magnificently - thank you Dave!

 

Here is an alternative procedure to Dave's Button1Click() that works both ways and includes messages to rotate the phone manually.

This is necessary for iOS lower than 16, where the content but not the display is rotated by the code.

In IOS 16 and up, rotation is done smoothly automatically.

 

I could only test iOS 13 though, maybe someone can extend the code to iOS < 13?

 

Steffen

Edited by Stewag

Share this post


Link to post
Posted (edited)

I can conform Daves code too, works well here also :classic_smile:👍.
 

3 hours ago, Stewag said:

    if TabControl1.ActiveTab.Name = 'XXX' then // limit message to specific tab
      showmessage('Please turn phone to landscape');

 


What I've implemented in the former manual approach, was a system that shows a message and listens to the user turning the screen into the right position, handled by anonymous method.
My goal was to distract the user as little as possible from his normal workflow, not enforcing him to press and buttons.

class procedure TCore_Orientation.SetCurrent( const AOrientNew       : TScreenOrientation;
                                              const AOrientPossible  : TScreenOrientations;
                                              const ANotifyWhenReady : TProc );
begin
    // Use an anonymous method, to notify the change to the caller
    FNotifyWhenReady  := ANotifyWhenReady;
    
    // Do whatever is necessary, either direct or after users message.
    
    // Register my ApplicationEvent handler, that listens to the change has occured, if that is necessary
end;

...

  //  How to use it in the caller
  TCore_Orientation.SetCurrent(
                    TScreenOrientation.Portrait,          // Desired orientation
                  [ TScreenOrientation.Portrait,          // Possible orientations allowed 
                    TScreenOrientation.InvertedPortrait
                  ],
                  procedure  // Wait until User turned it as desired, or after it was set by hardware. Never reach this when cancelled.
                  begin  
                      // Do whatever you wanted to do, after desired orientation is finally set up (triggered by ApplicationEvent or direct)
                  end );
                  
...

end;


 

Edited by Rollo62

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×