Best way to animate visiblity of UICollectionViewCell in iOS7+ - ios7

I need to animate the visibility of UICollectionViewCells on startup such that they are visible randomly when my application launches. But since I already have the items to be displayed they are visible at the same time. I tried below solution & it works correctly. But I want to know the best & optimized way to achieve this task
- (void)animateCollectionViewCellsVisiblity
{
// First set alpha to 0
for (NSUInteger i = 0; i< self.dataArray.count; i++)
{
[self applyAlpha:0.0 toItem:i inSection:0];
}
// Initially collectionview is hidden while fetching data, so show it now
self.collectionView.hidden = NO;
// Now set alpha to 1 with a random delay
for (NSUInteger i = 0; i< self.dataArray.count; i++)
{
CGFloat delay = [self randomNumberWithlowerBound:0 upperBound:self.dataArray.count] + 0.05;
[UIView animateWithDuration:.2 delay:delay options:UIViewAnimationOptionCurveEaseIn animations:^{
[self applyAlpha:1.0 toItem:i inSection:0];
} completion:nil];
}
}
- (void)applyAlpha:(CGFloat)alpha toItem:(NSInteger)item inSection:(NSInteger)section
{
MyCollectionViewCell *cell = (MyCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:section]];
cell.alpha = alpha;
}
- (int)randomNumberWithlowerBound:(int)lowerBound upperBound:(int)upperBound
{
int randomValue = arc4random_uniform(upperBound - lowerBound + 1) + lowerBound;
return randomValue;
}

Your solution is great. That's how it is done. Looks pretty optimized to me.
You could make your code shorter by dropping the section parameter which is always zero. And you possibly eliminate the applyAlpha method by adding just two lines before your animate block.

Related

How to detect which CALayer was clicked on?

I'm having a problem detecting which CALayer is being clicked on, as these CALayers have a constant CABasicAnimation moving the x and y positions.
My current code is as follows:
-(void)mouseUp:(NSEvent *)theEvent
{
CGPoint pointInView = NSPointToCGPoint([self convertPoint:[theEvent locationInWindow]fromView:nil]);
CALayer* clickedOn = [(CALayer*) self.layer hitTest:[self.layer convertPoint:pointInView toLayer:self.layer.superlayer]];
int selectedContact = -1;
for (int i = 0; i < [contactLayers count]; i++) {
CALayer* presentationLayer = [contactLayers[i] presentationLayer];
if (presentationLayer == clickedOn) {
selectedContact = i;
break;
}
}
if(selectedContact == -1)
return; //no contact selected;
CALayer* selectedContactLayer = contactLayers[selectedContact];
[selectedContactLayer removeFromSuperlayer];
}
contactLayers is an NSMutableArray containing all the possible CALayers the user can click on.
Every time this runs, i always seems to end up staying -1. I'm using presentationLayer since the CALayers have a CABasicAnimation applied to them. I also tried modelLayer, but this only works if you click on the initial location of each layer.
So just a recap: I have an NSMutableArray of CALayers that all have a CABasicAnimation applied, this array is called contactLayers. When the user clicks on a layer, I need to know what layer they clicked on by setting the index to the appropriate value in the array.
Compute 'clickedOn' by hit testing 'self.layer.presentationLayer', not 'self.layer'

Navigating to a next page in a gridView

I need help developing a logic that navigates to the next page of a gridView. My current approach takes me to page 2 but does not advances any further than that.
Here is what I have tried so far.
NSInteger nextPage;
NSINteger currentPage;
UIView *pageView = [self.pageViews objectAtIndex:page];
if ((NSNull*)pageView == [NSNull null]) {
CGFloat width = frame.size.width;
currentPage= floor((gridView.contentOffset.x+width/2)/width);
nextPage = currentPage+1; // update the next page button;
}
Next page button
- (IBAction)nextPage:(id)sender {
CGRect frame = gridView.frame;
frame.origin.x = frame.size.width * nextPage;
frame.origin.y = 0;
[gridView scrollRectToVisible:frame animated:YES];
}
Thanks.
The solution turned out to be very simple. I added a global variable to track the number of times the nextPage button was clicked and replaced the
NSInteger nextPage; with
int clickedNumber =0;
- (IBAction)nextPage:(id)sender {
clickedNumber = clickedNumber+1;
CGRect frame = gridView.frame;
frame.origin.x = frame.size.width * clickedNumber;
frame.origin.y = 0;
[gridView scrollRectToVisible:frame animated:YES];
NSLog(#"%d",clickedNumber);
}
This way every time the button is clicked the number is increased and it goes to goes to the next page according the number of times the button is clicked.
It and works like a charm. :)

