How can I guarantee order of subscriber messages in ReactiveCocoa? - objective-c

I want to do various tasks when self.personsArray is updated as follows:
- (void)viewDidAppear:(BOOL)animated
{
#weakify(self)
[RACObserve(self, personsArray) subscribeNext:^(NSArray *personsArray) {
#strongify(self)
// update a set of views
[self.view setNeedsUpdateConstraints];
[self.view setNeedsLayout];
}];
[RACObserve(self, personsArray) subscribeNext:^(NSArray *personsArray) {
#strongify(self)
// update a second set of views
[self.view setNeedsUpdateConstraints];
[self.view setNeedsLayout];
}];
[RACObserve(self, personsArray) subscribeNext:^(NSArray *personsArray) {
#strongify(self)
[UIView animateWithDuration:0.4 animations:^{
[self.view layoutIfNeeded];
}];
}];
}
I prefer separating the work in distinct blocks because it provides a logical separation. I'd like to guarantee the the last subscription message (the message that executes the animation block) is sent last. How can I do this?
It seems to me that what I'm looking for might be chaining (rather than multiple independent sets of signals as subscriptions, as I've done in this example), but I can't quite connect the dots.

Why don't you just update different parts like this:
- (void)viewDidAppear:(BOOL)animated
{
#weakify(self)
[RACObserve(self, personsArray) subscribeNext:^(NSArray *personsArray) {
#strongify(self)
// update a set of views
[self updateFirstSetOfView];
[self updateSecondSetOfView];
[self doSomethingElse];
// Finally
[UIView animateWithDuration:0.4 animations:^{
[self.view layoutIfNeeded];
}];
}];
}
You could achieve the same with -doNext: if you prefer the chaining.

Related

Synchronise all animations on collection-view cells

I am working on a project where I need to animate certain collection view cell background color to a flashing state. I am using the code below on my collectionviewcell subclass:
- (void) fadeInAnimation{
if (!self.animationRunning) {
if ([self canRunAnimation]) {
typeof(self) __weak weakSelf = self;
self.nameLabel.textColor = [UIColor blackColor];
self.animationRunning = true;
[UIView animateWithDuration:1.0
delay:0.0
options: UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAllowUserInteraction animations:^{
self.backgroundColor = [self.backgroundColor colorWithAlphaComponent:1.0];
}
completion:^(BOOL finished) {
[weakSelf fadeOutAnimation];
}];
}
else {
[self.layer removeAllAnimations];
self.backgroundColor = [self.backgroundColor colorWithAlphaComponent:1.0];
}
}
}
- (void) fadeOutAnimation {
typeof(self) __weak weakSelf = self;
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAllowUserInteraction animations:^{
self.backgroundColor = [self.backgroundColor colorWithAlphaComponent:0.65];
}
completion:^(BOOL finished) {
weakSelf.animationRunning = NO;
[weakSelf fadeInAnimation];
}];
}
My problem is that even though this code works, I think it is not very efficient and needs cleaning up. Also what I need, is that, all animations currently running should be synchronised in flashing however my code makes cells fade in and out in different times. I have a refresh method that refreshes UI based on the results on my API call every few seconds. Any ideas how I could synchronise currently running UIAnimations? I hope I have been clear in my question.
I fixed my problem by going through my view's subviews and capturing all collectionview cells on my multiple subviews and once I had them, I set their background color with alpha component gradually.. here's part of my solution inspired by Armand's answer in this question:
Animation UITableViewCell backgroundcolor
- (void) animateCellFromView:(UIView*)subview {
if ([subview isKindOfClass:[PageCollectionItemCell class]]) {
PageCollectionItemCell *cell = (PageCollectionItemCell*)subview;
if ([cell canRunAnimation]) {
cell.backgroundColor = [cell.backgroundColor colorWithAlphaComponent:self.alpha];
}
}
}

How to bring View to top of UIView Hierarchy

