Synchronise all animations on collection-view cells - objective-c

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];
}
}
}

Related

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.

Animating a subview with CentreY constraint

I have a UITableViewCell with two UILabels and a UISwitch. The labels are multilined and placed on top of each other. The UISwitch is centred vertically. When I turn off the switch I hide one of the labels and the height of the row changes. This causes the switch to jump though I call:
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
Here is my tableviewcell class implementation:
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
self.contentView.frame = self.bounds;
}
- (void)setUpView{
self.numberLabel.text = [NSString stringWithFormat:#"Quote number %ld", [self.cellData[#"rowNumber"] integerValue]];
[self.controlSwitch setOn:[self.cellData[#"checked"] boolValue]];
[UIView animateWithDuration:0.5 animations:^{
if ([self.controlSwitch isOn]) {
self.quoteLabel.text = self.cellData[#"quote"];
self.quoteLabel.hidden = NO;
self.bottomConst.constant = 10;
}else{
self.quoteLabel.text = #"";
self.quoteLabel.hidden = YES;
self.bottomConst.constant = 0;
}
}];
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.selectionStyle = UITableViewCellSelectionStyleNone;
[self.contentView updateConstraintsIfNeeded];
[self.contentView layoutIfNeeded];
self.numberLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.numberLabel.frame);
self.quoteLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.quoteLabel.frame);
}
- (IBAction)removeQuote:(id)sender {
[self.delegate updateCell:self];
}
The constraint settings on the UITableViewCell looks like this.
When the switch is turned on, I'm simply unhide the Quote label and set the text.
Here is the link on how it looks like. Is this the default behavior or is there a way I can control the movement of the switch when the row animates?
Try this:
...
[self.contentView updateConstraintsIfNeeded];
[UIView beginAnimations:nil context:nil];
[self.contentView layoutIfNeeded];
[UIView commitAnimations];
...

How can I guarantee order of subscriber messages in ReactiveCocoa?

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.

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;
})

Attempting to "turn on/off" different UITapGestureReconginzer methods

I have this lovely nav bar asset:
navBar view http://dl.dropbox.com/u/1734050/ViewWithNavBar.jpg
and when users tap on the middle button (one with the arrows) it will bring up this other lovely "share box" asset
shareBox dropdown from navBar http://dl.dropbox.com/u/1734050/navBarWithShareBox.jpg
What I have in the code so far is that when the screen is tapped, the nav bar will appear, and disappear on another tap. Secondly, when the user taps on the "share" button, the share box comes up, and the user can tap outside of the box to dismiss it.
Here is the problem: I cannot dismiss the nav bar after bringing up the share box!
Here is some code:
-(void)viewDidLoad {
...
...
...
UITapGestureRecognizer *tapNavBar = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapForNavBar:)];
self.tapForNavBar = tapNavBar;
[self.viewForTaps addGestureRecognizer:self.tapForNavBar];
// btw, tapForNavBar is a UITapGestureRecognizer instance variable.
// also, viewForTaps is a view (instance variable) I made to handle
// where the user could tap (most of the screen)
}
#pragma mark -
#pragma mark Tap and Gesture Methods
-(void)handleTapForNavBar:(UIGestureRecognizer *)gestureRecognizer {
if (self.navBarIsHidden) {
[UIView animateWithDuration:0.5 animations:^ {
//self.primeViewController.view.alpha = 0.8;
self.navBar.alpha = 1.0;
}];
self.detailViewButton.userInteractionEnabled = YES;
self.shareButton.userInteractionEnabled = YES;
self.aboutAppButton.userInteractionEnabled = YES;
self.navBarIsHidden = NO;
}
else {
[UIView animateWithDuration:0.5 animations:^ {
self.navBar.alpha = 0.0;
//self.primeViewController.view.alpha = 1.0;
}];
self.detailViewButton.userInteractionEnabled = NO;
self.shareButton.userInteractionEnabled = NO;
self.aboutAppButton.userInteractionEnabled = NO;
self.navBarIsHidden = YES;
}
}
Ok, so that should look all dandy (and works like it too!) -- now here is perhaps where it gets unorthodox?
-(IBAction)showShareMenu:(id)sender {
if (self.navBarShareBoxIsHidden) {
[UIView animateWithDuration:0.5 animations:^ {
self.navBarShareBox.alpha = 1.0;
}];
[self.tapForNavBar removeTarget:nil action:NULL];
UITapGestureRecognizer *tapShareBox = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapForShareBox:)];
self.tapForShareBox = tapShareBox;
[self.viewForTaps addGestureRecognizer:self.tapForShareBox];
self.navBarShareBoxIsHidden = NO;
self.twitterButton.userInteractionEnabled = YES;
self.facebookButton.userInteractionEnabled = YES;
self.googlePlusButton.userInteractionEnabled = YES;
}
else {
[UIView animateWithDuration:0.5 animations:^ {
self.navBarShareBox.alpha = 0.0;
}];
[self.tapForShareBox removeTarget:nil action:NULL];
[self.tapForNavBar addTarget:self action:#selector(handleTapForNavBar:)];
self.navBarShareBoxIsHidden = YES;
self.twitterButton.userInteractionEnabled = NO;
self.facebookButton.userInteractionEnabled = NO;
self.googlePlusButton.userInteractionEnabled = NO;
}
}
I then create this method to handle the specific shareBox tapping:
-(void)handleTapForShareBox:(UIGestureRecognizer *)gestureRecognizer {
if (!self.navBarShareBoxIsHidden) {
[UIView animateWithDuration:0.5 animations:^ {
self.navBarShareBox.alpha = 0.0;
}];
self.navBarShareBoxIsHidden = YES;
self.twitterButton.userInteractionEnabled = NO;
self.facebookButton.userInteractionEnabled = NO;
self.googlePlusButton.userInteractionEnabled = NO;
[self.tapForShareBox removeTarget:nil action:NULL];
[self.tapForNavBar addTarget:self action:#selector(handleTapForNavBar:)];
}
}
I'm assuming my problem is coming from alloc/initing the new UITapGestureRecognizer in the -(IBAction)showShareMenu method. I thought by using the removeTarget... and addTarget messages I could easily tell my code which TapGesture method it should use, but considering it isn't working, I was wrong! Where did I go wrong? If you need more info, I'd be happy to give more.
Ah Ha! Looks like I found a solution! In my handleTapForShareBox method I did this:
-(void)handleTapForShareBox:(UIGestureRecognizer *)gestureRecognizer {
if (!self.navBarShareBoxIsHidden) {
[UIView animateWithDuration:0.5 animations:^ {
self.navBarShareBox.alpha = 0.0;
}];
self.navBarShareBoxIsHidden = YES;
self.twitterButton.userInteractionEnabled = NO;
self.facebookButton.userInteractionEnabled = NO;
self.googlePlusButton.userInteractionEnabled = NO;
[self.tapForShareBox removeTarget:nil action:NULL];
//[self.tapForNavBar addTarget:self action:#selector(handleTapForNavBar:)];
// Here is the new stuff, I added another alloc init of a UITapGestureRecognizer
// and re-assigned it to my UITap instance variable
UITapGestureRecognizer *tapNavBar = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapForNavBar:)];
self.tapForNavBar = tapNavBar;
[self.viewForTaps addGestureRecognizer:self.tapForNavBar];
}
}
It works great now! However, there were a couple things:
1) Do I have to release the UITapGestureRecognizer local pointer I created? Or because I initWithTarget it will be autoreleased?
2) Is this a hack? Or is this a proper way of passing different tap methods around? I'm just a little confused why the removeTarget and addTarget didn't do the trick.