-
Notifications
You must be signed in to change notification settings - Fork 1.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
[ASDisplayNode] Provide safeAreaInsets and layoutMargins bridge #685
Conversation
…ies to ASDisplayNode * Add layoutMargins bridged to the underlying view * Add safeAreaInsets bridged to the underlying view * Add fallback calculation of safeAreaInsets for old iOS versions * Add automaticallyRelayoutOnSafeAreaChanges and automaticallyRelayoutOnLayoutMarginsChanges properties * Add additionalSafeAreaInsets property to ASViewController for compatibility with old iOS versions
🚫 CI failed with log |
🚫 CI failed with log |
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.
@ypogribnyi Thanks for submitting the PR. Please take a look at failing tests whenever you have time.
This also fixes tests.
🚫 CI failed with log |
🚫 CI failed with log |
@nguyenhuy, I fixed the tests that were failing because of my code. Now the log shows ticks ✓ for all tests and a strange error 'Early unexpected exit, operation never finished bootstrapping - no restart will be attempted' from xcodebuild. Is it because some tests silently fail, or is it some problem with Xcode? |
🚫 CI failed with log |
@ypogribnyi thanks for developing this! I think we could probably merge it soon if we can get it passing CI (apologies for the long delay over the holiday). Perhaps you could sync to latest and make any final tweaks you had in mind and see if the build server is cooperating now? |
# Conflicts: # CHANGELOG.md
🚫 CI failed with log |
@appleguy I have finally fixed failing tests. Have nothing else to add here. |
… safeAreaInsets # Conflicts: # Source/Private/_ASPendingState.mm
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.
Thanks for working on this, @ypogribnyi. Overall, great work! I saw you took extra steps to make sure the diff follows existing practices and conventions, which is awesome!
Source/ASDisplayNode.h
Outdated
@@ -719,6 +733,17 @@ extern NSInteger const ASDefaultDrawingPriority; | |||
@property (nonatomic, assign) BOOL autoresizesSubviews; // default==YES (undefined for layer-backed nodes) | |||
@property (nonatomic, assign) UIViewAutoresizing autoresizingMask; // default==UIViewAutoresizingNone (undefined for layer-backed nodes) | |||
|
|||
// Content margins | |||
@property (nonatomic, assign) UIEdgeInsets layoutMargins; |
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.
Regarding documentation, I think it's good to remind users that if they access this property, as well as safeAreaInsets
, during a layout pass, they should enable automaticallyRelayoutOnSafeAreaChanges
and automaticallyRelayoutOnLayoutMarginsChanges
. That is because their layout relies on these properties.
Source/ASDisplayNode.mm
Outdated
_fallbackSafeAreaInsets = insets; | ||
} | ||
|
||
BOOL needsManualUpdate = !AS_AT_LEAST_IOS11 || _flags.layerBacked; |
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.
Warning: accessing _flags
without instance lock. Let's move this line into the scope above.
Source/ASDisplayNode.mm
Outdated
} | ||
|
||
BOOL needsManualUpdate = !AS_AT_LEAST_IOS11 || _flags.layerBacked; | ||
BOOL updatesLayoutMargins = needsManualUpdate && self.insetsLayoutMarginsFromSafeArea; |
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.
Calling self.insetsLayoutMarginsFromSafeArea
is fine here as the getter acquires the lock itself. However, given the fact that we did acquire the lock just a moment ago (in the scope above), let's try to grab it during that time. One common pattern we use frequently is to add an internal _locked_
version of the getter. See isNodeLoaded
and _locked_isNodeLoaded
as an example.
- (void)safeAreaInsetsDidChange | ||
{ | ||
ASDisplayNodeAssertMainThread(); | ||
BOOL automaticallyRelayout; |
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.
Let's use the automaticallyRelayoutOnSafeAreaChanges
getter here.
- (void)layoutMarginsDidChange | ||
{ | ||
ASDisplayNodeAssertMainThread(); | ||
BOOL automaticallyRelayout; |
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.
Let's call self.automaticallyRelayoutOnLayoutMarginsChanges
here.
|
||
safeArea = ASConcatInsets(safeArea, additionalInsets); | ||
|
||
_node.fallbackSafeAreaInsets = safeArea; |
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.
- This node is guaranteed to be view-backed and loaded. The only reason the fallback insets is needed here is that the runtime is iOS 10 or earlier. Let's take advantage of that and avoid doing unnecessary work if possible.
Source/ASViewController.mm
Outdated
[super setAdditionalSafeAreaInsets:additionalSafeAreaInsets]; | ||
} | ||
|
||
_fallbackAdditionalSafeAreaInsets = additionalSafeAreaInsets; |
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.
Should we recalculate the root node's insets here?
* YES here only means that this node is used as an ASViewController node. It doesn't mean that this node is a root of | ||
* ASDisplayNode hierarchy, e.g. when its view controller is parented by another ASViewController. | ||
*/ | ||
@property (nonatomic, assign, getter=isViewControllerRoot) BOOL viewControllerRoot; |
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 call on making this property private :)
@@ -326,6 +335,9 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo | |||
*/ | |||
- (void)nodeViewDidAddGestureRecognizer; | |||
|
|||
// Recalculates fallbackSafeAreaInsets for the subnodes | |||
- (void)_fallbackUpdateSafeAreaOnChildren; |
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.
Nit: May not needed because _fallbackUpdateSafeAreaOnChildren
is implemented and called in ASDisplayNode.mm
only.
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.
This is also used by -[ASDisplayNode safeAreaDidChange]
, which is implemented in ASDisplayNode+UIViewBridge.mm
.
} | ||
} | ||
|
||
if (__loaded(self) && (!AS_AT_LEAST_IOS11 || _flags.layerBacked)) { |
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.
Warning: accessing _flags
without the lock.
* Update documentation for layoutMargins and safeAreaInsets properties. Suggest that users set the automaticallyRelayout* flags to ensure that their layout is synchronized to the margin's values. * Fix accessing ASDisplayNode internal structures without a lock. * Add shortcut in -[ASDisplayNode _fallbackUpdateSafeAreaOnChildren] to skip a child when possible. * Add shortcut in ASViewController to avoid fallback safe area insets recalculation in iOS 11. Fix fallback safe area insets recalculation when the additionalSafeAreaInsets are set. * Add debug check that a view controller's node is never reused without its view controller, so the viewControllerRoot flag value is always consistent. * Use getters instead of reading ivars directly in -layoutMarginsDidChange and -safeAreaInsetsDidChange.
… safeAreaInsets # Conflicts: # CHANGELOG.md
@nguyenhuy, thank you for the detailed review! |
This is awesome @ypogribnyi. I wonder if similar could be done to expose the readableContentGuide within cell nodes etc? Think it could even just be the layoutFrame from the readable content guide that is accessible so you can then constrain the layout to that within layoutSpecThatFits: |
@ypogribnyi do you have any cycles to wrap up remaining requests from Huy? Let us know either way - if you need someone to take this over, I'm not sure who might be able to, but then we'd be progressing towards landing it. This is a great contribution overall, so thanks for taking the time to work with us! |
It would be super helpful to have a |
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.
@ypogribnyi Sorry for the long delay here. Awesome contribution. Thank you!
} | ||
} | ||
|
||
- (void)_fallbackUpdateSafeAreaOnChildren |
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.
Thanks for the note. I think this implementation and your trade-offs are reasonable. Let's roll with this.
@@ -73,10 +74,14 @@ - (void)_initializeInstance | |||
if (_node == nil) { | |||
return; | |||
} | |||
|
|||
_node.viewControllerRoot = 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.
👍
…ge (TextureGroup#685)" This reverts commit 7f01b89.
…ureGroup#685) * [ASDisplayNode] Add safeAreaInsets, layoutMargins and related properties to ASDisplayNode * Add layoutMargins bridged to the underlying view * Add safeAreaInsets bridged to the underlying view * Add fallback calculation of safeAreaInsets for old iOS versions * Add automaticallyRelayoutOnSafeAreaChanges and automaticallyRelayoutOnLayoutMarginsChanges properties * Add additionalSafeAreaInsets property to ASViewController for compatibility with old iOS versions * Provide safeAreaInsets for layer-backed nodes. This also fixes tests. * Fix crash when insetsLayoutMarginsFromSafeArea is set from a background thread * Changes requested at code review: * Update documentation for layoutMargins and safeAreaInsets properties. Suggest that users set the automaticallyRelayout* flags to ensure that their layout is synchronized to the margin's values. * Fix accessing ASDisplayNode internal structures without a lock. * Add shortcut in -[ASDisplayNode _fallbackUpdateSafeAreaOnChildren] to skip a child when possible. * Add shortcut in ASViewController to avoid fallback safe area insets recalculation in iOS 11. Fix fallback safe area insets recalculation when the additionalSafeAreaInsets are set. * Add debug check that a view controller's node is never reused without its view controller, so the viewControllerRoot flag value is always consistent. * Use getters instead of reading ivars directly in -layoutMarginsDidChange and -safeAreaInsetsDidChange. * Minor change in CHANGELOG * Minor change in ASDisplayNodeTests.mm
My attempt to implement #658.
Safe area insets are quite important when designing for iPhone X or when dealing with view controller containers with overlapping bars (e.g.
UINavigationController
with a translucent navigation bar).This PR includes:
safeAreaInsets
,insetsLayoutMarginsFromSafeArea
,layoutMargins
,preservesSuperviewLayoutMargins
properties and-safeAreaInsetsDidChange
,-layoutMarginsDidChange
callbacks onASDisplayNode
all bridged to the underlying view.automaticallyRelayoutOnSafeAreaChanges
andautomaticallyRelayoutOnLayoutMarginsChanges
flags onASDisplayNode
so we can automatically send-setNeedsLayout
when the insets change.A compatibility
safeAreaInsets
calculation algorithm for old iOS versions (for iOS < 11 we have to observe view controller'stopLayoutGuide
andbottomLayoutGuide
lengths, which is very inconvenient).