Reputation: 1434
I have a UICollectionView, which receives data from a webservice. Based on the data it receives, it draws cells on my UICollectionView. Each cell is a custom Class which extends UICollectionViewCell. Each UICollectionViewCell is loaded via a .nib. My init method looks like this :
@implementation GridCell
- (id)initWithFrame:(CGRect)frame
{
if (self)
{
// Initialization code
NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:@"GridCell" owner:self options:nil];
if ([arrayOfViews count] < 1) {
return nil;
}
if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[UICollectionViewCell class]]) {
return nil;
}
self = [arrayOfViews objectAtIndex:0];
// It is important that you set the properties of the view after the above assignment
// because self is assigned to the nib after that call.
self.layer.masksToBounds = NO;
self.layer.shadowColor = [[UIColor blackColor] CGColor];
self.layer.shadowOffset = CGSizeMake(0.0, 1.0);
self.layer.shadowOpacity = 0.17;
self.layer.shadowRadius = 0.35f;
self.layer.shouldRasterize = YES;
}
return self;
}
I have a GridViewController which is basically a UIViewController :
@implementation GridViewController
- (id)initWithSize:(CGFloat)frameWidth :(CGFloat)frameHeight {
self = [super init];
if(self) {
// Have the getGridView returning the initialized view
// then assign it to the GridViewControllers view property
self.view = [self getGridView:frameWidth:frameHeight];
}
return self;
}
-(UIView *)getGridView:(CGFloat)width :(CGFloat)height {
UIView *gridHolder = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
UICollectionViewFlowLayout *flow = [[UICollectionViewFlowLayout alloc]init];
[flow setScrollDirection:UICollectionViewScrollDirectionHorizontal];
CGRect screen = [[UIScreen mainScreen] bounds];
CGFloat screenheight = screen.size.height;
// It is an iphone 5, setup the cellsize and the spacing between cells.
if(screenheight > 480) {
[flow setItemSize:CGSizeMake( (gridHolder.frame.size.width / 1.85) , (gridHolder.frame.size.height / 3.25) )];
[flow setSectionInset:UIEdgeInsetsMake(0, 0, 0, 0)];
[flow setMinimumInteritemSpacing:0];
[flow setMinimumLineSpacing:10];
// It is an iphone 4, setup the cellsize and the spacing between cells.
} else {
[flow setItemSize:CGSizeMake( (gridHolder.frame.size.width / 2.5) , (gridHolder.frame.size.height / 3.1) - 10)];
[flow setSectionInset:UIEdgeInsetsMake(0, 0, 0, 0)];
[flow setMinimumInteritemSpacing:0];
[flow setMinimumLineSpacing:10];
}
self.grid = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, gridHolder.frame.size.width, gridHolder.frame.size.height) collectionViewLayout:flow];
[_grid setBackgroundColor:[UIColor colorWithRed:0.961 green:0.961 blue:0.961 alpha:1]];
[_grid setDataSource:self];
[_grid setDelegate:self];
[_grid setBounces:NO];
[_grid registerClass:[GridCell class] forCellWithReuseIdentifier:Cell];
[gridHolder addSubview:self.grid];
return gridHolder;
}
This UIViewController is also my UICollectionViewDataSource. So i have the following methods for it :
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [releases count];
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(10, 10, 10, 10);
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
GridCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:Cell forIndexPath:indexPath];
return cell; // <-- finally return the cell
}
I added an UIButton over the cell. And i connected an IBAction to the button. But each time i click on this button, i get a nullpointer exception because the GridIcon is already thrown away by ARC. Must i retain the GridIcon somewhere? Or what is the best practice to overcome this (simple?) problem?
The method which resides in my GridIcon.m is :
- (IBAction)cellClicked {
NSLog(@"test");
}
And my GridIcon.h file has an IBAction described :
@interface GridCell : UICollectionViewCell
- (IBAction)cellClicked;
@end
Edit :
jackslash, thank you for your time and effort to make me realize this really mad structure. Let me explain how i setup this project.
I have a MainViewController which i set as the RootViewController in my AppDelegate.m. Then in my MainViewController i init 2 UIVIewController's. One of them is my GridViewController which sets the UICollectionView.
So the datasource for this UICollectionView is the UIViewController i init'ed. I needed to draw a region of the screen for this UIViewController so i give the height of the other UIViewController to the GridViewController. And the getGridView method gets the height of the screen minus the height of the view of the other UIViewConroller's view.
The structure is like this :
@interface MainViewController: UIViewController
// These next two classes both extend UIViewController
@property(nonatomic, strong) GridViewController *gridViewcontroller;
@property(nonatomic, strong) BottomBarController *bottomBarcontroller;
@end
In my MainViewController.m [viewDidLoad] i both init those UIViewcontrollers and add their views as a subView to the MainViewController.
What would be the best way to accomplish this then? Should i make a UICollectionViewController separate from my GridViewController? But how does this UICollectionView knows which region of the screen to draw on?
And for the init method within my GridCell, i actually use this to draw a shadow underneath my GridCell. How would i achieve the drawing underneath my cells without this init method?
Edit 2: In addition to my above edit:
Upvotes: 0
Views: 493
Reputation: 8570
So whats happening here is messed up in quite a few ways.
- (id)initWithSize:(CGFloat)frameWidth :(CGFloat)frameHeight
which one would expect to take a CGSize
from the name yet it takes two floats-(id)initWithSize::
method to init your view controllerGridCell
class files have a different name from the class they contain (bad form)You seem to fundamentally have misunderstood how all this is supposed to work. I'm going to help you here, but bear in mind that you should take the time to understand everything here and to also go and read a lot more about iOS. Perhaps look at the iOS course on iTunes U (CS193p) which is free and will help you write better code.
This is madness. If you want to load your UICollectionViewCell
subclass from a xib you dont do this, you register the nib with the collection view in a UICollectionViewController
subclass like this:
UINib * cellNib = [UINib nibWithNibName:@"GridCell" bundle:[NSBundle mainBundle]];
[self.collectionView registerNib:cellNib forCellWithReuseIdentifier:cellReuseIdentifier];
Then the cell is loaded from the xib file for you when you get the cell in
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
Make sure you have the class of the cell in your GridCell.xib
file set to your GridCell
subclass. Never write an init method like this again.
-(UIView *)getGridView:(CGFloat)width :(CGFloat)height
Always name all the parameters in an Objective-c method signature.
-(UIView *)getGridViewWithWidth:(CGFloat)width andHeight:(CGFloat)height
Is miles and miles better. But you would probably better using a CGSize
...
- (id)initWithSize:(CGFloat)frameWidth :(CGFloat)frameHeight
In Objective-c we would expect this to take a CGSize parameter because your method is named "initWithSize". A CGSize
is a C struct with a width
and height
. You can make one with CGSizeMake(<width>,<height>)
. You would make your init method look like this:
- (id)initWithSize:(CGSize)size
But you don't really want to be initing a UIViewController
with a CGSize
...
There is a reason UIViewController
does not have ANY kind of init method that takes any kind of size or dimension. The idea of a view controller is that it has the size of its view set by its parent (or window) and then is responsible for drawing its contents within its view, whatever size that may be. As a UIViewController
subclass In UIKit
there is a place for loading other views and view controllers that are needed to display whatever it is you are supposed to display. Check out the method -(void)viewDidLoad
and override it. But even so you would be much better off if you simply subclassed UICollectionViewController
as you wouldn't need any sort of container and your view controller's view property would already be a collectionView along with many other benefits.
Rename your GridIcon.h
and GridIcon.m
files to be the name of the class they contain. Always follow this rule for the sanity of future you, if not everyone else who works on your project.
Finally the reason its crashing isn't because of ARC or other technology failure its because the implementation here has real fundamental issues.
Upvotes: 5