-
Notifications
You must be signed in to change notification settings - Fork 24.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Alert leaving behind a window and view controller (issue 32304) #32305
Fix Alert leaving behind a window and view controller (issue 32304) #32305
Conversation
In hide, the window is explicitly removed from the screen and hidden and the root view controller is set to nil to prevent any possible retain cycle between them
The circlcie failure of |
Base commit: 34b7d22 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was facing the same issue, and come up with the _alertWindow.hidden = YES
solution, then found this PR, but it's just a suggestion anyway, not sure about the implications of one vs other besides the apple recommendation.
@@ -35,6 +35,9 @@ - (void)show:(BOOL)animated completion:(void (^)(void))completion | |||
|
|||
- (void)hide | |||
{ | |||
[_alertWindow resignKeyWindow]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a recommendation that we shouldn't call this directly on Apple docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, my bad. I guess I made an invalid assumption that UIWindow was like NSWindow
[_alertWindow setHidden: YES]; | ||
_alertWindow.rootViewController = nil; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering if _alertWindow.hidden = YES
is enough to get the expected behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, let me experiment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this will work
if (@available(iOS 13.0, *)) {
_alertWindow.windowScene = nil;
}
https://developer.apple.com/documentation/uikit/uiwindow/3197962-windowscene?language=objc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danibonilha thanks for your help. setting hidden was enough. I confirmed using Xcode debug view hierarchy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(If it hadn't worked you'd see another UIWindow hanging out in the scene)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resignKeyWindow should not be called directly
@danibonilha @paddlefish Thank you for the investigation and the fix! I can confirm that this bug happens to me with both iOS 15 and iOS 14.5. |
@lunaleaps has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
@@ -35,6 +35,7 @@ - (void)show:(BOOL)animated completion:(void (^)(void))completion | |||
|
|||
- (void)hide | |||
{ | |||
[_alertWindow setHidden: YES]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[_alertWindow removeFromSuperview]; feels more correct to me - have you tried that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From removeFromSuperview docs:
If the view’s superview is not nil, the superview releases the view.
AFAIK there shouldn't be any superview for _alertWindow
(UIWindow
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The correct way to remove a UIWindow
in iOS is to set its hidden property to YES
.
Also, needs to remove all references to the window and ARC will automatically free this UIWindow
.
The line after this change, set the _alertWindow
reference to nil
, but the window is already associated with a scene (see the screenshots from this PR). So we also need to remove the windowScene
from that window, as recommended by Apple: https://developer.apple.com/documentation/uikit/uiwindowscene/3198091-windows.
To remove the window from the current scene, or move it to a different scene, change the value of the window's windowScene property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@paddlefish since I can't push this change to your branch, I created another PR based on your changes and my comment and co-authorized you. @philIip & @lunaleaps you are welcome to take a look: #32833.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@asafkorem why wouldn't the alertWindow have a superview?
also, i'm not convinced that setting it to hidden is really related to this core issue. the issue is that setting _alertWindow to nil is not removing it from the view hierarchy (its retain count is not hitting 0), thus it is consuming touches. setting it to hidden unblocks the issue but doesn't fix the actual problem.
the latter point about the windowScene makes more sense in terms of the window lingering in memory somewhere. my suggestion with removing from the superview is potentially this UIWindow is still someone's subview and needs to be removed explicitly for the superview to clean it up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@asafkorem why wouldn't the alertWindow have a superview?
@philIip the _alertWindow
is a UIWindow
, which doesn't have any superview by default, and there isn't any call where we define it as a subview of another view. Therefore, calling removeFromSuperview
will do nothing, as the doc says (#32305 (comment)).
i'm not convinced that setting it to hidden is really related to this core issue
Setting the alert's window to be hidden from the hide
method ensures that this UI operation is handled correctly, regardless of the memory management.
Removing any strong reference (made by this class) to this object is done to ensure that this window will be released from memory once no other object is pointing at it.
Theoretically, this window could be retained from another place, for example some object that kept a strong reference to the topmost visible window ATM this window was presented, or some object that keeps a strong reference to the generated alert view-controller (that has a strong reference to this window). In these examples, the window will still be stuck after calling hide
until its reference count will be zero (same as the original bug).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@asafkorem I understand, where i'm coming from is that it is not impossible for UIWindow to have a superview (it is a UIView after all) since we can't control or know what UIKit does under the hood with the UIWindow management during runtime, and that could be a lingering strong reference. It's unlikely, but it's a possibility as well since the API allows it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@philIip that's right, it is possible by the API, but this should not happen in this case, since this class present and manage this window, and it does not do that.
If that happens, for any reason, it might indicate on some other bug that must be addressed, and I strongly recommend not to obscure this potential bug from this method.
We can close this now. See #32833 |
Summary: Resolves this issue: #32304. **NOTE:** This PR is based on a prior PR for this fix: #32305, I've co-authorized its creator for this change (paddlefish). Without this change, calling to hide an alert, leaves a `UIWindow` that blocks user interactions with the screen. The correct way to remove a `UIWindow` in iOS is to set its hidden property to `YES`. Also, it is required to remove all references to the window (the associated `windowScene` for example) and ARC will automatically free this `UIWindow`. The line after this change, set the `_alertWindow` reference to `nil`, but the window is already associated with a scene (see the screenshots from [this PR](#32305 (comment))). So we also need to remove the `windowScene` from that window, as recommended by Apple: https://developer.apple.com/documentation/uikit/uiwindowscene/3198091-windows. >To remove the window from the current scene, or move it to a different scene, change the value of the window's windowScene property. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Fixed] - remove alert's window when call to `hide`. Pull Request resolved: #32833 Test Plan: See #32305 Reviewed By: hramos Differential Revision: D33460430 Pulled By: lunaleaps fbshipit-source-id: b13c2c7ee6404f1e1c787265bc4af8a31005bcf1
Summary: Resolves this issue: #32304. **NOTE:** This PR is based on a prior PR for this fix: #32305, I've co-authorized its creator for this change (paddlefish). Without this change, calling to hide an alert, leaves a `UIWindow` that blocks user interactions with the screen. The correct way to remove a `UIWindow` in iOS is to set its hidden property to `YES`. Also, it is required to remove all references to the window (the associated `windowScene` for example) and ARC will automatically free this `UIWindow`. The line after this change, set the `_alertWindow` reference to `nil`, but the window is already associated with a scene (see the screenshots from [this PR](#32305 (comment))). So we also need to remove the `windowScene` from that window, as recommended by Apple: https://developer.apple.com/documentation/uikit/uiwindowscene/3198091-windows. >To remove the window from the current scene, or move it to a different scene, change the value of the window's windowScene property. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Fixed] - remove alert's window when call to `hide`. Pull Request resolved: #32833 Test Plan: See #32305 Reviewed By: hramos Differential Revision: D33460430 Pulled By: lunaleaps fbshipit-source-id: b13c2c7ee6404f1e1c787265bc4af8a31005bcf1
In hide, the window is explicitly removed from the screen and hidden and the root view controller is set to nil to prevent any possible retain cycle between them
This fixes issue 32304 #32304
Summary
Restores ability to display an alert, even on iOS 15, without the user losing all ability to interact with the screen.
Changelog
[iOS] [Fixed] - Fix Alert.alert causing user interaction to be blocked on iOS 15 (issues/32304)
Test Plan
Build and run a react-native app on iOS 15. Display an alert and dismiss it.
Now try to interact with the app some more.
Expected: Buttons, scrollbars, etc.. in the main view of the app still function.
Wiithout this fix: It's not possible to interact with those components, as an invisible window is covering the entire screen, blocking all user interaction.