I want a UILabel
to fade out and in when changing its text
. This animation should not be applied on initial setting to prevent a flicker when the view first appears. It's possible that my Reactive Cocoa solution is preferrable to its “traditional” counterpart, but they really both suck.
I can't get away from this good piece of advice from David Rönnqvist:
If the animation mysteriously went away, the model value should be the expected end state.
I'm somewhat misapplying Rönnqvist's statement – the text
property of a UILabel
is orthogonal to the concerns of a backing model or presentation CALayer
– but I think it's an applicable goal. I have two tasks and I want them decoupled:
- The
UILabel
needs to have itstext
changed over time. This is a binding that I should declare,RAC(self.formTitleLabel, text) = RACObserver(self.viewModel, instructionText)
. - I want this change to be animated, but that should be tangential – the text needs to change, the animation is just for style. There's also a small amount of logic behind this style – only applying the animation after the first change.
In both examples, the text
is updated in if-else
branches, one of which is buried within some completion
block. In the RAC example, we're getting its nicer KVO-like API, but we're not getting the declarative code that better expresses our intent. To add insult to injury, there's this empty -subscribeNext:
that's quite gross.
Clearly, there's room for improvement.
I guess I could grab the -updateFormTitleText:animated:
method from the imperative example and lift it into our reactive world:
- (void)viewDidLoad
{
[super viewDidLoad];
[self rac_liftSelector:@selector(updateFormTitleText:animated:)
withSignalsFromArray:@[
[RACObserve(self.viewModel, instructionText) take:1],
[RACSignal return:@NO],
]];
[self rac_liftSelector:@selector(updateFormTitleText:animated:)
withSignalsFromArray:@[
[RACObserve(self.viewModel, instructionText) skip:1],
[RACSignal return:@YES],
]];
}
- (void)updateFormTitleText:(NSString *)text animated:(BOOL)animated
{
if (animated) {
[UIView
animateWithDuration:0.2
animations:^{
self.formTitleLabel.alpha = 0.0;
}
completion:^(BOOL finished) {
self.formTitleLabel.text = text;
[UIView animateWithDuration:0.2 animations:^{
self.formTitleLabel.alpha = 1.0;
}];
}];
}
else self.formTitleLabel.text = text;
}
I think the intent is still too obfuscated, and there doesn't appear to be a way to avoid intermingling the property update and the animations. An improvement, I suppose.
Maybe something like this?