BR: iPad Pro 12.9 - iOS 17.2 - rotation no longer working

Hi JUCE devs,

I’ve verified reports from customers that when using iOS 17.2 on iPad 12.9 Pro, device orientation changes no longer work as expected.

Some weird top corner offset comes into play, and width/height go very wrong.

I’ve verified this behaviour with the iOS 17.2 Simulator.
I don’t see it with iOS 17.0.1 or earlier, when using iPad 12.9 Pro Simulators.
I don’t see it on the smaller iPad simulators, with any iOS version.

Can somebody please investigate?

Best wishes, Pete

I’ve not yet been able to figure this out, despite a lot of trying, but am wondering if it because JUCE has not yet moved to using UIScenes; i.e. by introducing the use of:

- (UISceneConfiguration *)application:(UIApplication *)application
//configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options 

I can only speculate, but wonder if it it could be related to the underlying UIScene (which iOS auto-creates if you’re not using the above method) and somehow or other restores state incorrectly using NSUserActivity and this method on UIScene…:

scene(_:willConnectTo:options:)

Please can you confirm that you’ve tested with the current develop branch?

Do you see the problem in any of the JUCE examples?

For me, the DemoRunner responds correctly to orientation changes on iOS 17.2 in the iPad 12.9 6th Gen Simulator. The various split-screen and overlay modes also seem to work as expected.

Hi @reuk I’ve actually implemented a minor change (see below) to the JUCE codebase, which puts the UIScheme implementation under JUCE control, which is I guess where we’d all like it :slight_smile:

My motivation for doing the work below, was just in case that resolved my issue - but it didn’t. That means that I’m pretty certain that the issue in question, somehow, is in my own code. I just can’t figure it out. It is just a real stinker - but that is my problem!

Note: app initialisation is moved to UISceneDelegate:willConnectToSession:connectionOptions

On a side note, when the app rotates, this is called:

- (void)windowScene:(UIWindowScene *_Nonnull)windowScene didUpdateCoordinateSpace:(id<UICoordinateSpace>_Nonnull)previousCoordinateSpace interfaceOrientation:(UIInterfaceOrientation)previousInterfaceOrientation traitCollection:(UITraitCollection *_Nonnull)previousTraitCollection

Add to the top of juce_Windowing_ios.mm …:

@interface MyUIScene : UIWindowScene
@end

@implementation MyUIScene
- (instancetype)initWithSession:(UISceneSession *)session connectionOptions:(UISceneConnectionOptions *)connectionOptions {
  self = [super initWithSession:session connectionOptions:connectionOptions];
  if (self) {
  }
  return self;
}
@end

@interface MyUISceneDelegate : NSObject<UIWindowSceneDelegate>
@end

@implementation MyUISceneDelegate

- (instancetype)init {
  self = [super init];
  if (self) {
  }
  return self;
}

// UISceneDelegate

- (void)scene:(UIScene *_Nonnull)scene willConnectToSession:(UISceneSession *_Nonnull)session options:(UISceneConnectionOptions *_Nonnull)connectionOptions {

  if (JUCEApplicationBase::getInstance() == nullptr) {
    if (auto* app = JUCEApplicationBase::createInstance())
    {
      if (! app->initialiseApp())
        exit (app->shutdownApp());
    }
    else
    {
      jassertfalse; // you must supply an application object for an iOS app!
    }
  }
}

- (void)sceneDidDisconnect:(UIScene *_Nonnull)scene {
}


- (void)sceneDidBecomeActive:(UIScene *_Nonnull)scene {
}

- (void)sceneWillResignActive:(UIScene *_Nonnull)scene {
}


- (void)sceneWillEnterForeground:(UIScene *_Nonnull)scene {
}

- (void)sceneDidEnterBackground:(UIScene *_Nonnull)scene {
}

- (void)scene:(UIScene *_Nonnull)scene openURLContexts:(NSSet<UIOpenURLContext *> *_Nonnull)URLContexts {
}

- (nullable NSUserActivity *)stateRestorationActivityForScene:(UIScene *_Nonnull)scene {
  return nil;
}

- (void)scene:(UIScene *_Nonnull)scene restoreInteractionStateWithUserActivity:(NSUserActivity *_Nonnull)stateRestorationActivity {
}

- (void)scene:(UIScene *_Nonnull)scene willContinueUserActivityWithType:(NSString *_Nonnull)userActivityType {
}

- (void)scene:(UIScene *_Nonnull)scene continueUserActivity:(NSUserActivity *_Nonnull)userActivity {
}

