-
Notifications
You must be signed in to change notification settings - Fork 213
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
Add UIViewPropertyAnimator reactive extension #151
Conversation
16ed3e9
to
d9d6bbd
Compare
d9d6bbd
to
36a7b90
Compare
Hey @twittemb - Looks great! |
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.
Hey @twittemb - Great PR!
Left a few points for change & discussion.
Regarding tests - Can we at least perform some basic unit tests, e.g.: - that after every "completion" the object arrives at its designated position/transform/etc ?
Thanks again!
import UIKit | ||
import PlaygroundSupport | ||
|
||
class MyViewController : UIViewController { |
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's the purpose of this file?
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.
My bad ... removed !
CHANGELOG.md
Outdated
@@ -4,6 +4,11 @@ Changelog | |||
master | |||
----- | |||
|
|||
3.3.0 | |||
----- | |||
- added `animate` operator on UIViewPropertyAnimator |
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 changing both of these to Added UIViewPropertyAnimator Reactive Extensions
?
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.
done.
@@ -40,6 +40,7 @@ | |||
- [filterMap()](filterMap) operator, filters out some values and maps the rest (replaces `filter` + `map` combo) | |||
- [Observable.fromAsync()](fromAsync) constructor, translates an async function that returns data through a completionHandler in a function that returns data through an Observable | |||
- [ofType()](ofType) operator, filters the elements of an observable sequence, if that is an instance of the supplied type. | |||
- [UIViewPropertyAnimator.rx.animate](animate) operator, provides a Completable that when subscribed to starts the animation and completes once the animation is ended |
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.
You're right about the fact this is the first Extension we have on RxCocoa. I'm not 100% sure on how to name this thing.
Let's change for now to this - I also removed the note about "subscribing" because I think its implicit in the fact its an Observable:
**UIViewPropertyAnimator** [animate](animate) operator, returns a Completable that completes as soon as the animation ends.
eg.
- UIViewPropertyAnimator animate operator, returns a Completable that completes as soon as the animation ends.
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.
done.
self.view.addSubview(self.box3) | ||
|
||
// trigger the animation chain | ||
self.animator1.rx.animate |
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.
Two quick notes about this example:
- I'd love it if the animations could be different, different speeds or scale and translate etc
- It would be great if we could have a button on screen to trigger the animation. That way its obvious how to trigger the animation from a user event
- Can we add a note somewhere about the fact they should open the Assistant Editor to see the live 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.
done.
/// Completable that when subscribed to, starts the animation | ||
/// and completes once the animation is ended | ||
public var animate: Completable { | ||
return Completable.create(subscribe: { (completable) -> Disposable in |
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 think we usually use the shortened syntax here, e.g.
Completable.create { completable in
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.
done.
/// Completable that when subscribed to, starts the animation | ||
/// and completes once the animation is ended | ||
public var animate: Completable { | ||
return Completable.create(subscribe: { (completable) -> Disposable in |
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 a let strongBase = base
outside of the closure? I think that otherwise there's a risk of retaining self here.
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.
done but with a slightly different way. Until now I didn't notice we could use the capture list that way:
{ [base] completable in ... }
What do you think of this syntax ?
public var animate: Completable { | ||
return Completable.create(subscribe: { (completable) -> Disposable in | ||
|
||
self.base.addCompletion({ (position) in |
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.
Same here about trailing closure syntax, and I'd also use a guard:
base.addCompletion { position in
guard position == .end else { return }
completable(.completed)
}
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.
done.
/// | ||
/// - Parameter delay: the delay to apply to the animation start | ||
/// - Returns: the Completable that will send .completed once the animation is ended | ||
public func animate(afterDelay delay: Double) -> Completable { |
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.
can't we squash down both the method and property to a single method with a default value ?
e.g.
public func animate(afterDelay delay: Double = 0) -> Completable
/// Usage
animator.rx.animate()
animator.rx.animate(afterDelay: 5)
I think it would also better be on-par with the fact its an "operator". Computed properties have an implicit meaning of just returning things and not having side-effects necessarily.
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.
done. Good catch.
} | ||
|
||
@available(iOSApplicationExtension 10.0, *) | ||
extension UIViewPropertyAnimator { |
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.
Why is this needed? I think since UIViewPropertyAnimator is NSObject, you get this out-of-the-box.
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.
done.
36a7b90
to
4b69891
Compare
Hi @freak4pc All points have been addressed, even the Unit Test. I hope this will meet your expectations. Thibault. |
4b69891
to
7de5009
Compare
This commit adds a Rx extension to UIViewPropertyAnimator. The purpose is to trigger a Completable(.completed) once the animation is ended so it is easy to chain them with the syntactic sugar 'andThen'.
ccabecd
to
e78ac14
Compare
Hey @twittemb, I made a few minor changes:
I also rebased your changes on top of master since there were some changes. There are two final issues we need to resolve:
Thanks again for the hard work. @twittemb |
Hi @freak4pc I have pushed a new version that prevents the demo from crashing. I disable the button once the animation is completed. If the developer wants to see it again he will have to launch the demo again. Seems an acceptable compromise. |
I certainly don't want to tell you what to do, @twittemb. But UIViewPropertyAnimator is a nasty little API that crashes as soon as the hosting app doesn't take enormous care of its under-documented fragile inner state machine. And if it works on iOS11, this doesn't mean it does on iOS10. It's really nasty. I'd... spend a little more time tuning this RxSwiftExt addition, if I were you and if I had time. This is just experience talking: the crash experienced by @freak4pc rings a big warning bell in my mind. |
I'm afraid I did spread some FUD in my previous message, I apologize. I'll try to put it in another, more positive way: A demo app is a great way to check the ergonomics of a proposed API in context. If the most simple way to use an API can lead to a crash, then maybe something has to be fixed. button.rx.tap
.flatMap { [unowned self] in
self.animator1.rx.animate()
.andThen(self.animator2.rx.animate(afterDelay: 0.2))
.andThen(self.animator3.rx.animate(afterDelay: 0.1))
.debug("animation sequence")
}
.subscribe() The demo code above looks pretty innocuous, and can be described as the "most simple way to use the API". (Side note: It would be great if this code would never crash (and the sample code can stay as it is), or, if it crashes, explains how to avoid the crash (and the sample code would be updated with the mandatory work-around that avoids the crash, along with a comment that explains everything). This would improve this PR with nice properties: ergonomics check, and proper documentation of eventual caveats. |
Hi @groue Thanks for your feedback ... No offense taken 😊 In the last version I pushed, the demo app doesn't crash anymore BUT you make a fair point saying that we should be careful not to spread bad practices. My PR is about "bootstrapping" a Rx way of chaining animations and hopefully it will be amended with new features in the future. My point is that it is up to the developer to use UIPropertyViewAnimator is a safe way, with or without an Rx extension. Perhaps, if you're ok with that, I could add a comment in the demo app to warn the developers about handling correctly the inner state of the animation ? See ya. |
Sure, I'd like to know how that crash can be avoided. Put in another way: is it a known issue of the PR (<insert work-around here>), or a bug in UIKit? It's still a little unclear to me :-) |
It's not a bug in UIKit, it's an expected and confusing behavior of UIKit where you can't "reuse" them AFAIK.
https://developer.apple.com/documentation/uikit/uiviewanimating/1649786-startanimation The other option is to use the class method like UIView.animate(withDuration...), e.g. : In practice, it might be better to make a Reactive wrapper for the static method and not the Otherwise, should we just create the |
5b8e6a9
to
0433c25
Compare
@twittemb @groue I think this is a good solution. Also, when the user doesn't provide a delay (delay = 0), I call the regular |
Thanks @freak4pc. Your explanation is pretty clear, and the sample code is much better (and more playful)! |
Right, good point :) Since we're creating them fresh anyways. |
@freak4pc do you want me to make the fix ? |
@twittemb I'll amend my last commit, no worries. |
0433c25
to
48f6722
Compare
Donezo @twittemb! I'll think about how to add this to the Readme until tomorrow. For now, I'm inviting @RxSwiftCommunity/contributors to add any feedback on this Reactive Extension before its merged. Thanks ! :) |
48f6722
to
352b6cd
Compare
0282982
to
2c5c03e
Compare
@twittemb Let me know your thoughts on the proposed Readme. |
Hi guys It sounds great to me !!! Thanks a lot for the help. |
Thanks ! See ya 😊 |
This commit adds a Rx extension to UIViewPropertyAnimator.
The purpose is to trigger a Completable(.completed) once the
animation is ended so it is easy to chain them with the syntactic
sugar 'andThen'.
I'm note sure how to amend the Readme file because it seems to be the first UIKit extension, it is not really a Rx new operator. Should we start a dedicated section in the Readme ?
I wrote an example in the Playground but I don't know if I should add UI Unit Tests as well ? I'm not very comfortable with UI Unit Tests.