Scroll Table View with a cell that was selected with a long press

I have a table view that I would like to scroll when a selected a cell and "pop" it out and move it around the table view. The table view scrolls just fine and the long press to select a cell works, but I'm not able to scroll the table view when I have a cell selected.
I read up on UIGestureRecognizerDelegate but I'm not sure that is making a difference. I also call these method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Here is part of my code:
- (IBAction)longPressGestureRecognized:(id)sender {
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
UIGestureRecognizerState state = longPress.state;
//longPress.delegate = self;
CGPoint location = [longPress locationInView:self.personTableView];
NSIndexPath *indexPath = [self.personTableView indexPathForRowAtPoint:location];
static UIView *snapshot = nil; ///< A snapshot of the row user is moving.
static NSIndexPath *sourceIndexPath = nil; ///< Initial index path, where gesture begins.
switch (state) {
case UIGestureRecognizerStateBegan: {
if (indexPath) {
sourceIndexPath = indexPath;
UITableViewCell *cell = [self.personTableView cellForRowAtIndexPath:indexPath];
// capture the color of the cell before blacking out
savedTextColor = cell.detailTextLabel.textColor;
// Take a snapshot of the selected row using helper method.
snapshot = [self customSnapshotFromView:cell];
// Add the snapshot as subview, centered at cell's center...
__block CGPoint center = cell.center;
snapshot.center = center;
snapshot.alpha = 0.0;
[self.personTableView addSubview:snapshot];
[UIView animateWithDuration:0.25 animations:^{
// Offset for gesture location.
center.y = location.y;
snapshot.center = center;
snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05);
snapshot.alpha = 0.98;
// Black out.
cell.detailTextLabel.alpha = 0;
cell.textLabel.alpha = 0;
} completion:nil];
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint center = snapshot.center;
center.y = location.y;
snapshot.center = center;
// Is destination valid and is it different from source?
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
// ... update data source.
[dataSingelton saveUpdatedPersonList:(int)indexPath.row sourceIndex:(int)sourceIndexPath.row];
//[[dataSingelton mutableDataArray] exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];
// ... move the rows.
[self.personTableView moveRowAtIndexPath:sourceIndexPath toIndexPath:indexPath];
// ... and update source so it is in sync with UI changes.
sourceIndexPath = indexPath;
}
break;
}
default: {
// Clean up.
UITableViewCell *cell = [self.personTableView cellForRowAtIndexPath:sourceIndexPath];
[UIView animateWithDuration:0.25 animations:^{
snapshot.center = cell.center;
snapshot.transform = CGAffineTransformIdentity;
snapshot.alpha = 0.1;
cell.detailTextLabel.alpha = 1;
cell.textLabel.alpha = 1;
} completion:^(BOOL finished) {
[snapshot removeFromSuperview];
snapshot = nil;
cell.hidden = NO;
}];
sourceIndexPath = nil;
break;
}
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (UIView *)customSnapshotFromView:(UIView *)inputView {
UIView *snapshot = [inputView snapshotViewAfterScreenUpdates:YES];
snapshot.alpha = 0.1;
snapshot.layer.masksToBounds = NO;
snapshot.layer.cornerRadius = 0.0;
snapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);
snapshot.layer.shadowRadius = 5.0;
snapshot.layer.shadowOpacity = 0.4;
return snapshot;
}
When I set the delegate to self like this:
longPress.delegate = self;
The able view scrolls, but then my selected cell doesn't move. What am I missing to make this work?
* Updated *
After doing some more reading/research I think in order for me to achieve the effect I want (the scrolling effect on the TableView in Apple's weather app) I think that my long press gesture will also have to handle the scrolling of my table view. The default scrolling starts to scroll the entire table instantly, where as I need the table to scroll only when the long pressed cell is at the top or the bottom of my TableView. Am I correct in thinking like this? I've been trying to find an example to break down and study, but haven't had any luck finding anything like that.
** Updated **
So I'm trying to figure out how to trigger the scroll up timer when the user drags a cell up towards the top. The problem I'm having right now is figuring out when that cell is close to the top, when its at the very top of the tableview, thats easy I could use something like tableViewHight + 100, but this code wont work when the tableview is scrolled down. Here is what I have so far:
NSLog(#"This is table view hight %.2f", tableViewHight);
NSLog(#"This is content offset %f", (personTableView.contentOffset.y));
NSLog(#"Should be 100 place holder %.2f", (tableViewHight - tableViewHight) + personTableView.contentOffset.y + 100);
NSLog(#"location y %f", location.y);
// gets called repeatedly
// Step 1: check to see if we need to start scrolling
if (location.y < tableViewHight - tableViewHight + 100) {
// ** top **
// Step 2: make sure the current y postoin isn't greater then the previous y postion
if (location. y <= originalLocation.y) {
// NSLog(#"This is the offset y %f", self.personTableView.contentOffset.y);
// Step 4: check to see if we have a timer and if not create one
[self startTimer];
}
else{
//Step 3: they started to scroll up so end the timer
[self endTimer];
}
}
else if(location.y > tableViewHight - 100)
{
// ** bottom **
// Step 2: make sure the current y postoin isn't less then the previous y postion
if (location. y >= originalLocation.y) {
NSLog(#"its less then 0");
if (!timer) {
// Step 4: check to see if we have a timer and if not create one
[self startTimer];
}
}
else{
//Step 3: they started to scroll up so end the timer
[self endTimer];
}
}
else{
// ** middle **
// Step 2: check to see if we have a timer and if so destory it
[self endTimer];
}
Basically I want to trigger that if statement when the cell is less then 100px from the hight of the scrollable table view.
From the UILongPressGestureRecognizer you can get the current y position of the touch. From that you can calculate how close to the bottom or top of the UITableViewthe touch is. If you determine that it's close enough to begin scrolling, then start a repeating NSTimer that increments or decrements the contentOffset of your UITableView by 1 pixel, scrolling your table. Change how fast it scrolls by changing the timeInterval of the timer.
When the UILongPressGestureRecognizer ends, or if the y position of the touch moves outside of the scrolling zone, then invalidate your timer, which will stop the scrolling.
If you felt adventurous you could also increase the speed of the scrolling dynamically as the user gets closer to the edge of the UITableView by changing the timeInterval as the user moves their touch up or down, as that is generally how this type of functionality works.
Good luck!

NSView Instantiation and Get Its Values with Identifier

I have a subclass of NSView named clickView as follows.
// clickView.h
#interface clickView : NSView {
BOOL onOff;
}
- (BOOL)getOnOff;
// clickView.m
- (BOOL)getOnOff {
return onOff;
}
- (void)mouseDown:(NSEvent *)event {
if (onOff) {
onOff = NO;
} else {
onOff = YES;
}
[self setNeedsDisplay:YES];
NSLog(#"%#",self.identifier);
}
It utilizes its drawRect method (, which is not shown here,) to fill the rectangle with a color if the user clicks on it. And it uses a boolean value (onOff) to see if it's been clicked on. Now, switching to AppleDelegate, I instantiate this NSView subclass as follows.
// AppDelegate.m
- (IBAction)create4Clicked:(id)sender {
NSInteger rowCount = 10;
NSInteger colCount = 10;
NSInteger k = 1;
for (NSInteger i2 = 0; i2 < rowCount; i2 ++) {
for (NSInteger i3 = 0; i3 < colCount; i3 ++) {
NSView *view = [[clickView alloc] initWithFrame:NSMakeRect(50+i2*10,50+i3*10,10,10)];
[view setIdentifier:[NSString stringWithFormat:#"%li",k]];
[view1 addSubview:view]; // view1 is NSView that's been created with Interface Builder
k++;
}
}
}
So I now have 100 squares displayed on view1 (NSView). If I click on any of the squares, I do get its identifier. (See 'mouseDown.') Now, what I need to figure out is how to tell which square has its 'onOff' set to YES (or NO).
Thank you for your help.
A. First off all:
Probably you are new to Cocoa and Objective-C.
a. Why don't you use buttons?
b. onOff is obviously a property. Why don't you use declared properties?
B. To your Q:
You can retrieve the views with a specific state by asking the superview for its subviews and then filter them with a predicate:
NSPredicate *clickedPredicate = [NSPredicate predicateWithFormat:#"onOff == %d", YES];
NSArray* clickViews = [view1 subviews]; // Returns all subviews
clickViews = [clickViews filteredArrayUsingPredicate:clickedPredicate]; // On-views
But you should think about storing the state outside the views. What is your app for?

UILabel Dynamic Content auto size trouble

Please, I need some help with some UILabels that must hold dynamic content. They are contained in a UIScrollView.
I can't get them to resize properly depending on screen orientation. I also want to keep an exact distance between them.
Right now, I'm having trouble with 2 of them (many to come).
I'm using the following code to resize them but it doesn't get me the expected results:
- (void) updateLabelsPositionForPortrait
{
summary_PersonalMonth.numberOfLines = 0;
summary_PersonalDay.numberOfLines = 0;
summary_PersonalMonth.frame = CGRectMake(summary_PersonalMonth.frame.origin.x,
summary_PersonalMonth.frame.origin.y,
summary_PersonalMonth.frame.size.width + 15,
summary_PersonalMonth.frame.size.height);
[summary_PersonalMonth sizeToFit];
summary_PersonalDay.frame = CGRectMake(summary_PersonalDay.frame.origin.x,
summary_PersonalDay.frame.origin.y + summary_PersonalMonth.bounds.origin.y + 10,
summary_PersonalDay.frame.size.width + 15,
summary_PersonalDay.frame.size.height);
[summary_PersonalDay sizeToFit];
[self updateScrollViewContentSize];
}
- (void) updateLabelsPositionForLandscape
{
summary_PersonalMonth.numberOfLines = 1;
summary_PersonalDay.numberOfLines = 1;
summary_PersonalMonth.frame = CGRectMake(summary_PersonalMonth.frame.origin.x,
summary_PersonalMonth.frame.origin.y,
summary_PersonalMonth.frame.size.width,
summary_PersonalMonth.frame.size.height);
[summary_PersonalMonth sizeToFit];
summary_PersonalDay.frame = CGRectMake(summary_PersonalDay.frame.origin.x,
summary_PersonalDay.frame.origin.y - summary_PersonalMonth.bounds.origin.y - 10,
summary_PersonalDay.frame.size.width,
summary_PersonalDay.frame.size.height);
[summary_PersonalDay sizeToFit];
}
and I call each method in shouldAutorotateToInterfaceOrientation: in the corresponding orientation.
if (UIInterfaceOrientationIsPortrait([UIDevice currentDevice].orientation))
{
[self updateLabelsPositionForPortrait];
}
else
{
[self updateLabelsPositionForLandscape];
}
The results are these:
Initial result on view load
Result after rotation in Landscape
Result after rotating back to portrait
Please, I need advice !
Thank you !
Store all labels starting form first to last in position in NSArray.
Now making one function which set frames of labels according to orientation:
-(void)setFramesOfLabel:(BOOL)flag
{
if(flag) //portrait
{
int height = 20;
int spaceBetweenLabels = 20;
itn xspace = 20 //
}
else//landscape changes
{
int height = 20;
int spaceBetweenLabels = 20;
itn xspace = 20 /
}
int count = 0;
for(UILabel *label in arrLabels)
{
NSString *strText = [arrTexts objectAtIndex:count]; //as u may having text array to fill in labels as i assume
CGSize expectedLabelSize = [strText sizeWithFont:label.font constrainedToSize:maximumLabelSize lineBreakMode:UILineBreakModeWordWrap]; //
CGrect *newFrame = CGRectMake(xspace,height,expectedLabelSize.width,expectedLabelSize.height);
[label setFrame:newFrame];
height += expectedLabelSize.height;
count ++;
}
[scrollView setContentSize(scrollView.width,height)];
}
Use
if (UIInterfaceOrientationIsPortrait([UIDevice currentDevice].orientation))
{
[self setFramesOfLabel:TRUE];
}
else
{
[self setFramesOfLabel:FALSE];
}
You seem to be re-operating on the same frames over and over again. Try using this code:
// Declare the below variables as globals
CGRect defaultRectPortrait = CGRectZero;
CGRect defaultRectLandscape = CGRectZero;
- (void) updateLabelsPositionForPortrait
{
summary_PersonalMonth.numberOfLines = 0;
summary_PersonalDay.numberOfLines = 0;
if(defaultRectPortait == CGRectZero)
defaultRectPortait = CGRectMake(summary_PersonalMonth.frame.origin.x,
summary_PersonalMonth.frame.origin.y,
summary_PersonalMonth.frame.size.width + 15,
summary_PersonalMonth.frame.size.height);
[summary_PersonalMonth setFrame:defaultRectPortrait];
[summary_PersonalMonth sizeToFit];
// Do a similar thing for summary_PersonalDay
[self updateScrollViewContentSize];
}
- (void) updateLabelsPositionForLandscape
{
// Use similar logic for summary_PersonalMonth and summary_PersonalDay in landscape as well
}
And finally:
if (UIInterfaceOrientationIsPortrait([UIDevice currentDevice].orientation))
{
[self updateLabelsPositionForPortrait];
}
else
{
[self updateLabelsPositionForLandscape];
}