Objects inside my custom UITableViewCell (created with storyboard) are nil - ios

I've created a custom table view cell in my iPhone app through the following steps.
In my storyboard, I created a sample cell, dragged in a UILabel and a UIImageView.
Added new files, which I made a subclass of UITableViewCell.
In Interface Builder, I selected my cell and I assigned its class as the class I just created in step 2.
In the code for my custom table view cell, I created two IBOutlet properties and connected them to my UILabel and UIImageView in the storyboard.
My custom table view cell also includes a method, where it receives another object from which it sets its own attributes:
-(void)populateWithItem:(PLEItem *)item
{
if (item.state == PLEPendingItem) {
status.text = #"Pending upload..."; //status is a UILabel IBOutlet property
}
else if(item.state == PLEUploadingItem)
{
status.text = #"Uploading...";
}
imageView.image = [UIImage imageWithContentsOfFile:item.path]; //imageView is the UIImageView IBOutlet property
}
This method is called from my tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath as follows:
PLEPendingItemCell* cell = (PLEPendingItemCell*)[tableView dequeueReusableCellWithIdentifier:item_id];
if (cell == nil) {
cell = [[PLEPendingItemCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:pending_id];
}
[cell populateWithItem:((PLEItem*)[itemList objectAtIndex:indexPath.row])];
return cell;
The problem is that the cells always show up empty. I set a breakpoint in populateWithItem and realized that both the status UILabel and the image UIImageView were nil inside that method.
Shouldn't IB be initializing these? If not, where should I be doing that?

If you're setting up your cell in the storyboard, you always need to create your cell using tableView:dequeueReusableCellWithIdentifier:forIndexPath: because that's where the storyboard creates your cell and hooks up the views.
Creating a cell with its constructor directly, as in your sample code, won't load any subviews from a storyboard, nib, etc. The class you've made doesn't know anything about the storyboard prototype cells.

Related

Reuse of UICollectionViewCells during scrolling

I'm having an issue,
I have a simple UICollectionView with a static 200 cells that load images from Flickr.
my CellForItemAtIndexPath looks like this:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
if(![[cell.subviews objectAtIndex:0] isKindOfClass:[PFImageView class]])
{
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
[cell addSubview:imageView];
}
return cell;
}
PFImageView is a subclass of UIImageView that loads a Flickr photo URL on a background thread and then updates it's own image on the main thread - this works fine.
The logic is really simple - I create a cell if there isn't one dequeueable.
If the cell (which I'm expecting to be dequeued and already have a PFImageView) doesn't have a PFImageView, I alloc and init an imageView for the cell and add it as a subview of the cell.
Thus I expect if the cell has been dequeued it should already have a PFImageView as a subview and as we should not get into the if statement to create a new imageView and kick off a new photo download request
Instead what I see is that the cells at the top and bottom of the UICollectionView that 'go off screen' momentarily - when they come back on screen they are not being reused and seemingly a new cell is created and the picture refreshed.
1) How can I achieve a static image once the cell has been created (i.e. not refreshing when the cell goes slightly off screen.
2) Why are the cells not being reused?
Many thanks for your time.
John
UICollectionView will reuse cells for maximum efficiency. It does not guarantee any particular reuse or population strategies. Anecdotally, it seems to place and remove cells based on integer power of two regions — e.g. on a non-retina iPad it might divide your scroll area up into regions of 1024x1024 and then populate and depopulate each of those regions as they transition into and out of the visible area. However you should not predicate any expectations on its exact behaviour.
In addition, your use of collection view cells is incorrect. See the documentation. A cell explicitly has at least two subviews — backgroundView and contentView. So if you add a subview it will be at index 2 at the absolute least and, in reality, the index will be undefined. In any case you should add subviews to contentView, not to the cell itself.
The most normal way of doing what you're doing would be to create a custom UICollectionView subclass that inherently has a PFImageView within it.
I see several potential issues:
You are looking specifically at index 0 of the cell for the child class that you are adding. The UICollectionViewCell may have other views as children, so you can't just assume that the only (or first) child is the one you added.
I don't see that you are calling registerClass:forCellWithReuseIdentifier: or registerNib:forCellWithReuseIdentifier:, one of which is required for proper use of dequeue (https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewCell_class/Reference/Reference.html).
You are only setting the URL of the PFImageView in the case that you have to construct the PFImageView. The idea with dequeuing reusable views is that you will only construct a small subset of the views needed, and the UITableView will recycle them as they move offscreen. You need to reset the value for the indexPath that is being requested, even when you don't construct the new content.
If your case is as simple as you describe, you can probably get away with adding your PFImageView to the contentView property of your dequeued UICollectionView.
In your controller:
// solve problem 2
[self.collectionView registerClass:[UICollectionViewCell class] forReuseIdentifer:#"FlickrCell"];
In collectionView:cellForItemAtIndexPath
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
// solve problem 1 by looking in the contentView for your subview (and looping instead of assuming at 0)
PFImageView *pfImageView = nil;
for (UIView *subview in cell.contentView.subviews)
{
if ([subview isKindOfClass:[PFImageView class]])
{
pfImageView = (PFImageView *)subview;
break;
}
}
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
if (pfImageView == nil)
{
// No PFImageView, create one
// note the use of contentView!
pfImageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.contentView.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell.contentView];
[cell.contentView addSubview:pfImageView];
}
else
{
// Already have recycled view.
// need to reset the url for the pfImageView. (Problem 3)
// not sure what PFImageView looks like so this is an e.g. I'd probably remove the
// URL loading from the ctr above and instead have a function that loads the
// image. Then, you could do this outside of the if, regardless of whether you had
// to alloc the child view or not.
[pfImageView loadImageWithUrl:staticPhotoURL];
// if you really only have 200 static images, you might consider caching all of them
}
return cell;
For less simple cases (e.g. where I want to visually lay out the cell, or where I have multiple children in the content), I typically customize my UICollectionViewCell's using Interface Builder.
Create a subclass of UICollectionViewCell in the project (In your case, call it PFImageCell).
Add an IBOutlet property to that subclass for the view I want to change in initialization (In your case, a UIImageView).
#property (nonatomic, assign) IBOutlet UIImageView *image;
In Interface Builder, create a prototype cell for the UITableView.
In the properties sheet for that prototype cell, identify the UICollectionViewCell subclass as the class.
Give the prototype cell an identifier (the reuse identifier) in the property sheet.
Add the view child in interface builder to the prototype cell (here, a UIImageView).
Use IB to map the IBOutlet property to the added UIImageView
Then, on dequeue in cellForRowAtIndexPath, cast the dequeued result to the subclass (PFImageCell) and set the value of the IBOutlet property instance. Here, you'd load the proper image for your UIImageView.
I am not sure if the cell is being re-used or not. It may be being reused but the subview may not be there. My suggestion would be to create a PFImageViewCollectionViewCell Class (sub class of UICollectionViewCell) and register it as the CollectionView Cell and try. That's how I do and would do if I need a subview inside a cell.
Try adding a tag on this particular UIImageView
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static int photoViewTag = 54353532;
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
PFImageView *photoView = [cell.contentView viewWithTag:photoViewTag];
// Create a view
//
if (!photoView) {
photoView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
imageView.tag = photoViewTag;
[cell.contentView addSubview:imageView];
}
// Update the current view
//
else {
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
photoView.imageURL = staticPhotoURL;
}
return cell;
}
I would really recommend to create your own UICollectionViewCell subclass though.
EDIT: Also, note that I used the contentView property instead of adding it directly to the cell.

