cellForItemAtIndexPath: firing in iOS7 but not iOS6 - ios

When recently adding a UICollectionView with a custom UICollectionViewFlowLayout subclass, the collectionView:cellForItemAtIndexPath: method is only being called on iOS7, and not on iOS6. In other words, everything works great in iOS7 but my custom collectionView items are not showing up in iOS6. Interestingly, the cells appear to be there (the collectionView scrolls), but all items are empty with a white background.
The collectionView was set up in a .xib file, dataSource and delegate were attached, and UICollectionViewDataSource and UICollectionViewDelegateFlowLayout were added after the #interface call in the view controller .h file.
The collectionView item size, section inset, line spacing, and inter-item spacing are all being set in the custom flow layout init method.
Some code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.collectionView.collectionViewLayout = [[TFSpringFlowLayout alloc] init];
[self.collectionView registerClass:[TFWorkoutCell class] forCellWithReuseIdentifier:CellIdentifier];
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
// This method is returning a value > 0
return _workouts.count;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
// This is being called on iOS7, but is never being called on iOS6
...removed for clarity
return cell;
}
EDIT: Issue Solved. My custom Flow Layout included some iOS7-specific overrides utilizing the new UIDynamicAnimator class. These were not causing a crash, but were preventing the cells from being drawn in iOS6.

Here is what the issue was for me, in case anybody else runs into this problem in the future.
My custom UICollectionViewFlowLayout contained several method overrides to implement iOS7's new UIKit Dynamics. These did not cause the application to crash, but prevented the cells from being drawn in iOS6.
Here is the offending code:
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
return [self.dynamicAnimator itemsInRect:rect];
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
return [self.dynamicAnimator layoutAttributesForCellAtIndexPath:indexPath];
}
And the simple change required for the fix:
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
if ([UIDynamicAnimator class])
return [self.dynamicAnimator itemsInRect:rect];
else
return [super layoutAttributesForElementsInRect:rect];
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
if ([UIDynamicAnimator class])
return [self.dynamicAnimator layoutAttributesForCellAtIndexPath:indexPath];
else
return [super layoutAttributesForItemAtIndexPath:indexPath];
}
Adding an if/else statement to check for iOS6 or iOS7 and only returning the appropriate response fixed the issue for me. Hope this helps someone else!

Related

Init method for custom UICollectionViewCell doesn't fired

I have UICollectionView that use custom UICollectionViewCell class called ClaimInfoCell. I want to fire its init method by overriding initWithCoder: but this method doesn't fired.
- (id)initWithCoder:(NSCoder*)aDecoder
{
if(self = [super initWithCoder:aDecoder])
{
// Do something
}
return self;
}
I checked in collectionView:cellForItemAtIndexPath: and everything is fine. NSLog-ing that cell, and it was the ClaimInfoCell's instance.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ClaimInfoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"claimInfoCell" forIndexPath:indexPath];
return cell;
}
Already followed this and this answer, but I can't find the right that fits on mine.
I use storyboard and customize that cell on its collection view directly. So how I should declare my custom init method my cell? Any help would be appreciated. Thank you.

How do I create UITableViewCells with dynamic height that are compatible with both iOS7 and iOS8?

