[ASDisplayNode] Provide safeAreaInsets and layoutMargins bridge#685
[ASDisplayNode] Provide safeAreaInsets and layoutMargins bridge#685nguyenhuy merged 13 commits intoTextureGroup:masterfrom
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 |
nguyenhuy
left a comment
There was a problem hiding this comment.
@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
nguyenhuy
left a comment
There was a problem hiding this comment.
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!
| @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.
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.
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.
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.
Let's use the automaticallyRelayoutOnSafeAreaChanges getter here.
| - (void)layoutMarginsDidChange | ||
| { | ||
| ASDisplayNodeAssertMainThread(); | ||
| BOOL automaticallyRelayout; |
There was a problem hiding this comment.
Let's call self.automaticallyRelayoutOnLayoutMarginsChanges here.
|
|
||
| safeArea = ASConcatInsets(safeArea, additionalInsets); | ||
|
|
||
| _node.fallbackSafeAreaInsets = safeArea; |
There was a problem hiding this comment.
- 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.
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.
Good call on making this property private :)
| - (void)nodeViewDidAddGestureRecognizer; | ||
|
|
||
| // Recalculates fallbackSafeAreaInsets for the subnodes | ||
| - (void)_fallbackUpdateSafeAreaOnChildren; |
There was a problem hiding this comment.
Nit: May not needed because _fallbackUpdateSafeAreaOnChildren is implemented and called in ASDisplayNode.mm only.
There was a problem hiding this comment.
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.
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 |
nguyenhuy
left a comment
There was a problem hiding this comment.
@ypogribnyi Sorry for the long delay here. Awesome contribution. Thank you!
| } | ||
| } | ||
|
|
||
| - (void)_fallbackUpdateSafeAreaOnChildren |
There was a problem hiding this comment.
Thanks for the note. I think this implementation and your trade-offs are reasonable. Let's roll with this.
| return; | ||
| } | ||
|
|
||
| _node.viewControllerRoot = YES; |
…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.
UINavigationControllerwith a translucent navigation bar).This PR includes:
safeAreaInsets,insetsLayoutMarginsFromSafeArea,layoutMargins,preservesSuperviewLayoutMarginsproperties and-safeAreaInsetsDidChange,-layoutMarginsDidChangecallbacks onASDisplayNodeall bridged to the underlying view.automaticallyRelayoutOnSafeAreaChangesandautomaticallyRelayoutOnLayoutMarginsChangesflags onASDisplayNodeso we can automatically send-setNeedsLayoutwhen the insets change.A compatibility
safeAreaInsetscalculation algorithm for old iOS versions (for iOS < 11 we have to observe view controller'stopLayoutGuideandbottomLayoutGuidelengths, which is very inconvenient).