-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[Notifications Refresh] Resolve an issue where the comment moderation sheet not resizing properly when comment status changes #23272
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
Changes from all commits
7a88eaa
5fc2145
89afc01
91d9a2b
62f7923
7832ce2
e1e6826
304dfc9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import SwiftUI | ||
|
|
||
| private struct SizePreferenceKey: PreferenceKey { | ||
| static var defaultValue: CGSize = .zero | ||
|
|
||
| static func reduce(value: inout CGSize, nextValue: () -> CGSize) { | ||
| value = nextValue() | ||
| } | ||
| } | ||
|
|
||
| private struct SizeModifier: ViewModifier { | ||
| let size: (CGSize) -> Void | ||
|
|
||
| private var sizeView: some View { | ||
| GeometryReader { geometry in | ||
| Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size) | ||
| } | ||
| } | ||
|
|
||
| func body(content: Content) -> some View { | ||
| content.background( | ||
| sizeView | ||
| .onPreferenceChange(SizePreferenceKey.self, perform: { value in | ||
| size(value) | ||
| }) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| extension View { | ||
| func readSize(_ size: @escaping (CGSize) -> Void) -> some View { | ||
| modifier(SizeModifier(size: size)) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,7 @@ private extension CommentDetailContentTableViewCell { | |
| hostingController.rootView = content | ||
| } else { | ||
| let hostingController = UIHostingController<CommentContentHeaderView>(rootView: content) | ||
| hostingController.view.setContentCompressionResistancePriority(.required, for: .vertical) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sometimes the comment cell header is compressed ( height is 0 ) and a constraints ambiguity warning is thrown in the console. Setting the compression resistance fixed this issue. This change is not part of the fix, but I thought of including in this PR because it's just a 1 line. |
||
| hostingController.view.backgroundColor = .clear | ||
| hostingController.willMove(toParent: parent) | ||
| stackView.insertArrangedSubview(hostingController.view, at: 0) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| import UIKit | ||
| import SwiftUI | ||
|
|
||
| final class CommentModerationSheetHostingView: UIView { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the view that fixes this bug. For context, the bug was occurring because the moderation hosting view was not resizing automatically when its content changed. This is a known issue in UIKit and SwiftUI integration, where the hosting view doesn't resize when the SwiftUI view's size changes. While it's possible to resize the hosting view by calling There is a workaround to resolve this issue and it works as following:
|
||
|
|
||
| private var hostingController: UIHostingController<Content>? | ||
| private var intrinsicContentSizeChangeObservation: NSKeyValueObservation? | ||
|
|
||
| init(viewModel: CommentModerationViewModel, | ||
| parent: UIViewController, | ||
| sizeChanged: @escaping (CGSize) -> Void) { | ||
| super.init(frame: .zero) | ||
| self.setup( | ||
| with: viewModel, | ||
| sizeChanged: sizeChanged, | ||
| parent: parent | ||
| ) | ||
| } | ||
|
|
||
| required init?(coder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|
|
||
| private func setup( | ||
| with viewModel: CommentModerationViewModel, | ||
| sizeChanged: @escaping (CGSize) -> Void, | ||
| parent: UIViewController | ||
| ) { | ||
| self.backgroundColor = .clear | ||
| let content = Content(viewModel: viewModel, sizeChanged: sizeChanged) | ||
| let controller = UIHostingController(rootView: content) | ||
| controller.view.translatesAutoresizingMaskIntoConstraints = false | ||
| controller.view.backgroundColor = .clear | ||
| controller.willMove(toParent: parent) | ||
| self.addSubview(controller.view) | ||
| self.pinSubviewToAllEdges(controller.view) | ||
| parent.addChild(controller) | ||
| controller.didMove(toParent: parent) | ||
| self.hostingController = controller | ||
| } | ||
|
|
||
| /// There was a bug where the moderation view did not resize correctly when the moderation view state changed, | ||
| /// resulting in an incorrect view height after state transitions. | ||
| /// | ||
| /// To address this bug, the hosting view was laid out to the edges of `viewController.view` to provide enough space | ||
| /// for the moderation view to animate smoothly. However, this setup caused the hosting view to intercept touch events, | ||
| /// preventing them from passing through to underlying views. | ||
| /// | ||
| /// This custom `hitTest` method resolves the touch event handling issue by ensuring that touch events are forwarded to | ||
| /// the appropriate subview or parent view. | ||
| /// | ||
| /// This method has unit tests to verify its functionality. | ||
| override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ℹ️ I'm planning to unit test this method.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in 7832ce2
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding a short documentation to this would make sense I think as it isn't very obvious why it is needed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in 304dfc9 |
||
| guard let hitView = super.hitTest(point, with: event), | ||
| let hostingView = self.hostingController?.view, | ||
| let parent = superview, | ||
| hitView === hostingView | ||
| else { | ||
| return super.hitTest(point, with: event) | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the touch happens inside the moderation view (for example, if the user taps the |
||
|
|
||
| // Iterate through the parent's subviews to find the view that should respond to the touch event | ||
| for subview in parent.subviews where subview !== self { | ||
| let point = convert(point, to: subview) | ||
| if let respondingView = subview.hitTest(point, with: event) { | ||
| return respondingView | ||
| } | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the hosting view is tapped, the touch is forwarded to one of the hosting view's siblings. In the case of the |
||
|
|
||
| // If no subviews are hit, return the parent view | ||
| return parent | ||
| } | ||
|
|
||
| private struct Content: View { | ||
| let viewModel: CommentModerationViewModel | ||
| let sizeChanged: (CGSize) -> Void | ||
|
|
||
| var body: some View { | ||
| VStack(spacing: 0) { | ||
| Spacer() | ||
| CommentModerationView(viewModel: viewModel) | ||
| .readSize(sizeChanged) | ||
| } | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.