I am creating an app that I want to run under both iOS7 and iOS8, that has variable table cell heights. In iOS7 I used to define a prototype cell and then use this in heightForRowAtIndexPath to calculate the height, based on the size of the text placed into labels.
This no longer seems to work when using Xcode 6. I created a small test app today to see if I was going crazy (maybe I am). I started by NOT defining heightForRowAtIndexPath at all. When I test it with iOS7 and iOS8 devices it behaves differently. In the iOS8 device it automatically determines the cell height and works fine. In iOS7 the cells are returned all the same height. It doesn't do the automatic height adjustment for the cells with variable text, for iOS7, only for iOS8.
So... me thinking... hmmm... under iOS8 I can use auto layout to determine the height and only implement heightForRowAtIndexPath for iOS7...
Okay, so... I tried adding in heightForRowAtIndexPath. Under iOS7 it crashes on method systemLayoutSizeFittingSize. I played with this for hours and nothing I do seems to change this. It is extremely simple code.
Here is the UITableViewController code:
//
// TestTableViewController.m
// testTables
//
#import "TestTableViewController.h"
#import "Cell1.h"
#interface TestTableViewController ()
#property (nonatomic, strong) Cell1 *cellPrototype;
#end
#implementation TestTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"Cell1" bundle:nil] forCellReuseIdentifier:#"Cell1"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 10;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
self.cellPrototype = [self.tableView dequeueReusableCellWithIdentifier:#"Cell1"];
self.cellPrototype.label1.text = [self cellContents:indexPath];
[self.cellPrototype layoutIfNeeded];
CGSize size = [self.cellPrototype systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Cell1 *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell1" forIndexPath:indexPath];
cell.label1.text = [self cellContents:indexPath];
return cell;
}
- (NSString*)cellContents:(NSIndexPath *)indexPath {
if (indexPath.row == 0) {
return #"one line";
}
else if (indexPath.row == 1) {
return #"two lines I think will result from this piece of text that goes over";
}
else if (indexPath.row == 2) {
return #"three lines I think will result from this piece of text that goes over and then even extends a little more again";
}
else {
return #"three lines I think will result from this piece of text that goes over and then even extends a little more again, and then extends over more and more lines like it will go on forever and never end but in fact it does end right here.";
}
}
#end
Here is the cell header (there is nothing in the .m file):
//
// Cell1.h
// testTables
//
#import <UIKit/UIKit.h>
#interface Cell1 : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *label1;
#end
Here is the xib for the cell. As you can see, it's not complex:
So... does this old method not work any more? How can I get variable height cells that will run in both iOSes?
Many of the warnings you are describing are explained in detail here. Automatic Preferred Max Layout Width is not available on iOS versions prior to 8.0
Prior to iOS 8, the preferredMaxLayoutWidth needed to be set manually. This property can be set anywhere if it's unlikely to change but my preferred solution is to use a subclassed UILabel that automatically sets the preferredMaxLayoutWidth.
Your subclassed UILabel would override layoutSubviews so that its preferredMaxLayoutWidth is always the same as its width.
- (void)layoutSubviews {
self.preferredMaxLayoutWidth = self.bounds.size.width;
[super layoutSubviews];
}

dequeueReusableCellWithReuseIdentifier doesn't go through init nor initWithCoder functions

When creating cells for UICollectionView, dequeueReusableCellWithReuseIdentifier doesn't go through init nor initWithCoder function of CategoryView.
The view is creating, it has a proper type (CategoryView) but init nor initWithCoder of CategoryView is not called, so essential functionality is not executed. Is there some other init in this senario?
- (CategoryView *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CategoryView *cell = [cv dequeueReusableCellWithReuseIdentifier:#"CategoryView" forIndexPath:indexPath];
[cell someConfiguration];
return cell;
}
In this case, the problem was that the base class was not specified for your cell prototype in Interface Builder. So make sure the base class is set:
Then, when you call dequeueReusableCellWithReuseIdentifier, using the storyboard identifier you specified in the prototype cell, it calls initWithCoder when the cell is first instantiated. If the cell scrolls out of view and is later re-used for another NSIndexPath, the prepareForReuse is called:
#interface CategoryView : UICollectionViewCell
#end
#implementation CategoryView
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
NSLog(#"init");
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
NSLog(#"reuse");
}
#end
try - (instancetype)initWithFrame:(CGRect)frame
It works for me. I wrote UI programmatically.
Good luck!
UICollectionViews and UITableViews reuse cells to improve performance. initWithCoder: will only run once per reusable cell. As such, if you need something called every time a cell is displayed I would recommend writing a method as follows in your cellForRowAtIndexPath: method:
- (CategoryView *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CategoryView *cell = [cv dequeueReusableCellWithReuseIdentifier:#"CategoryView" forIndexPath:indexPath];
// self.parameters = an NSDictionary of the colors, text, etc. you need to the cell to know about
[cell configureWithParameters:self.parameters];
return cell;
}
Then, inside your configureWithParameters: method you can include colors, text, etc. that will help you setup your CategoryView.
You'll have to declare your configureWithParameters: method in your CateogryView.h as follows:
// CategoryView.h
- (void)configureCell:(NSDictionary *)parameters;
Then include your customizations in the .m as follows:
// CategoryView.m
- (void)configureCell:(NSDictionary *)parameters{
// Put Whatever initialization code you need here
// Example:
self.label.textColor = parameters["color"];
self.label.text = parameters["text"];
}
Make sure you have collection view delegate/datasource connected to your class.
Make sure you give base class for cell.
Make sure your cell has reuse identifier.
Make sure to register Nib for your reuse identifier. (You don't need to do that if your cell present in your collection view in storyboard).
And finally try changing
- (CategoryView *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
to
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
Now your
- (instancetype)initWithCoder:(NSCoder *)aDecoder
Should be called.
Assuming you've subclassed UICollectionViewCell, you can put your initialization code in awakeFromNib().

iOS 6 CollectionView Dynamically change Layout

I'm kinda newbie in UICollectionView and I'm working in a project that dynamically changes the UICollectionViewLayout in a given action.
My CollectionView has 6 sections, each of them with 10 elements. Those Cells are basically an UIImageView and my Custom Layout called StackViewLayout stacks all elements for each section (something like Apple's Photos.app).
If the user selects the element of the stack (for all sections), the UICollectionView dynamically changes the layout to the UICollectionViewFlowLayout, so all the elements can be viewed as grid.
My problem is that when user selects a stack, no matter which section, when the Layout is changed do Flow Layout, all sections are displayed in the grid, instead of displaying the elements for the selected section (stack), which is the behavior I wanted.
Is there any way to show only the Flow Layout for the selected section in the Custom Layout?
Here is my Controller Implementation snippet code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadStackLayout]; // stack all sections at first load.
}
#pragma mark - UICollectionView Data Source Methods
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 6;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 10;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
{
// custom UICollectionViewCell, which will hold an image.
CVCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
cell.imageView.image = _images[indexPath.row];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
// element size.
return CGSizeMake(100,100);
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (isStacked)
{
[self loadFlowLayout]; //HERE I WANT TO LOAD FLOW LAYOUT ONLY FOR THE SECTION OF THE SELECTED ITEM!
} else
{
// to implement.
}
}
// HERE IS THE METHOD THAT CALLS MY CUSTOM LAYOUT IN ORDER TO STACK THE ELEMENTS FOR EACH SECTION IN COLLECTION VIEW. IT IS WORKING AS IT SHOULD.
-(void)loadStackLayout
{
if (([self.collectionView.collectionViewLayout isKindOfClass:[UICollectionViewFlowLayout class]]))
{
isStacked = YES;
[self.collectionView setCollectionViewLayout:[[StackViewLayout alloc] init] animated:YES];
}
}
// HERE IS THE METHOD THAT CALLS MY FLOWLAYOUT IN ORDER TO UN-STACK THE ELEMENTS AND SHOW THEM IN A GRID. CURRENTLY IT IS SHOWING ALL SECTIONS IN THE GRID.
-(void)loadFlowLayout
{
if (([self.collectionView.collectionViewLayout isKindOfClass:[StackViewLayout class]]))
{
isStacked = NO;
[self.collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init] animated:YES];
}
}
I think you can probably do it by having an if clause in your numberOfItemsInSection: method, to return 0 for any section that's not the selected one. Obviously, you'll need to keep track of the selected section to do this.

iOS: How can I change the orientation of a UICollectionViewCell?

I have a UICollectionViewController that I load when a user turns their iPad so as to put it into landscape orientation. The problem I’m having is that my UICollectionViewCells are still loading as if it were in portrait orientation. I set the UICollectionViewController to landscape inside Interface Builder, and I’m using a custom cell class that I’ve called CollectionViewCell. Each cell contains just an image and a label.
Is there something that I should be doing either in Interface Builder or in my code? I tried using CGAffineTransformationMakeRotation, but that was no help. If anyone else has encountered this before, I would be extremely grateful! Thanks very much!
Here’s a bit of the code for reference:
-(void)viewDidLoad
{
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
}
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section
{
return listArray.count;
}
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView
{
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"devices" forIndexPath:indexPath];
cell.statusImage.image=[UIImage imageNamed:#"Default.png"];
cell.name.text=#"HEY !!!!!";
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(320, 420);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(50, 20, 50, 20);
}
In case anyone else has had this problem, here is how I solved it.
I was initially not allowing autorotation, and instead was just registering for orientationChanged notifications using NSNotificationCenter. This worked great except that it was preventing my second view from realizing that it needed to load in landscape mode.
So instead of that, I’ve created a class for the tabBar that my main view is embedded in, and I return NO for the shouldAutoRotate method in that class and in every other view except for the one which I want to load in landscape mode. I’m still using the orientationChanged notifications in my main view’S controller, since it is embedded in the tabBar. But I’m just using autorotation when returning to the main view.
If anyone has any questions, let me know. Hope it helps!

Resources