-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Reader: Add Discover Channels #23676
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
Conversation
Generated by 🚫 Danger |
|
| App Name | WordPress Alpha |
|
| Configuration | Release-Alpha | |
| Build Number | pr23676-24d5826 | |
| Version | 25.4.1 | |
| Bundle ID | org.wordpress.alpha | |
| Commit | 24d5826 | |
| App Center Build | WPiOS - One-Offs #10895 |
|
| App Name | Jetpack Alpha |
|
| Configuration | Release-Alpha | |
| Build Number | pr23676-24d5826 | |
| Version | 25.4.1 | |
| Bundle ID | com.jetpack.alpha | |
| Commit | 24d5826 | |
| App Center Build | jetpack-installable-builds #9936 |
| /// Pins edges of the view to the edges of the given container. By default, | ||
| /// pins to the superview. | ||
| @discardableResult | ||
| public func pinEdges( |
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.
Nitpick: update the signature to be pin(edges: .all, to: view, ...) instead of pinEdges(.all, to: view, ...)?
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.
It won't be clear what it does with a default argument view.pin(to: view). It has to have the default argument because you'll use .all 99% of the time.
| assertionFailure("view has to be installed in the view hierarchy") | ||
| return [] | ||
| } | ||
| translatesAutoresizingMaskIntoConstraints = false |
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.
Add an assertion (self.isDescendant(of: container)) to make sure self is a subview of container, because this line may not be appropriate, depending on the relationship between self and container.
// OK to set `subview.translatesAutoresizingMaskIntoConstraints = false`
// because subview's frame is set by auto-layout.
subview.pinEdges(to: superview)
// Not okay to set `superview.translatesAutoresizingMaskIntoConstraints = false`
// because we don't know if superview's frame is set by auto-layout or not.
superview.pinEdges(to: subview)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.
make sure self is a subview of container
It's not a strict requirement. You can pin a view to a layout guide or to a sibling view. It should be pretty clear from the method's name what it does. SnapKit, PureLayout, and a couple of other frameworks all pretty much work the same way, so it shouldn't surprise that that's how it works.
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 there is no hierarchical requirement in these two views, we should remove this line, as I mentioned in the example code above:
// Not okay to set `superview.translatesAutoresizingMaskIntoConstraints = false`
// because we don't know if superview's frame is set by auto-layout or not.
superview.pinEdges(to: subview)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.
IIRC UIViewController.view is not framed using auto layout. Maybe you can reproduce the issue I mentioned by using pinEdges(to: subview) on a UIViewController.view.
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.
Yup, you shouldn't pin the edges of the superview to its child, and I added a bit of docs explaining it. It even sounds kind of backwards, doesn't it?
The default signature view.pinEdges() enforces the idea that you are pinning it to a superview because that's the only reasonable option when you don't have a target view.
As a user, you want to avoid writing translatesAutoresizingMaskIntoConstraints. It's a pretty common mistake to forget it, which I've also accidentally done many times when using the existing pinSubviewToAllEdges methods. If you take pretty much every Auto Layout framework, they are designed to ensure you never have to write translatesAutoresizingMaskIntoConstraints. The simple rule is that the receiver of the pin* methods is the view you want to layout using Auto Layout → it's safe to set translatesAutoresizingMaskIntoConstraints to false.
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.
As a user, you want to avoid writing translatesAutoresizingMaskIntoConstraints. [...] If you take pretty much every Auto Layout framework, they are designed to ensure you never have to write translatesAutoresizingMaskIntoConstraints.
That's true. I get it's a bit annoying that we are forced to write this one long line for pretty much every single view in the app. However, the framework (or convenient method in this case) should only set the property when it's appropriate to do so. As I mentioned above, it's not appropriate to call translatesAutoresizingMaskIntoConstraints = true, if self is not framed using auto-layout.
Here is a diff to reproduce the issue:
diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift
index af9eda30d5..ffa871d863 100644
--- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift
+++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift
@@ -101,6 +101,11 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe
view.addSubview(streamVC.view)
streamVC.view.pinEdges()
streamVC.didMove(toParent: self)
+
+ let testView = UIView()
+ testView.backgroundColor = .red
+ view.insertSubview(testView, at: 0)
+ view.pinEdges(to: testView) // Reader goes blank.
}
/// TODO: (tech-debt) the app currently stores the responses from the `/discover`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.
Here is a diff to reproduce the issue:
I added a runtime check to catch this early:
#if DEBUG
if let target = target as? UIView {
assert(!target.isDescendant(of: self), "The target view can't be a descendant for the view")
}
#endif| @discardableResult | ||
| public func pinEdges( | ||
| _ edges: Edge.Set = .all, | ||
| to container: AutoLayoutItem? = 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.
It might be good to make this container part of function signature, so that the relationship is clearer: view.pinEdges(toContainer: anotherView) instead of view.pinEdges(to: anotherView).
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 "container" isn't the best term to use because you can pin the edges to a layout guide view.pinEdges(to: view.safeAreaLayoutGuide) or to a sibling view (not a container). I also replaced the variable name it a more generic term "target".
| pin(.top, topAnchor.constraint(greaterThanOrEqualTo: container.topAnchor, constant: insets.top)) | ||
| pin(.trailing, trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor, constant: -insets.right)) | ||
| pin(.bottom, bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor, constant: -insets.bottom)) | ||
| pin(.leading, leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor, constant: insets.left)) |
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.
It doesn't really make a difference in runtime, but using NSDirectionalEdgeInsets instead of UIEdgeInsets would make this line looks more consistent: pin(.leading, ....insets.leading).
| let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: ReaderCard.entityName()) | ||
| let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) | ||
| let coordinator = persistentContainer.persistentStoreCoordinator | ||
| try coordinator.execute(deleteRequest, with: mainContext) |
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.
NSBatchDeleteRequest API doc says we need to manually merge the changes to the context object. Does this API do that for us? Its API doc is pretty short, and doesn't mention 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.
I found an existing method that removes cards in ReaderCardService and replaced batch delete with it. There aren't going to be many of these cards, so the performance should be fine.
| scrollView.addSubview(channelsStackView) | ||
| scrollView.showsHorizontalScrollIndicator = false | ||
| scrollView.clipsToBounds = false | ||
| channelsStackView.pinEdges() |
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.
Nitpick: it's not obvious what views are involved from this line alone. The default parameter values may save us from typing a few words, but personally, I'd prefer the better readability of having necessary parts in a statement. i.e. channelsStackView.pinEdges(toContainer: self)
Alternatively, using margins instead of pinEdges might be better for conveying the idea that insets are adding between the view and its parent: channelsStackView.margins()
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.
We could add a separate pinEdgesToSuperview method but I didn't want to duplicate this entire signature.
|
Updates:
|
| func clean() { | ||
| coreDataStack.performAndSave { context in | ||
| self.removeAllCards(in: context) | ||
| ReaderCardService.removeAllCards() |
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.
Pass self.coreDataStack to the function.
|
|
||
| // MARK: UITextViewDelegate | ||
|
|
||
| func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { |
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.
What do you think about adding some defensive code here to make sure only /interest URL is handled? Also, maybe add an assertion failure for other URLs.
ab66f82 to
0e9f4c3
Compare


pinEdgesandpinCenterAutoLayout APIs to replace the now deprecated previous inflexible oneschangeLayoutMarginsreaderDiscoverEndpointFFRegression Notes
Potential unintended areas of impact
What I did to test those areas of impact (or what existing automated tests I relied on)
What automated tests I added (or what prevented me from doing so)
PR submission checklist:
RELEASE-NOTES.txtif necessary.Testing checklist: