diff --git a/modules/juce_gui_basics/native/juce_NativeMessageBox_ios.mm b/modules/juce_gui_basics/native/juce_NativeMessageBox_ios.mm index 4230ff1f31..7182599bd9 100644 --- a/modules/juce_gui_basics/native/juce_NativeMessageBox_ios.mm +++ b/modules/juce_gui_basics/native/juce_NativeMessageBox_ios.mm @@ -117,7 +117,7 @@ std::unique_ptr ScopedMessageBoxInterface::create (co return iOSGlobals::currentlyFocusedPeer; }); - + NSUniquePtr alert; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageBox) }; diff --git a/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm b/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm index c3f6341ba6..9e5dcbc575 100644 --- a/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm +++ b/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm @@ -40,6 +40,43 @@ namespace juce { +struct WindowSceneTrackerListener +{ + virtual ~WindowSceneTrackerListener() = default; + virtual void windowSceneChanged() = 0; +}; + +struct WindowSceneTracker +{ +public: + WindowSceneTracker() = default; + + void setWindowScene (UIWindowScene* x) API_AVAILABLE (ios (13.0)) + { + windowScene = x; + listeners.call ([] (auto& l) { l.windowSceneChanged(); }); + } + + UIWindowScene* getWindowScene() const API_AVAILABLE (ios (13.0)) + { + return static_cast (windowScene); + } + + void addListener (WindowSceneTrackerListener& l) + { + listeners.add (&l); + } + + void removeListener (WindowSceneTrackerListener& l) + { + listeners.remove (&l); + } + +private: + ListenerList listeners; + id windowScene = nil; +}; + //============================================================================== static NSArray* getContainerAccessibilityElements (AccessibilityHandler& handler) { @@ -355,10 +392,11 @@ struct UIViewPeerControllerReceiver //============================================================================== class UIViewComponentPeer final : public ComponentPeer, - public UIViewPeerControllerReceiver + public UIViewPeerControllerReceiver, + private WindowSceneTrackerListener { public: - UIViewComponentPeer (Component&, int windowStyleFlags, UIView* viewToAttachTo); + UIViewComponentPeer (Component&, int windowStyleFlags, id viewToAttachTo); ~UIViewComponentPeer() override; //============================================================================== @@ -460,6 +498,7 @@ public: bool fullScreen = false, insideDrawRect = false; NSUniquePtr hiddenTextInput { [[JuceTextView alloc] initWithOwner: this] }; NSUniquePtr tokenizer { [[JuceTextInputTokenizer alloc] initWithPeer: this] }; + SharedResourcePointer windowSceneTracker; static int64 getMouseTime (NSTimeInterval timestamp) noexcept { @@ -508,6 +547,19 @@ private: [controller setNeedsStatusBarAppearanceUpdate]; } + void windowSceneChanged() override + { + if (isSharedWindow) + return; + + if (@available (ios 13, *)) + { + window.windowScene = windowSceneTracker->getWindowScene(); + } + + updateScreenBounds(); + } + //============================================================================== class AsyncRepaintMessage final : public CallbackMessage { @@ -1699,7 +1751,9 @@ struct ChangeRegistrationTrait }; //============================================================================== -UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags, UIView* viewToAttachTo) +UIViewComponentPeer::UIViewComponentPeer (Component& comp, + int windowStyleFlags, + id viewToAttachTo) : ComponentPeer (comp, windowStyleFlags), isSharedWindow (viewToAttachTo != nil), isAppex (SystemStats::isRunningInAppExtensionSandbox()) @@ -1728,8 +1782,9 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags, if (isSharedWindow) { - window = [viewToAttachTo window]; - [viewToAttachTo addSubview: view]; + auto* uiViewToAttachTo = static_cast (viewToAttachTo); + window = [uiViewToAttachTo window]; + [uiViewToAttachTo addSubview: view]; } else { @@ -1737,6 +1792,12 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags, r.origin.y = [UIScreen mainScreen].bounds.size.height - (r.origin.y + r.size.height); window = [[JuceUIWindow alloc] initWithFrame: r]; + + if (@available (ios 13, *)) + { + window.windowScene = windowSceneTracker->getWindowScene(); + } + [((JuceUIWindow*) window) setOwner: this]; controller = [[JuceUIViewController alloc] init]; @@ -1755,10 +1816,14 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags, setTitle (component.getName()); setVisible (component.isVisible()); + + windowSceneTracker->addListener (*this); } UIViewComponentPeer::~UIViewComponentPeer() { + windowSceneTracker->removeListener (*this); + if (iOSGlobals::currentlyFocusedPeer == this) iOSGlobals::currentlyFocusedPeer = nullptr; @@ -1865,6 +1930,12 @@ void UIViewComponentPeer::setFullScreen (bool shouldBeFullScreen) if ((! shouldBeFullScreen) && r.isEmpty()) r = getBounds(); + // If we're using the UIScene lifecycle we create our first window before we get assigned + // a UIWindowScene, in which case we won't be able to determine the available screen area + // and the available bounds may be empty. In this case, we still want to fill the screen + // as soon as we find out the available screen area, so set this flag unconditionally. + fullScreen = shouldBeFullScreen; + // (can't call the component's setBounds method because that'll reset our fullscreen flag) if (! r.isEmpty()) setBounds (detail::ScalingHelpers::scaledScreenPosToUnscaled (component, r), shouldBeFullScreen); @@ -2300,7 +2371,7 @@ void UIViewComponentPeer::performAnyPendingRepaintsNow() ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo) { - return new UIViewComponentPeer (*this, styleFlags, (UIView*) windowToAttachTo); + return new UIViewComponentPeer (*this, styleFlags, (id) windowToAttachTo); } //============================================================================== diff --git a/modules/juce_gui_basics/native/juce_Windowing_ios.mm b/modules/juce_gui_basics/native/juce_Windowing_ios.mm index 2cb7ef66a8..cb8998adcd 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_ios.mm +++ b/modules/juce_gui_basics/native/juce_Windowing_ios.mm @@ -44,8 +44,123 @@ namespace juce // This is an internal list of callbacks (but currently used between modules) Array appBecomingInactiveCallbacks; + + struct BadgeUpdateTrait + { + #if JUCE_IOS_API_VERSION_CAN_BE_BUILT (16, 0) + API_AVAILABLE (ios (16)) + static void newFn (UIApplication*) + { + [[UNUserNotificationCenter currentNotificationCenter] setBadgeCount: 0 withCompletionHandler: nil]; + } + #endif + + static void oldFn (UIApplication* app) + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + app.applicationIconBadgeNumber = 0; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } + }; + + struct SceneUtils + { + // This will need to become more sophisticated to enable support for multiple scenes + static void sceneDidBecomeActive() + { + ifelse_17_0 ([UIApplication sharedApplication]); + isIOSAppActive = true; + } + + static void sceneWillResignActive() + { + isIOSAppActive = false; + + for (int i = appBecomingInactiveCallbacks.size(); --i >= 0;) + appBecomingInactiveCallbacks.getReference (i)->appBecomingInactive(); + } + + static void sceneDidEnterBackground() + { + if (auto* app = JUCEApplicationBase::getInstance()) + { + #if JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK + appSuspendTask = [application beginBackgroundTaskWithName:@"JUCE Suspend Task" expirationHandler:^{ + if (appSuspendTask != UIBackgroundTaskInvalid) + { + [application endBackgroundTask:appSuspendTask]; + appSuspendTask = UIBackgroundTaskInvalid; + } + }]; + + MessageManager::callAsync ([app] { app->suspended(); }); + #else + app->suspended(); + #endif + } + } + + static void sceneWillEnterForeground() + { + if (auto* app = JUCEApplicationBase::getInstance()) + app->resumed(); + } + + SceneUtils() = delete; + }; } // namespace juce +API_AVAILABLE (ios (13.0)) +@interface JuceAppSceneDelegate : NSObject +@end + +@implementation JuceAppSceneDelegate +SharedResourcePointer windowSceneTracker; +- (void) scene: (UIScene*) scene + willConnectToSession: (UISceneSession*) session + options: (UISceneConnectionOptions*) connectionOptions +{ + if ([scene isKindOfClass: UIWindowScene.class]) + windowSceneTracker->setWindowScene (static_cast (scene)); + else + jassertfalse; +} + +- (void) sceneDidDisconnect: (UIScene*) scene +{ + if (scene == windowSceneTracker->getWindowScene()) + windowSceneTracker->setWindowScene (nullptr); +} + +- (void) sceneDidBecomeActive: (UIScene*) scene +{ + SceneUtils::sceneDidBecomeActive(); +} + +- (void) sceneWillResignActive: (UIScene*) scene +{ + SceneUtils::sceneWillResignActive(); +} + +- (void) sceneDidEnterBackground: (UIScene*) scene +{ + SceneUtils::sceneDidEnterBackground(); +} + +- (void) sceneWillEnterForeground: (UIScene*) scene +{ + SceneUtils::sceneWillEnterForeground(); +} + +- (void) windowScene: (UIWindowScene*) windowScene + didUpdateCoordinateSpace: (id) previousCoordinateSpace + interfaceOrientation: (UIInterfaceOrientation) previousInterfaceOrientation + traitCollection: (UITraitCollection*) previousTraitCollection +{ + windowSceneTracker->setWindowScene (windowScene); +} +@end + #if JUCE_PUSH_NOTIFICATIONS @interface JuceAppStartupDelegate : NSObject #else @@ -56,7 +171,6 @@ namespace juce std::optional initialiser; } -@property (strong, nonatomic) UIWindow *window; - (id) init; - (void) dealloc; - (void) applicationDidFinishLaunching: (UIApplication*) application; @@ -68,6 +182,11 @@ namespace juce - (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*) identifier completionHandler: (void (^)(void)) completionHandler; - (void) applicationDidReceiveMemoryWarning: (UIApplication *) application; + +- (UISceneConfiguration*) application: (UIApplication*) application + configurationForConnectingSceneSession: (UISceneSession*) connectingSceneSession + options: (UISceneConnectionOptions*) options API_AVAILABLE (ios (13.0)); + #if JUCE_PUSH_NOTIFICATIONS - (void) application: (UIApplication*) application @@ -141,64 +260,22 @@ namespace juce - (void) applicationDidEnterBackground: (UIApplication*) application { - if (auto* app = JUCEApplicationBase::getInstance()) - { - #if JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK - appSuspendTask = [application beginBackgroundTaskWithName:@"JUCE Suspend Task" expirationHandler:^{ - if (appSuspendTask != UIBackgroundTaskInvalid) - { - [application endBackgroundTask:appSuspendTask]; - appSuspendTask = UIBackgroundTaskInvalid; - } - }]; - - MessageManager::callAsync ([app] { app->suspended(); }); - #else - ignoreUnused (application); - app->suspended(); - #endif - } + SceneUtils::sceneDidEnterBackground(); } - (void) applicationWillEnterForeground: (UIApplication*) application { - ignoreUnused (application); - - if (auto* app = JUCEApplicationBase::getInstance()) - app->resumed(); + SceneUtils::sceneWillEnterForeground(); } -struct BadgeUpdateTrait -{ - #if JUCE_IOS_API_VERSION_CAN_BE_BUILT (16, 0) - API_AVAILABLE (ios (16)) - static void newFn (UIApplication*) - { - [[UNUserNotificationCenter currentNotificationCenter] setBadgeCount: 0 withCompletionHandler: nil]; - } - #endif - - static void oldFn (UIApplication* app) - { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - app.applicationIconBadgeNumber = 0; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } -}; - - (void) applicationDidBecomeActive: (UIApplication*) application { - ifelse_17_0 (application); - isIOSAppActive = true; + SceneUtils::sceneDidBecomeActive(); } - (void) applicationWillResignActive: (UIApplication*) application { - ignoreUnused (application); - isIOSAppActive = false; - - for (int i = appBecomingInactiveCallbacks.size(); --i >= 0;) - appBecomingInactiveCallbacks.getReference (i)->appBecomingInactive(); + SceneUtils::sceneWillResignActive(); } - (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*)identifier @@ -217,6 +294,17 @@ struct BadgeUpdateTrait app->memoryWarningReceived(); } +- (UISceneConfiguration*) application: (UIApplication*) application + configurationForConnectingSceneSession: (UISceneSession*) connectingSceneSession + options: (UISceneConnectionOptions*) options +{ + auto* result = [UISceneConfiguration configurationWithName: juceStringToNS (TRANS ("Default Configuration")) + sessionRole: connectingSceneSession.role]; + result.delegateClass = JuceAppSceneDelegate.class; + result.sceneClass = UIWindowScene.class; + return result; +} + - (void) setPushNotificationsDelegateToUse: (NSObject*) delegate { _pushNotificationsDelegate = delegate; @@ -520,7 +608,16 @@ Desktop::DisplayOrientation Desktop::getCurrentOrientation() const // query its frame. struct TemporaryWindow { - UIWindow* window = [[UIWindow alloc] init]; + UIWindow* window = std::invoke ([&] + { + if (@available (ios 13, *)) + { + SharedResourcePointer windowSceneTracker; + return [[UIWindow alloc] initWithWindowScene: windowSceneTracker->getWindowScene()]; + } + + return [[UIWindow alloc] init]; + }); ~TemporaryWindow() noexcept { [window release]; } };