I'm trying to reuse a UITableViewController for multiple purposes. My problem is that I'd like to display different buttons and other elements depending on the intention for displaying the list.
I'm currently using multiple cell prototypes to display different info for each item's detail, but I'd also like to be able to swap the controls depending on intention. I've been showing and hiding controls to accomplish this, but looking at the storyboard gets a bit ugly.
I was thinking maybe using a container view... just figured I throw this out there and see if anyone is doing anything similar. I didn't want to created separate list views just to change buttons.
Without more details, it's hard to say whether this will solve your problem particularly. However, I do precisely this in my current project and this is how:
I am going to assume the following. Let me know if they don't hold true and I'll update my answer accordingly.
You're creating a cell prototype for every permutation and combination, including actions. And so you have too many prototype cells.
Your actions are 'buttons' or similar controls on the cell (and not the swipe to reveal edit actions).
Create a custom class for your cell. Add a container for your actions and position it appropriately in story board. Connect the container to an outlet in the cell.
#interface MyCustomCell : UITableViewCell
#property MyCellTypeEnum type;
-(void) configure;
#end
#implementation MyCustomCell
-(void) configure {
switch(type) {
case type1:
// add actions to container
break;
case type2:
// etc.
}
}
#end
And in your TableViewController's cellForRowAtIndexPath do the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// ... dequeue appropriate cell
cell.type = <appropriate type>
[cell configure];
return cell;
}
Hope that helps.
Related
My tableOne contains custom UITableViewCell called CustomCellForTableOne (.h and .m). When user clicks on a cell in tableOne, I want them to segue to tableTwo. So far so easy.
I want the cell in tableOne to become the table header in tableTwo. Is there a simple straightforward way for doing this? Ideally, I want to simple create a CustomCellForTableOne from data that are passed through the segue and assign that cell as the header of my tableTwo.
I don't know how to assign header to a table programmatically (so I could try it)
Would it work? (since I haven't tried it yet anyway, I thought I might ask to save some time) Or must I take some different approach?
It should be possible as cell and header view for table both inherit from UIView. Implement the following in your tableTwo and return your table view cell instance from it. -
- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section
Headers are hard to handle in table views. I would place an extra section in the second table only for that specific cell which has been passed through. This could be the first section of the second table and you could design it as it looks like a header.
Another way is to create a plain view and place it in the header of the second table (this is totally legal). Then you can safely add your specific cell (if it has a view) in that plain view via addSubView: .
I was able to achieve what you're looking to do with the following code in the first view controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// create the new table view
TestTableViewController2 *testView2 = [TestTableViewController2 new];
// get a reference to the cell that they clicked and create a totally new cell, a 'copy' of
// the original that we can pass to the next view
UITableViewCell *clickedCell = [tableView cellForRowAtIndexPath:indexPath];
UITableViewCell *clickedCellCopy = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
clickedCellCopy.textLabel.text = clickedCell.textLabel.text;
clickedCellCopy.detailTextLabel.text = clickedCell.detailTextLabel.text;
// set this new cell as the header cell on the new view
testView2.myHeaderCell = clickedCellCopy;
// push the new view onto the stack
[self.navigationController pushViewController:testView2 animated:YES];
}
Then add the property to your second table view controller:
#property (nonatomic, retain) UITableViewCell *myHeaderCell;
And then in the .m's viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
// set our header cell as the table view's header
self.tableView.tableHeaderView = self.myHeaderCell;
}
That gave me the following result:
https://www.dropbox.com/s/t42lraue1kfolf3/cell%20demo.mov?dl=0
You could create your custom UITableViewCell in a xib file and load it for each view. You are going to have to create it in both view controllers. Depending how complex, you could also do this entirely in code.
I am new to iOS programming. My cell is in a .nib. This cell displays an image which is working fine. I don't want the cell to perform any action or be selected. So, I have the following code :
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.contentView.userInteractionEnabled = YES;
My problem is I have a button which isn't called on touch. I have an action defined in the cell.m file. But nothing happens. I also tried adding the button programmatically, but in vain. I will appreciate any help or pointers as I have tried this for past few hours. I am pretty sure I am doing something silly.
Update:
This is how my cell xib structure looks -
There cell.nib, cell.m and cell.h. There is MainViewController which uses dequeueReusableCellWithIdentifier method to get the cell. This works fine as I am able to see the cell with the correct image. Action method is defined inside cell.m
Here is the xib file
https://www.dropbox.com/s/mzqzi6iz8lkbx2f/SHTableCell.xib
Thanks.
After checking the SHTableCell.xib you shared, it seems you have prevented user interaction on the cell itself so enabling user interaction on it's contentView / subViews will not make a difference.
In your xib, select "Test Cell" and check "User Interaction Enabled"
Also, it seems you haven't specified a re-use identifier to the cell.
It would be better if you specified one so that your -cellForRowAtIndexPath: could properly re-use the cell.
So... if you have something like:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//...
SHTableCell *cell = (SHTableCell*)[tableView dequeueReusableCellWithIdentifier:#"SomeIdentifier"];
//...
}
then, "SomeIdentifier" is what should be in the xib in the first place.
I'm trying to add another view controller inside a UITableView cell. The idea is that you tap the cell, and it expands to show more content--a messaging interface. It's important (I think) that this is controlled by a separate Messaging ViewController.
Expanding the cell and having views inside the cell expand with the proper constraints is actually very straightforward in Storyboards, so I tried to keep everything in storyboards by adding my new VC to the TableViewCell via a Container. That way I'd be able to add constraints on the container view, and pipe the content in from my Messaging VC.
Here's the error:
Illegal Configuration: Container Views cannot be placed in elements that are repeated at runtime.
Any way to get around this issue, or is there a way I can pipe the view from my viewcontroller into this tableviewcell and have it constrain to a configuration that I set in Storyboards? Thank you!
I had the same task and decided it this way:
Step 1. Create subclass MyCell: UITableViewCell.
Step 2. If you use Self-Sizing Cells, in InterfaceBuilder add UIView to MyCell, then add height constraint and constraints to all sides. This view needed for set height of cell.
If not, skip this step and use heightForRowAtIndexPath.
Step 3. In MyCell.h add outlet from view height constraint and controller property:
#interface MyCell: UITableViewCell
#property (weak, nonatomic) MessagingVC *controller;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *viewHeight;
#end
Step 4. In cellForRowAtIndexPath add code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell" forIndexPath:indexPath];
// adjust this for your structure
cell.controller = [[UIStoryboard storyboardWithName:#"MessagingVC" bundle:nil] instantiateInitialViewController];
[self addChildViewController:cell.controller];
[cell.contentView addSubview:cell.controller.view];
[cell.controller didMoveToParentViewController:self];
// if you use Self-Sizing Cells
cell.viewHeight.constant = 200; // set your constant or calculate it
return cell;
}
Step 5. Add didEndDisplayingCell method:
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if ([cell isKindOfClass:[MessagingVC class]])
[((MyCell*)cell).controller removeFromParentViewController];
}
Make your UITableViewController content as Static.
You can just drag Container View into UITableVeiw in the storyboard. For example, you can drag it before prototype cell, and you will see your container's view controller before your prototype cells. By the way you can drag any UI element to table view. I'm not sure, how to deal with autolayout in the combination table view + container view, maybe you need to manually calculate / set constraints at the runtime. Will update my answer when I'll find the right solution about autolayout.
Putting container views in table view cells is way too heavy. Table view cells should be lightweight so the user can scroll through them quickly. It's not necessary to put the entire view controller in each cell. The cell should just represent some of the data for that row.
When the user touches the cell you just use a normal segue to the messaging view controller. Its presentation will be automatic. Then create and specify an animationController to handle the transition to make it appear as though the message composition view was contained within the table view cell.
Edit: See my answer for a functioning app that somehow implemented what I was trying to do.
I've checked around for this and followed every available tutorial - this all seems pretty straightforward but my Storyboard & inspector simply will now allow me to do the following:
-- Add buttons to custom UITableViewButtons (using custom class 'Song Cell')
Every time I try to do so, it puts the button on a view which is above the table view. I've tried setting the cells to dynamic, static, basic, and every other toggle switch I could find.
I think its because I have a slightly awkward settup in terms of views, so I tried to set my TableView to a custom class as well. However, its not showing up in Storyboard's Class Inspector. Here is what I did, to set this Table View to a custom class, so no avail:
-- Create custom class inheriting from UITableViewController, called SongTableViewController
-- Set, in Storyboard, a table view controller's class to SongTableViewController
See this hierarchy:
// edit - Apparently I do not have 10 rep to post pics, so I'll just draw it myself:
▼ Voting View Controller - Current Songs
▼ View
▼ Table View // This is where I would like the custom class, SongTableViewController
> Song Cell
> Song Cell // These cells are where I would like to add the custom buttons
> Song Cell
> Constraints
> Label - 00:00
> Label - Voting will reset in:
Navigation Item - Current Songs
First Responder
Exit
When I select the Table View and go to the inspector to change its class, there is no option except for UITableView. Trying to hardcode this and hitting 'return' also does nothing.
Is my inability to add buttons to these cells due to the structure of
my views? Or something else?
Maybe you should create a xib with your custom cell. For exemple, the class "CustomeCell" with the xib "CustomeCell.xib".
You put some objects on your cell via xib file and in your class with your UITableView, do something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
// configure cell
return cell;
}
Don't forget to link you datasource and delegate for your TableView in the xib file AND add the delegates in your UITableView class :)
Storyboards are usefull but sometimes, the good way is to use xib files :)
EDIT: You can read this tutorial, it's a very good example how to manage custom cell / tableview with xib files: http://www.appcoda.com/customize-table-view-cells-for-uitableview/
Hope it help you :)
You probably don't want to change the class of the table view. It sounds like what you really want to do is change the class of one (or more) of the cell prototypes. In the storyboard select one of the cells and change its class to your song cell class.
Here is a description of what was going on, and an example of how to impliment this:
You have one UIViewController subclass, and add
the table view to it by dragging and dropping in the storyboard.
You then have a bit of extra work to do to fill the gap between a
table view controller and a plain view controller - declare that you
conform to the datasource and delegate protocols, create an outlet for
the table view, connect the outlet to the table view and connect the
table view's delegate and datasource outlets to your VC.
Implement the three datasource methods (number of sections, number of
rows and cellForRow...) and you're done.
Link to Prototype
So far I´ve only needed to implement prototype cells with pre-defined designs (normal, subtitle, etc.) and it hasn´t been a problem.
Now I need to implement prototype cells which contain some controls like a segmented switch, a switch or any other. The problem is that I haven´t been able to find out how the actions triggered are implemented and how are they related to the control. Also I heven´t found any example of how different prototype cells inside a single UITableViewController are implemented.
I know it´s kind of a generic question, but I´d appreciate some pointers here. Maybe someone knows about some documentation, tutorial, etc. Well, any help would do,
Thnaks in advance.
It took me also a while to understand how to use the prototype cells. If you want to access the user interface elements inside a prototype cell, you have to create a subclass of UITableViewCell and assign it to the prototype cell in Interface Builder.
Then you have to define the IBOutlet properties manually e.g.:
#interface OptionSwitchCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UISwitch* switchControl;
#end
After that, you can connect the interface elements through control-dragging from the element to the property definition in the assistant view.
The IBActions instead can be defined inside the owning View Controller. You can control-drag from a interface element to the View Controller header file and create an action. Inside the action implementation you will likely want to know which cell was been triggering the action. I do it like this:
#implementation SomeTableViewController
- (IBAction)toggleActivity:(id)sender {
OptionSwitchCell* cell = (OptionSwitchCell *)[sender superview].superview;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
...
}
#end
Another solution for finding the corresponding cell and index path (by jrturton):
- (IBAction)toggleActivity:(id)sender {
CGPoint hitPoint = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *hitIndex = [self.tableView indexPathForRowAtPoint:hitPoint];
OptionSwitchCell* cell = (OptionSwitchCell *)[self.tableView cellForRowAtIndexPath:hitIndex];
...
}
Although this is a bit quirky, I haven't found a better solution so far. Hope that helps.