Adding label/button to UICollectionView - Footer or not? - objective-c

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.

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.

Animating UICollectionViewCell subviews on layout change

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?

How to make UITextView in section header adjust its height to its content

I cannot get this to work. I am using autolayout on the current view controller. I have a UITableView that has section headers and each section header has UITextView that has text that varies in length depending on the section. I cannot make it enlarge its height automatically to fit the contents so there will be no need for scroll (its contents are attributed text)
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
//UITextView *tv = [[UITextView alloc] init];
//tv.editable = NO;
//tv.attributedText = [self millionaireResults][section][#"header"];
//return tv;
return [self millionaireResults][section][#"headerview"]; //this is a uitextview
}
// this did not workeither
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
UITextView *tv = [self millionaireResults][section][#"headerview"];
return tv.frame.size.height;
}
How can this problem be solved?
I updated the code per the suggestion of Michael below
Make your "UITextView *tv" object a property and then you can do something like this (assuming you only have exactly one section to your table view):
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return (self.tv.frame.size.height);
}
If you have more sections (which is appears you do), you should make that property a NSArray of UITextView objects.
This also means you need to set the contents of your "tv" object before "viewForHeaderInSection:" gets called.
This is the answer that worked for me
When you are creating the UITextView, you must set the scrollEnabled
to false.
Your UITextView must be given the width that covers horizontal space otherwise auto size calculation are off (sometimes it is sometimes it is not, i think depending on wordbreak or something, but it was inconsistent!) and only fixes itself if you rotate the device to force redraw
In the heightForHeaderInSection method, you must get the
sizeThatFits and return its height as the height of your text view
Here is the height calculation (I found this on this site http://www.raywenderlich.com/50151/text-kit-tutorial )
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
UITextView *tv1 = (UITextView *)[self millionaireResults][section][#"headerview"];
// sizethatfits calculates the perfect size for this UITextView
// if you gave your UITextView full width
CGSize goodsize = [tv1 sizeThatFits:tv1.frame.size];
return goodsize.height+4; // here 4 is not necessary, i just put it as an offset
}
Here is the code that creates those UITextView objects
for (int i = 0; i < [milarr count]; i++) {
UITextView *tv = [[UITextView alloc] init];
tv.editable = NO;
tv.attributedText = milarr[i];
// labelTopTitle and this table in question have same width in an autoLayouted view
// so i am giving width of labelTopTitle to let UITextView cover full available
// horizontal space
tv.frame = CGRectMake(0, 0, self.labelTopTitle.frame.size.width,FLT_MAX);
//tv.backgroundColor = [UIColor grayColor];
//tv.textContainerInset = UIEdgeInsetsZero;
tv.scrollEnabled = NO;
[results addObject:#{#"headerview": tv,
#"rows":#[...]
}
];
}

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

Add a second subtitle row to UITableViewCellStyleSubtitle

I have already made a custom class with the init:
if(self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier]) {
}
As I wanted to add a blue texted label to the right hand side of the cell and have it go right when selected otherwise it looked odd when highlighted:
self.sizeTextLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.contentView.frame.size.width-110, 0.0, 100.0, 44.0)];
self.sizeTextLabel.textAlignment = NSTextAlignmentRight;
self.sizeTextLabel.backgroundColor = [UIColor whiteColor];
self.sizeTextLabel.textColor = [UIColor colorWithRed:81/255.0 green:102/255.0 blue:145/255.0 alpha:1.0];
self.sizeTextLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleHeight;
[self.contentView addSubview:self.sizeTextLabel];
There is probably a better way of doing that ^
What I would like to do is add another label that is effectively another subtitle row, I then increase the height of the row when the cell is created with the heightForRowAtIndexpath method.
Problem: When I add a new label 'row' to the content view, it does not get higher (the default views shift to the middle of the view). How do I create and position it correctly below the subtitle view? If I were to change the first subtitle to multiline it would be nice if the second label knew what to do.
I wish cocoa had relative positioning. Or I haven't found it yet!
You can make the detail text label multi line and add your two strings to the one label. In this example, my data has three keys in its dictionaries, "main" for the main label text, and "detail1 and "detail2" for the two subtitle strings.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSDictionary *object = _objects[indexPath.row];
cell.textLabel.text = object[#"main"];
NSString *concat = [NSString stringWithFormat:#"%#\n%#",object[#"detail1"],object[#"detail2"]];
cell.detailTextLabel.numberOfLines = 2;
cell.detailTextLabel.text = concat;
return cell;
}
You can make a custom cell with interface builder and use it instead of UITableViewCell.
Check this: http://www.backslashtraining.com/blog/2012/3/10/ios-5-tip-of-the-week-custom-table-view-cells.html