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:
parent
2c5b1fbb6f
commit
70a2dd7e15
3 changed files with 223 additions and 55 deletions
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -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]; }
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue