I want two custom (i.e. subclassed) UIViews in a subclassed UITableViewCell as shown in the below picture. The two UIViews are the same subclass.
Both the custom UIViews and the TableViewCell have associated xib's.
I would appreciate some advice on the best way to go about this. I load the TableViewCell this way.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CustomCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:NULL];
// CustomCell is an IBOutlet connected to the above nib
cell = BLCustomCell;
}
// configure the cell
}
I want to set outlets in the Custom Views to easily display data from my data model. Do I need a view controller for the Custom Views? I'm having trouble getting the nib to load for the Custom Views. (And yes, I realize my code above does not address this issue.) How do I get it to load? Does the TableView's controller need outlets to the Custom View objects?
Thanks!
The simplest way to handle complex UITableViewCells is to create a subclass of UITableViewCell, with its own IBOutlets that connect to the subviews, then just set properties of your custom cell in cellForRowAtIndexPath:. There are various other approaches, but this one seems to break down the problem reasonably well, and expands to handle more complex situations.
Take a look at the iOS Recipes book by Matt Drance, it cover this area well.
Related
I have custom cell created with nib. In the table view I am using the method -(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath to set the height of cell
Everything works fine.
But the problem is that I also want to change the size of UIlabel which is added as subview in nib of cell.
How do I do that?
Which metod to override in customcell class ?
The method you are looking for is:
-(void)layoutSubviews
{
[super layoutSubview];
//Do your magic
}
layoutSubviews is call after the cell is created and whenever the device orientation changes, to allow you to resize and/or move the subviews (plus make any other minor adjustments) to compensate for differences between the orientations, but in this case you can also use it to redraw your subviews.
As you have UILabel in your custom cell class, make a function in that class that take frame you want to set as parameter. Set frame of label in that function. You need to call this function from your cellForRowAtIndexPath method before returning the cell.
If you are reusing your custom cell you should call method only when the (cell == nil)
Also if you can add some code in your question that would be helpful and you can get more precise answer.
You need to treat your custom cell in the same way you would treat a normal view or view controller class with a xib. i.e. You need to create IBOutlets for the controls in you custom cell and during creation of the cell you can access the controls quite easily.
myCell.myCustomUILabel.text = #"blah"
There are some gotchas when using custom cells in a xib and onnecting up the IBOutlets. This SO answer (of mine) explains how to create and link up IBOutlets of a custom cell.
Using storyboard, static cells, in cellForRowAtIndexPath: the line
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
always returns nil.
I have checked the following:
Identifier of the cell is correctly set in IB/Storyboard and I use the same identifier in code. I verified this many times.
I have not instantiated the view controller elsewhere (which was the problem in this stackoverflow question).
My view controller is a subclass of UITableViewController, of course, wrapped into an ad hoc navigation controller in storyboard. Suspecting that my view controller somehow does not know about the cell identifiers defined in storyboard because it might be another instance, here is the code the "instantiates" it. In prepareForSegue:, I use
CustomViewController *vc = [[[segue destinationViewController]
viewControllers] objectAtIndex:0];
Other customizations of the view controller done here (setting properties etc.) works fine.
I am using static cells because the number of sections and rows does not change, and each cell contains static text (and other controls or text fields to be edited).
It seems to me this is a very common task (customize static cells from storyboard in the view controller's datasource methods). What am I doing wrong?
With static content in a table view, you do not implement any of the datasource methods (including tableView:cellForRowAtIndexPath:, so you would never dequeue the cells. There is no dequeuing for static content (that you can get involved in, anyway).
If you want to get a pointer to a particular cell:
get it from the table view using cellForRowAtIndexPath::
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
have an outlet to the specific cell and customise it directly.
Iterate through the cells and check the reuseIdentifier property to get the cell you are interested in.
Any of these things can be done in viewWillAppear or similar.
If you want to have completely different content in your cells to that found on the storyboard then static cells probably aren't the right choice. You should use dynamic prototypes (note you can have multiple prototypes in the storyboard) with the traditional data source methods instead.
You can still use dataSource/delegate methods of static UITableView, you just don't have to create new cells.
If you want to modify cells with dataSource methods, inside cellForRowAtIndexPath: :
UITableViewCell * cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
and then start modifying cell.
The solution was to use prototype cells rather than static cells. I am still giving the check to #jrturton as he was the first who got me the idea.
Another interesting error I just solved: with prototype cells of type "Custom", if you try to fill cell.textLabel with text, it will just automatically work, but all your other subviews behave very strangely. I just use my custom label now, and everything works fine.
Cheers, thanks everyone for helping out.
Different to the answer above,
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
will not work. But the mentioned method to create an outlet to the cell itself is working.
It is also possible to place views such as UIButtons or UITextFields on the cell and have outlets for those as well.
Both methods can also be used in combination. E.g. set the cell.textLabel.text for a particular cell and have another control which will be accessed from the controls outlet.
In the storyboard, the Static Cells CAN'T implement the Methods in the <UITableViewDataSource> protocol.
So you could use the methods which ones are include in <UITableViewDelegate>.
I know this is a very old question, but the better way to handle this is to use tableView(_:willDisplay:forRowAt:)
I want to add a UIPickerView to a UITableViewCell. Right now I am just getting a black window. Can someone also explain me the concept of subclassing UI objects to a cell ie:when do we make our cell as the delegate and the datasource delegate? Thanks
EDIT: Here is the code of what i am doing right now
case 1: {
cell = [tableView dequeueReusableCellWithIdentifier:#"groups"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"groups"];
}
/*NSString *myGroupDetail = [_groupArray objectAtIndex:indexPath.row];
NSLog(#"the group detail label is %#",myGroupDetail);
cell.textLabel.text = myGroupDetail;
*/
[cell addSubview:_groupPicker];
break;
}
The groups is the section in which I want the picker view, I am getting that data from an array.
you would have to allocate and initialize a pickerview in your cellForRow method of the tableviewdelegate. ill sketch it for you =) how to initialize a cell itself should not be hard to find out if you google a bit ;-)
...(tableView *)... cellForRowAtIndexPath... {
if(indexPath.row == pickerRow){
UIPickerView *pickerView = [[UIPickerView alloc]init];
cell = ... // alloc and initialize a cell
cell addSubview:pickerView];
}
else{ // your other cells }
return cell;
}
UPDATE: im currently having trouble with git, so i uploaded a sample project to my private server: UITablePicker example
github: https://github.com/sebamisc/UItableViewWithPicker
yu can modify and use the code however you want =)
sebastian
Well, I never did exactly that.
Does it have to be in a cell or could you use the table's header or footer view for that? (I would not suggest a section header/footer view.)
Assuming it is within a UITableViewCell.
Yes, I personally would subclass UITableViewCell. Did that a lot. In that case you cell object could be the data source delegate of the picker. For doing so your subclass of UITableViewCell (let's assume you name it MyTableViewCell) needs to fulfil the related protocol. You add that UIPickerView programmatically within the init Method (initWithStyle) of MyTableViewCell. For the layout within the table cell, you should overwrite the method layoutSubviews. If your app can be rotated to landscape and portrait orientations and/or if your app is designed to run on iPad as well, then this method should dynamically consider the table's bounds. (Screen or windwo bounds are often used hiere but that is not save when the table is displayed within a split master view or a popup view on iPads.)
Strictly spoken your MyTableViewCell should not be the data source delegate, simply because it is a view element and view objects are not supposed to manage any business logic within an MVC design pattern. Smarter would be the implementation of some dedicated view controller for your table view cell hat fulfills the protocol and is assigned as the delegate. However, both would work. In the event that it is the only picker view within your sell, then you could easily use your UITableViewController subclass even without tagging the UIPickerView.
An alternative to subclassing a UITableViewCell is to create the UIPickerView within the cellForRowAtIndexPath method of your tableViewController. That is fine for singe-orientation apps. However, you may setup it in a way that it re-arranges its the UIPickerView automatically.
In any case you should overwrite the heightForRowAtIndexPath method of UITableViewController when your table views do not have the same hight any more. If all of them still have the same height, then you can simply set the rowHeight property of your UITableView.
How do I access UITableViewController parameter from within a subclass'ed UITableViewCell?
I have a parameter in the UITableViewController for the font size (i.e. users can change font size). So the layoutSubviews method in my custom subclassed UITableViewCell will need to access the latest font when it needs to re-layout itself (as it label positions will depend on the font).
So my question, from with my custom subclassed UITableViewCell, and specifically within the layoutSubviews method, how do I access the "uiFont" parameter which is an instance variable from the UITableViewController?
There are a few ways to do it: access the UITableViewController via a property on the application delegate (which you can access from anywhere using [UIApplication sharedApplication].delegate), give the cell a reference to the UITableViewController when you create it in tableView:cellForRowAtIndexPath:, or follow the UIResponder chain from the cell view until you find a UITableViewController.
But really, this is probably a poor architecture. You should probably just call reloadData on the table view to have it recreate all the cells, and set the font in tableView:cellForRowAtIndexPath:. Or if the font setting should persist, you could store it in NSUserDefaults and have the cells listen for NSUserDefaultsDidChangeNotification.
Accessing the UITableViewController object from within your cell isn't a good approach in terms of design. What you should be doing is creating an ivar in the table cell itself to store the UIFont object:
#interface CustomCell : UITableViewCell {
UIFont *font;
}
#property (nonatomic, retain) UIFont *font;
And then in your tableView:cellForRowAtIndexPath method, set the font of the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
[cell setFont:uiFont];
...
}
I am having an issue with the resusable table cells. I only ever have 11 cells loading, where nearly all the content in them is downloaded, parsed and displayed correctly.
The problem that I am having is that I am shown a UIImageView square with a background colour unique to that row. And when the device is rotated or the table view is scrolled up or down (thus removing unused cells), the colours change to one of the others.
I am not completely sure how to stop this. Currently
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
is being called, and in this it detects the orientation, creates a Cell Identifier, creates the cell and adds things to the cell.
Any help would be great!
What are you using as a cell identifier? Use something unique, like
NSString *cellIdentifier = someObject.someProperty;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
where someObject.someProperty can be for example item.title
Hm, so this is how dequeuing works. Even though that conceptually there are 11 rows, actually iOS allows you to cheat and just reuse a few of them, so it works a lot more faster.
You just have to remember that if you asked the table view for a dequeued cell, you still have to reconfigure it so it shows correct data.
If your issue is because that your cells actually does internal processing… don’t do that. Move that piece of code elsewhere. ;)
To create tableviewCells you need to use a unique cell Identifier, but all cells in that tableview need the SAME identifier so:
static NSString *cellIdentifier = #"YOURINDETIFIER";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
Now you would setup your cells properties.
If you are using a custom cell, be sure to reconfigure ALL subviews otherwise you will get artefacts from previously used cells.
Can you post what you are doing in cellForRowAtIndexPath ?