Cannot set properties on custom table view cell

I have a problem settings my view elements on a custom cell. The table cells appear in my tableView, but the properties do not set and thus only empty/blank cells appear.
The tableView is not a tableView controller, but only a tableView in a viewController.
I have the following files:
CustomCell.xib:
Here i use IB to build the custom cell by using a Table View Cell from object library with labels and images on. I set the Identifier as orderListCell. From this screen I ctrl+drag to create the outlets in customCell.h
CustomCell.h
Here I see all my IBOutlets as properties from above mentioned file
CustomCell.m
Here I leave as is
OrderListViewController.h
Here I import customCell.h and use protocols UITableViewDelegate, UITableViewDataSource
OrderListViewController.m
Here I set my tableView delegate and tableView dataSource to self. I also create an IBOutlet for my tableView from the Storyboard.
I use the following code to try and display my cells:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"orderListCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[CustomCell alloc] init];
}
cell.myLabel.text = #"aaaaaaaa"; //[[self.orders objectAtIndex:indexPath.row] objectForKey: #"tableNo"];
return cell;
}
I have simplified my code a bit to demonstrate that even setting the label to a simple string (#"aaaaaaaa") doesnt work. When I look at the objects in my debugger the cell does have all the properties from the IBOutlets and the cell does appear in my tableView, just the label.text = xxx does not seem to work.
I have looked at the following posts but either dont understand it properly or it does not work for me:
ios 7 customizing UITableViewCell's content view
Can't set properties in Custom UITableViewCell
Set label for a custom cell
Any help or advice would be greatly appreciated
cell = [[CustomCell alloc] init]; does not create your cell from the XIB, so none of the XIB content will be created.
Use registerNib:forCellReuseIdentifier: to register the XIB with the table view so that it will create your cell instances for you (you don't need to create instances yourself in cellForRowAtIndexPath).
If you don't want to do that, you can load your NIB explicitly with nibWithNibName:bundle: and instantiateWithOwner:options:, then get the cell instance from the list of returned views (which should only contain 1 item).

How do I have a UITableView with two different cell types and set each of the`cell layouts up programmatically, instead of via a Storyboard?

Previously I had this set up with a storyboard, having dragged the UILabels, positioned them and sized them whatnot on the UITableViewCell I dragged them onto, and then do a different version of that for the other UITableViewCell.
For example, like follows (but in the picture they've yet to be customized with the labels):
Then in the datasource, I'd simply check the Identifier, and depending on what the Identifier was, customize the cell accordingly.
However, I've needed more customization than I can get from the storyboard, as each cell is going to have two UIViews (a top one and a bottom one to allow sliding of the top one) so I can't really do this with storyboarding, as I add the labels and everything to the UIView programmatically.
But my question is: When I do it programmatically, how can I tell which cell is which so I can customize the layout of the UILabels accordingly? With a storyboard I can obviously just drag a UILabel onto each one, but when doing it programmatically and setting up the UIView, I don't know how to say, "Hey, if the identifier is this, add the UILabels like so" because the UIViews aren't aware of any Identifiers.
Basically the structure looks like this:
UITableView -> UITableViewCell -> CellFront(UIView) & CellBack(UIView)
And the look of the cell comes from the labels added to the CellFront UIView. But there's two looks to the cells and I don't know how to do it without a storyboard.
Although UIViews are not aware of identifiers, they have a property called tag which can be used for any purpose that you would like. You can set the tag to, say, 1 on cells of one kind, and to 2 on cells of the other kind, and then use the tag to distinguish the cells in code. Moreover, once your views are tagged, you can call viewWithTag: on the containing view, and get back the view with the tag that you want.
If you are creating the cells solely in code, then you register your UITableViewCell subclass in the viewDidLoad method of your table view controller. That method sets the identifier. Then, you use that identifier in cellForRowAtIndexPath: just like you would for a xib or storyboard created cell.
[self.tableView registerClass:[MyCellSubclass class] forCellReuseIdentifier:#"MyIdentifier"];
Here is one approach:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Adjust the condition to match your needs
if (indexPath.row == 0) {
static NSString *Identifier1 = #"CellType1";
// cell type 1
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier1];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier1];
// add subviews here
}
// set cell properties
return cell;
} else {
static NSString *Identifier1 = #"CellType2";
// cell type 2
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier2];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier2];
// add subviews here
}
// set cell properties
return cell;
}
}

Image on top of Cell

Can i put an image in the top of an TableViewCell instead of putting it on the left side ? I used this to put it in the cell:
Code:
cell.imageView.image = [UIImage imagenNamed:#"river.png"];
For asynchronous image downloading and showing on table use this:
go to https://github.com/enormego/EGOImageLoading
and download the classes.
then in your .h file:
#class EGOImageView;
EGOImageView *imageIconView; // object for caching the images
and in .m file
#import "EGOImageView.h"
#import "EGOCache.h"
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell * cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
imageIconView=[[EGOImageView alloc] initWithPlaceholderImage:[UIImage imageNamed:#"icon72x72.png"]];
[self setFlickrPhoto:[NSString stringWithFormat:#"yourimageurl"];
imageIconView.frame=CGRectMake(0, 0, 120, 100);
[cell addSubview:imageIconView];
return cell;
}
- (void)setFlickrPhoto:(NSString*)flickrPhoto
{
imageIconView.imageURL = [NSURL URLWithString:flickrPhoto];
}
You can create a custom cell. You just pick style custom for the cell type. If you are using static cells you can just insert an ImageView in the cell, drag an outlet to the controller header file and set the image in the code.
If you are using dynamic cells its best to create a new class for your cells. Set the custom class for the cell in the interface builder. Create and outlet for the imageview in the custom cell class header. Then when you instantiate cells in the tableView:cellForRowAtIndexPath, set the cell class to the custom cell class and set its property.
There are very good tutorials for storyboards that cover dynamic and static cell tables:
Beginning Storyboards in iOS Part 1
Beginning Storyboards in iOS Part 2
you have set the frame of an imageview in cellforRowAtIndexPath
Yes you can. for this you have to go for custom tableview cell. you can design this using separate XIB for tableview cell. May be this link will help you.
or search on google also for more examples its easy to implement.

Connect outlet of a Cell Prototype in a storyboard

I'm a newbie with the Storyboard and so I have some difficulties...
I have created a TableViewController and I would like to customize the Cell Prototype. In the Cell Prototype I have added several Labels I would like to customize with my own class which inherits from UITableViewCell (AreaListCell).
In the Storyboard, for the Cell Prototype I have configured the Custom Class with "AreaListCell" and its style is "Custom".
In the storyboard, when I select the Cell Prototype and then the assistant, the assistant display my class that implements the UITableViewController (AreasTableViewController) and not
my "AreaListCell" class.
The consequence is I can create outlet (using Ctrl + Drag from the label of the Cell Prototype) to the AreasTableViewController class but not to the AreaListCell class !
Any idea how to connect the Cell Prototype with my AreaListCell class?
Thanks for your help!
UPDATE: As of Xcode 4.6 (possibly earlier) you can now create outlets by control-dragging! - This has to be done into an interface section or class extension (the class extension doesn't exist by default for new cell subclasses. Thanks to Steve Haley for pointing this out.
You can't get the outlet automatically connected and created by dragging into the code block in the assistant editor, which is poor, but you can create the outlets manually and connect them then.
In your cell subclass interface:
#interface CustomCell : UITableViewCell
#property (nonatomic) IBOutlet UILabel* customLabel;
#end
Synthesize as normal in the implementation.
In the storyboard, select the cell and go to the connections inspector, you will see the new outlet. Drag from there to the relevant element in your prototype:
This can now be accessed as cell.customLabel in your cellForRowAtIndexPath: method.
Yeah you can't connect views that are inside of a custom prototype cell using the ctrl+drag method. Instead use the tag property of the view and then when you are building the cell pull the labels out using their tags.
Here:
//Let's assume you have 3 labels. One for a name, One for a count, One for a detail
//In your storyboard give the name label tag=1, count tag=2, and detail tag=3
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomTableViewCell *theCell = [tableView dequeueReusableCellWithIdentifier:#"Prototype Cell"];
UILabel *nameLabel = (UILabel *)[theCell viewWithTag:1];
UILabel *countLabel = (UILabel *)[theCell viewWithTag:2];
UILabel *detailLabel = (UILabel *)[theCell viewWithTag:3];
nameLabel.text = #"name";
countLabel.text = #"count";
detailLabel.text = #"details";
return theCell;
}
You could also set the labels up as properties in your custom cell code and then when the cell is initialized use the viewWithTag call to assign the label properties to the labels you have created on your storyboards.
It took me a few days to realize I couldn't ctrl+drag from inside a custom cell to create an IBOutlet.
Good luck!
EDIT: You CAN create IBOutlets for your labels inside of a custom cell and create the links programatticaly, just not through the ctrl+drag method.
EDIT 2: I was totally wrong, you can ctrl+drag. See the second answer to this question. It is tricky, but it works quite well.
Swift 3
// we are using this if your images are at server.
// we are getting images from a url.
// you can set image from your Xcode.
The URL of images are in an array name = thumbnail i.e self.thumbnail[indexPath.row]
on UITableviewCell put a imageView on cell
select UIimageView assign it a tag from storyboard.
let pictureURL = URL(string: self.thumbnail[indexPath.row])!
let pictureData = NSData(contentsOf: pictureURL as URL)
let catPicture = UIImage(data: pictureData as! Data)
var imageV = UIImageView()
imageV = cell?.viewWithTag(1) as! UIImageView
imageV.image = catPicture

Resources