- (void)scene:(UIScene *_Nonnull)scene didFailToContinueUserActivityWithType:(NSString *_Nonnull)userActivityType error:(NSError *_Nonnull)error {
}

- (void)scene:(UIScene *_Nonnull)scene didUpdateUserActivity:(NSUserActivity *_Nonnull)userActivity {
}

// UIWindowSceneDelegate

- (void)windowScene:(UIWindowScene *_Nonnull)windowScene didUpdateCoordinateSpace:(id<UICoordinateSpace>_Nonnull)previousCoordinateSpace interfaceOrientation:(UIInterfaceOrientation)previousInterfaceOrientation traitCollection:(UITraitCollection *_Nonnull)previousTraitCollection {

}

- (void)windowScene:(UIWindowScene *_Nonnull)windowScene performActionForShortcutItem:(UIApplicationShortcutItem *_Nonnull)shortcutItem completionHandler:(void(^_Nonnull)(BOOL succeeded))completionHandler {

}

- (void)windowScene:(UIWindowScene *_Nonnull)windowScene userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *_Nonnull)cloudKitShareMetadata {

}

@end

// Replace this:

- (void) applicationDidFinishLaunching: (UIApplication*) application
{
    ignoreUnused (application);
    initialiseJuce_GUI();

//    if (auto* app = JUCEApplicationBase::createInstance())
//    {
//        if (! app->initialiseApp())
//            exit (app->shutdownApp());
//    }
//    else
//    {
//        jassertfalse; // you must supply an application object for an iOS app!
//    }
}

// Add this (which initialises the UIScene)

- (UISceneConfiguration *)application:(UIApplication *)application
configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {

  UISceneConfiguration* result = [[UISceneConfiguration alloc] initWithName:@"MyUIScene" sessionRole:UIWindowSceneSessionRoleApplication];

  result.sceneClass = MyUIScene.class;
  result.delegateClass = MyUISceneDelegate.class;
  return result;
}

In juce_UIViewComponentPeer_ios.mm
In UIViewComponentPeer::UIViewComponentPeer method…
Replace code as shown:

    if (isSharedWindow)
    {
        window = [viewToAttachTo window];
        [viewToAttachTo addSubview: view];
    }
    else
    {
      r = convertToCGRect (component.getBounds());
      r.origin.y = [UIScreen mainScreen].bounds.size.height - (r.origin.y + r.size.height);

      // window = [[JuceUIWindow alloc] initWithFrame: r];
      // [((JuceUIWindow*) window) setOwner: this];

      // Create window so that it is owned by the scene (scene should already be available)
      bool bFoundScene = false;
      UIApplication* sharedApplication = [UIApplication sharedApplication];
      if (sharedApplication != nil) {
        for (UIScene* scene in [sharedApplication connectedScenes]) {
          if ([scene isKindOfClass: [UIWindowScene class]]) {
            UIWindowScene* uiWindowScene = (UIWindowScene*)scene;
            bFoundScene = true;
            window = [[JuceUIWindow alloc] initWithWindowScene:uiWindowScene];
            break;
          }
        }
      }

      if (bFoundScene == false) {
        // Unexpected!
        window = [[JuceUIWindow alloc] initWithFrame: r];
      }

      [((JuceUIWindow*) window) setOwner: this];

      controller = [[JuceUIViewController alloc] init];

Hi @reuk,

I’m pleased to say that I’ve also fixed my weird rotation problem - which is where this all started!

I suspect that things are playing up for me because my app is a really big beast, and takes a longer time to do some things than other apps. So, this points probably points to some sort of underlying timing-related issue in the JUCE iOS layer. Of course, I don’t really know, I’m just speculating. But anyhow, here is my simple fix.

Replace JUCE method as follows:

// MARK: MPC - rotation fix (begin)
void UIViewComponentPeer::updateScreenBounds()
{
    auto& desktop = Desktop::getInstance();

    forceDisplayUpdate();

    if (fullScreen)
    {
        fullScreen = false;
        setFullScreen (true);
    }
    else if (! isSharedWindow)
    {
        auto newDesktop = desktop.getDisplays().getPrimaryDisplay()->userArea;

        component.setBounds (newDesktop);
    }

    [view setNeedsDisplay];
}
// MARK: MPC - rotation fix (end)

Best wishes, Pete

Hi @reuk,

Sorry, I had a borked implementation pasted-in there. Now fixed.

Pete