Animating UICollectionViewCell subviews on layout change - objective-c

I have two UICollectionViewLayout, one for showing stacked cells and another one to show individual cells on full screen whe they're selected.
I need to animate the cell's subviews to certain positions each time the cell is selected, and then move again those subviews when the cell is deselected.
STEP 1: Cell is selected. Subview is animated from its original position to a new one. No problem here.
STEP 1: Cell is deselected. Subview is animated to another position, BUT the animation starts from the original position, not the new position I indicated in the previous step.
Is there any way to save the modified status, and start the animations from there?
UPDATE
Here's how I change layouts on cell selection (ViewController.m):
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
__weak CustomCellCollectionViewCell *cell = (CustomCellCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
// Hide single view
if ([indexPath isEqual:self.exposedItemIndexPath]) {
self.exposedItemIndexPath = nil;
[cell deselectedAnimations];
[self animateCollectionView:NO];
}
// Select single view
else if (self.exposedItemIndexPath == nil) {
self.exposedItemIndexPath = indexPath;
[cell selectedAnimations];
[self animateCollectionView:YES];
}
}
- (void)setExposedItemIndexPath:(NSIndexPath *)exposedItemIndexPath
{
if (![exposedItemIndexPath isEqual:_exposedItemIndexPath]) {
if (exposedItemIndexPath) {
self.audiosContentOffset = self.collectionView.contentOffset;
SelectedLayout *singleLayout = [[SelectedLayout alloc] initWithExposedItemIndex:exposedItemIndexPath.item];
[self.collectionView setCollectionViewLayout:singleLayout animated:YES];
}else{
self.stackedLayout.overwriteContentOffset = YES;
self.stackedLayout.contentOffset = self.audiosContentOffset;
[self.collectionView setCollectionViewLayout:self.stackedLayout animated:YES];
[self.collectionView setContentOffset:self.audiosContentOffset animated:YES];
}
_exposedItemIndexPath = exposedItemIndexPath;
}
}
selectedAnimations moves a UIView inside the cell, and deselectedAnimations moves it somewhere else when the cell is deselected. BUT, deselectedAnimations starts from the UIView's original position, not from where it was left at the end of selectedAnimations.
The cells and its subviews are based on a nib file. It seems to me that, on layout change, the cell is being rendered again following the "instructions" on the nib file, and that's why the subview is animated starting from its original position instead of where i left it on selectedAnimations.
My question is: is there any way to "save" the cell's subviews positions and start deselectedAnimations from there?

Related

How to avoid selecting other cells that have the same indexPath