My app currently has a search bar and generates a holder view of results dropping down from the search bar. However, the ResultContainerView is behind my other Views. I am wondering how to bring that search bar container view to the top of the UIView hierarchy. I tried using [super bringSubviewToFront:_searchResultsHolderView];
but that did not work. Here is my method for when the searchbar is tapped.
- (void)_expandSearchResults
{
CCLogVerbose (#"");
_searchResultsHolderView.alpha = 0.0;
_searchResultsHolderView.hidden = NO;
[_searchResultsHolderView setFrameHeight:10];
[UIView beginAnimations:#"expandSearchResults" context:nil];
[UIView setAnimationDuration:kAnimationDuration_SearchResults];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector (searchAnimationDidStop:finished:context:)];
_searchResultsHolderView.alpha = 1.0;
[_searchResultsHolderView setFrameHeight:_searchResultsHt];
[super bringSubviewToFront:_searchResultsHolderView];
[UIView commitAnimations]; // start animation
}
And this is my reloadData method:
- (void)_reloadSearchResults:(NSString *)searchS
{
CCLogVerbose (#"");
if ([searchS length] == 0) {
_searchResults = nil;
[_searchResultsTableView reloadData];
return;
}
NSAssert (self.patientService != nil, #"The Patient Service is invalid");
[self.patientService searchPatients:searchS
expandType:AllPatientExpansions
success:^(NSArray *list) {
_searchResults = list;
[_searchResultsTableView reloadData];
}
failure:^(NSError *error) {
CCLogDebug (#"Failed: %#", error);
}];
}
[self.superview bringSubviewToFront:self];
[self bringSubviewToFront:_searchResultsHolderView];
First you need to bring self to the top of the stack. After that you can call bringSubViewToFront and bring your _searchResultsHolderView to the front.

use of _block variable in ios7

i use this code for assigning value to varible in block i am using ios7 xcode5 its not working for me.
_Block NSString *temp=Nil;
[UIView animateWithDuration:0.7f delay:0.03f usingSpringWithDamping:30.0f initialSpringVelocity:30.0f options:UIViewAnimationOptionCurveLinear animations:^{
[vwBottomMain setFrame:CGRectMake(0.0f, vwBottomMain.frame.origin.y-39, vwBottomMain.frame.size.width,vwBottomMain.frame.size.height)];
} completion:^(BOOL finished) {
temp=#"test";
}];
Although you don't show the rest of this method, the most likely problem is that your completion handler is asynchronous but you're expecting results immediately.
Try running the following code to see what order things are happening in. It should demonstrate that the assignment happens after the test.
__block NSString *temp = nil;
[UIView animateWithDuration:0.7f delay:0.03f usingSpringWithDamping:30.0f initialSpringVelocity:30.0f options:UIViewAnimationOptionCurveLinear animations:^{
NSLog(#"Animation section");
} completion:^(BOOL finished) {
NSLog(#"Completion handler");
temp = #"test";
}];
NSLog(#"String test %#", temp);

Flip view controllers inside bigger view controller will flip the main view controller

I want to flip a view controller which has smaller size than the screen. I am using transitionFromViewController but this would flip also the whole screen. And I want just the smaller view controller to be flipped.
First the login should be visible, after flipped the register view should be available!
My code is
- (void)showLogin {
vcLogin = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[self.view addSubview:vcLogin.view];
[self addChildViewController:vcLogin];
if (IS_IPAD()) {
vcLogin.view.center = self.view.center;
}
}
- (void)showRegister {
vcRegister = [[RegisterViewController alloc] initWithNibName:#"RegisterViewController" bundle:nil];
[self.view addSubview:vcRegister.view];
[self addChildViewController:vcRegister];
if (IS_IPAD()) {
vcRegister.view.center = self.view.center;
}
[vcLogin willMoveToParentViewController:nil];
[self transitionFromViewController:vcLogin toViewController:vcRegister duration:0.5f options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{} completion:^(BOOL finished) {
[vcLogin removeFromParentViewController];
[vcRegister didMoveToParentViewController:self];
}];
}
I also tried with [UIView transitionFromView:...] but the result is the same!
Any suggestions? Thanks!
Have you tried core animation? You might use something like the code below and have one view controller with subviews.
[UIView transitionFromView:viewOne
toView:viewTwo
duration:1.0f
options:UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionShowHideTransitionViews
completion:(^(BOOL finished){
})];

How to flip an individual UIView (without flipping the parent view)

This is an iPad project where I have a UIView with several subViews, and I am trying to animate one of this UIViews using [UIView transitionFromView:toView:duration:options:completion], but when I run this method the whole parent view gets flipped!
This is the code I'm using:
UIView *secondView = [[UIView alloc] initWithFrame:CGRectMake(200, 200, 300, 300)];
[newView setBackgroundColor:[UIColor redColor]];
[UIView transitionFromView:self.firstView.view toView:secondView duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
Any idea on how can I flip between this views without animating the whole parent view?
Thanks!
this code may helps you:
put the two views you want to flip inside an unnamed view with the same size
and link the IBOutlet UIView *newView,*oldView; to the views and put the new view on top
bool a = NO;
#implementation ViewController
- (IBAction)flip:(id)sender
{
if (a == NO) {
[UIView transitionFromView:oldView toView:newView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:NULL];
a = YES; // a = !a;
}
else {
[UIView transitionFromView:newView toView:oldView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:NULL];
a = NO; // a = !a;
}
}
I had problems getting this to work also. I used the code written by Floris, but although it worked for the first "flip" the next flip started to go weird where the buttons and labels on the frontside UI View lost there positions.
I put the code below into place and it is working fine.
Couple of things to note:
panelView is a UIView control on the ViewController that has two other UIViews nested inside it (subviews). The first one is called frontView, second one is called backView. (see picture below)
I have a bool property called displayingFront on the class
(in my .h)
#property BOOL displayingFront;
in my .m
#synthesize displayingFront;
in my viewDidLoad method
self.displayingFront = YES;
This is the code in the .m that i have rigged up to the two buttons if front and back UIViews...
- (IBAction)flip:(id)sender
{
[UIView transitionWithView:self.panelView
duration:1.0
options:(displayingFront ? UIViewAnimationOptionTransitionFlipFromRight :
UIViewAnimationOptionTransitionFlipFromLeft)
animations: ^{
if(displayingFront)
{
self.frontView.hidden = true;
self.backView.hidden = false;
}
else
{
self.frontView.hidden = false;
self.backView.hidden = true;
}
}
completion:^(BOOL finished) {
if (finished) {
displayingFront = !displayingFront;
}
}];
}
Use a container view to hold your subviews, and make the container view the same size as your subview that you're trying to animate.
So, you can setup your views like this.
self.view-> "a container view" -> subviews
**//flipping view continuously**
bool a = NO;
-(void)viewDidLoad
{
// THIS is the canvass holding the two views this view is flipping
UIView *v=[[UIView alloc]initWithFrame:CGRectMake(40, 40, 150, 150)];
v.backgroundColor=[UIColor clearColor];
[self.view addSubview:v];
[v addSubview:yourfirstview];
[v addSubview:yoursecondview];
// calling the method
[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(flip)
userInfo:nil
repeats:YES];
}
//flipping view randomly
-(void)flip
{
if (a == NO)
{
[UIView transitionFromView:yourfirstview toView:yoursecondview duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft completion:NULL];
a = YES;
}
else if(a==YES)
{
[UIView transitionFromView:yoursecondview toView:yourfirstview duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft completion:NULL];
a = NO;
}
}
I fixed this problem by using old-school animations:
[UIView beginAnimations:#"Flip" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:firstView cache:YES];
[self addSubview:secondView];
[UIView commitAnimations];
Additionally you can remove the firstView:
[firstView removeFromSuperview];
I'd like to make an update of Steve Parker's answer in SWIFT 4:-
var displayBlankView:Bool = true
UIView.transition(with:flipView, duration:1.0, options:displayBlankView ? .transitionFlipFromLeft:.transitionFlipFromRight, animations:{
if(self.displayBlankView){
self.view1.isHidden=true
self.view2.isHidden=false
}else{
self.view1.isHidden=false
self.view2.isHidden=true
}
}, completion:{
(finished: Bool) in
self.displayBlankView = !self.displayBlankView;
})