1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

UIViewComponentPeer: Adopt the UIScene lifecycle on iOS 13+

This commit is contained in:
reuk 2025-06-04 21:47:03 +01:00
parent 2c5b1fbb6f
commit 70a2dd7e15
No known key found for this signature in database
3 changed files with 223 additions and 55 deletions

View file

@ -117,7 +117,7 @@ std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (co
return iOSGlobals::currentlyFocusedPeer;
});
NSUniquePtr<UIAlertController> alert;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageBox)
};

View file

@ -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<UIWindowScene*> (windowScene);
}
void addListener (WindowSceneTrackerListener& l)
{
listeners.add (&l);
}
void removeListener (WindowSceneTrackerListener& l)
{
listeners.remove (&l);
}
private:
ListenerList<WindowSceneTrackerListener> 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<JuceTextView> hiddenTextInput { [[JuceTextView alloc] initWithOwner: this] };
NSUniquePtr<JuceTextInputTokenizer> tokenizer { [[JuceTextInputTokenizer alloc] initWithPeer: this] };
SharedResourcePointer<WindowSceneTracker> 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<UIView*> (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);
}
//==============================================================================

View file

@ -44,8 +44,123 @@ namespace juce
// This is an internal list of callbacks (but currently used between modules)
Array<AppInactivityCallback*> 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<BadgeUpdateTrait> ([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<UIWindowSceneDelegate>
@end
@implementation JuceAppSceneDelegate
SharedResourcePointer<WindowSceneTracker> windowSceneTracker;
- (void) scene: (UIScene*) scene
willConnectToSession: (UISceneSession*) session
options: (UISceneConnectionOptions*) connectionOptions
{
if ([scene isKindOfClass: UIWindowScene.class])
windowSceneTracker->setWindowScene (static_cast<UIWindowScene*> (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<UICoordinateSpace>) previousCoordinateSpace
interfaceOrientation: (UIInterfaceOrientation) previousInterfaceOrientation
traitCollection: (UITraitCollection*) previousTraitCollection
{
windowSceneTracker->setWindowScene (windowScene);
}
@end
#if JUCE_PUSH_NOTIFICATIONS
@interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate, UNUserNotificationCenterDelegate>
#else
@ -56,7 +171,6 @@ namespace juce
std::optional<ScopedJuceInitialiser_GUI> 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<BadgeUpdateTrait> (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> windowSceneTracker;
return [[UIWindow alloc] initWithWindowScene: windowSceneTracker->getWindowScene()];
}
return [[UIWindow alloc] init];
});
~TemporaryWindow() noexcept { [window release]; }
};