I've been struggling with this issue for weeks and still have not found one solution. No one really gave me an answer on SO and have not found anything that helps my issue either.
When making a change of a cell's content's color, position, whatever, other cells with the same indexPath also get modified.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object{
static NSString *simpleTableIdentifier = #"cell";
self.cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (self.cell == nil) {
self.cell = [[SearchCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
This is how I create cells. Help..!
EDIT
This is a slide-out view implemented in each cell. When you swipe to right on a cell, it shows up from the left but even though only the selected cell's content should be modified, several other cell's slide-out menu also come out.
This is how I implement slide-out menu in my custom cell class.
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.optionView = [[OptionView alloc]initWithFrame:CGRectMake(0, 0, self.contentView.frame.size.width , self.contentView.frame.size.width)];
self.optionView.delegate = self;
[self.contentView addSubview: self.optionView];
OptionView' delegate - called when swiped to the right
-(void)handleGesture:(UISwipeGestureRecognizer*)shouldOpenMenu{
POPSpringAnimation *anim = [POPSpringAnimation animation];
anim.property = [POPAnimatableProperty propertyWithName:kPOPViewFrame];
anim.toValue = [NSValue valueWithCGRect:CGRectMake(-self.frame.size.width/4, 0, self.optionView0.frame.size.width, self.optionView0.frame.size.height)];
anim.springBounciness = 11;
anim.springSpeed = 5;
[self.optionView0.layer pop_addAnimation:anim forKey:#"spring0"];
First, each tableview cell has different index path.
Second, you never want to save cells at class level properties. Create them locally and return from cellForIndexPath:. Your table view will take care of the cell thereon.
Third, and very important, cells are reused. So, you should reset the cell before using it. For instance, if you are setting cell colour to red in some condition and then you move the table down and this cell gets discarded and new cell is rendered by re-using the discarded cell. You would want to reset the cell colour to clear if you are not setting cell colour for that row.
Follow these rules and you should be able to fix your issue.

Adding label/button to UICollectionView - Footer or not?

I have a collectionView that displays an array of images in a single, horizontal layout. I'm trying to add a UILabel under each cell. I assumed the best/easiest way to do this would be to add a footer view/supplementary view for each cell. Therefore, I'm overriding viewForSupplementaryElementOfKind. I have an array of images names that correlate to the array of images. So I'm just hoping the see the image name as the label for each each cell. However, all labels and names are overlapping on the first cell. Also, the footer view doesn't come into view until I scroll to the last image/cell, so I don't see the overlapping labels on the first cell until I scroll to the end. I know the issue is in my viewForSupplementaryElementOfKind implementation, but I'm not sure how to correct it. Can someone please advise? Thanks in advance!!
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
NSLog(#"viewForSupplementaryElementOfKind");
static NSString *FooterCellIdentifier = #"FooterView"; // string value identifier for cell reuse
FooterViewCell *footerView;
if (kind == UICollectionElementKindSectionFooter) {
NSLog(#"*******Element Kind is a footer!*******");
footerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter
withReuseIdentifier:FooterCellIdentifier forIndexPath:indexPath];
for (int i = 0; i < [self.imagesArray count]; i++) {
UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.myCollectionView.frame.origin.x/2, self.myCollectionView.frame.origin.y/2, CellWidth, 20)];
//testLabel.text = [self.imageNames objectAtIndex:indexPath.row];
testLabel.text = self.imageNames[i];
[self.myCollectionView addSubview:testLabel];
}
return footerView;
} else {
NSLog(#"*******Element Kind is NOT a footer!*******");
return nil;
}
}
Header and footer views on UICollectionView are only relative to sections.
Since the labels are supposed to identify the images (the model), you should build your UICollectionViewCell with the label and image view inside, as subviews. The easiest way is doing it inside a storyboard, with prototype cells on the collection view.

UICollectionViewCell Padding

I am trying to set zero padding on collection view cells, I have set "Min Spacing" on the view controller to:
Yet it still has gaps between the cells:
Also I'd like it so that the cells wrap nicely depending on the width of the frame, for example each cell is 50px wide, so if there are six cells and I set the frame width to 150px, it will display two lines of three cells.
Yet if I set the frame width to 150 by doing:
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect frame = self.collectionView.frame;
frame.size.width = 150;
self.collectionView.frame = frame;
}
It looks like in the above screen shot (too wide).
If I set it to something ridiculously small such as 10, it then wraps to some extent:
The UICollectionViewCell is set to 50 x 50:
I have also tried setting the size of the cell programatically, and also removed the UIEdgeInset:
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 0, 0, 0);
}
I have disabled auto layout just incase that had any interference. Any advice as to how I can remove the padding and also have them wrap depending on the frame width / height?
You are using a UICollectionViewController which, like a UITableViewController, has the collection view (or table view) as the base view property of the view controller. This means it can't be resized from within the view controller; it's size is controlled by its superview - either the window, in this case, which has it as the root view controller, or a parent view controller if you were embedding it.
If you want to reduce the collection view area so the cells abut one another, you can amend the section insets of the collection view in the storyboard. In your case, an inset of 15 left and right brings the cells together - this is 9 * 50 (450) plus 30 = 480 which is the width of a 3.5inch iPhone in landscape.
Obviously this will be different in the iPhone 5 or iPad. You can either calculate the insets at run time, use a collection view held in a standard UIViewController subclass, or hold the UICollectionViewController as an embedded view controller. The latter two will enable you just to specify a size, which is probably nicer than calculating insets.
I am not sure those cells in the screenshot are 50x50 (EDIT: I guess they are...).
Check if you connected the UICollectionViewDelegate and UICollectionViewDataSource.
This it the method you need to implement
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGSize retval = CGSizeMake(50, 50);
return retval;
}
If it does not work, try putting this code inside viewDidLoad
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setMinimumInteritemSpacing:0.0f];
[flowLayout setMinimumLineSpacing:0.0f];
[flowLayout setItemSize:CGSizeMake(50.0f, 50.0f)];
[self.collectionView setCollectionViewLayout:flowLayout];
Implement this method in your ViewController
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
return 2; //the spacing between cells is 2 px here
}
the minimumInteritemSpacing is just that, a minimum value, so it can be larger if the UICollectionViewLayout class decides it needs to be
To correct for this you can create your own layout subclass, and implement the - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath method, something like the answer from this question (see the second question, not the one marked as correct)
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* atts =
[super layoutAttributesForItemAtIndexPath:indexPath];
if (indexPath.item == 0) // degenerate case 1, first item of section
return atts;
NSIndexPath* ipPrev =
[NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
CGRect fPrev = [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
CGFloat rightPrev = fPrev.origin.x + fPrev.size.width + 10;
if (atts.frame.origin.x <= rightPrev) // degenerate case 2, first item of line
return atts;
CGRect f = atts.frame;
f.origin.x = rightPrev;
atts.frame = f;
return atts;
}

UITableViewAccessoryType Randomly Not Showing

For each cell in the TableView I basically have a Boolean variable that stores if the data is being loaded for that cell. So tapping on a cell will cause the accessory type to change to a UIActivityIndicator. The TableView loads fine, but when I pop back to the TableView one or two of the cells randomly do not have the default DisclosureIndicator... it has nothing
The code I am using is inside cellForRowAtIndexPath
NSLog([entry isLoading] ? #"Yes" : #"No");
if ([entry isLoading]) {
UIActivityIndicatorView *activityView =
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[activityView startAnimating];
[cell setAccessoryView:activityView];
}
else{
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
I have stepped through the code and the cells that do not show the DisclosureIndicator are still running the code that is setting it to a AccessoryDisclosureIndicator... I'm not sure what's going on since the cells missing the indicatory is triggering the else statement causing the setAccessoryType
Thanks!
Try this,
if ([entry isLoading]) {
UIActivityIndicatorView *activityView =
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[activityView startAnimating];
[cell setAccessoryView:activityView];
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
else{
[cell setAccessoryView:nil];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
As per documentation,
accessoryView
If the value of this property is not nil, the UITableViewCell class
uses the given view for the accessory view in the table view’s normal
(default) state; it ignores the value of the accessoryType property.
The provided accessory view can be a framework-provided control or
label or a custom view. The accessory view appears in the right side
of the cell.
So you need to set accessoryView to nil show the accessoryType or else it will ignore it.

UITableView subview appears in wrong section when scrolling?

In a UITableView I add a UIView as subview but ONLY for section 1. Section 1's content is loaded from a plist and the plist contains mutable content. If there are enough rows to allow scrolling, then the following happens: I scroll to the bottom, and back up, and the UITextField appears randomly on some of section 0's cells. I have no clue why this is happening! So what i do is this (in ´cellForRowAtIndexPath´):
if (indexPath.section == 0) {
//do stuff
}
else if (indexPath.section == 1) {
d = [UIView alloc] init];
[cell.contentView addSubview:d];
}
and this gets totally messed up when I scroll. The subviews appear in section 0 where they shoudnt, and on didSelectRowAtIdexPath I reload for section 1 and then subviews even appear twice (over each other)... Its a complete MESS! Please, Please help.......
Without seeing any code this seems to be an issue pertaining to reusable cells. What happens is that the cells that have scrolled off the screen are reused for the new content that is to be shown. So i reckon you need to make a distinction in cellForRowAtIndexPath for section 0 and 1 and basically use different set of cells for them.
EDIT: Ok ima give a shot to your problem here
UITableViewCell *cell;
if (indexPath.section == 0) {
cell = [tableView dequeueReusableCellWithIdentifier:#"CellWithoutSubview"];
if (cell ==nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStylePlain reuseIdentifier:#"CellWithoutSubview"] autorelease];
}
//do stuff with cell like set text or whatever
}
else if (indexPath.section == 1) {
cell = [tableView dequeueReusableCellWithIdentifier:#"CellWithSubview"];
if (cell ==nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStylePlain reuseIdentifier:#"CellWithSubview"] autorelease];
d = [[UIView alloc] init];
[cell.contentView addSubview:d];
[d release];
}
}
return cell;
So now you'll have two types of cells for the tableview that'll be reused one without the subview and one with the subview.
You must be using dequeueReusableCellWithIdentifier. The purpose of dequeueReusableCellWithIdentifier is to use less memory. If the screen can fit 4 or 5 table cells, then with reuse you only need to have 4 or 5 table cells allocated in memory even if the table has 1000 entries.
So the subviews in UITableViewCell are also cached. So when the cell is reused, you need to clean out the old view & then put in the new content.
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier: #"your-id"];
if (cell)
{
//reusing old cell. reset all subviews before putting new content.
}
else
{
//Create a fresh new cell
}
You should use switch instead:
switch ( indexPath.section )
{
case 0:
{
/* do soemthing */
}
break;
case 1:
{
d = [UIView alloc] init];
[cell.contentView addSubview:d];
}
break;
}