I have a tableview with a few items. I want to get a new view when I click on one of the cells. However, I want the view to be different depending on which cell I clicked.
For example: I have a tableview of buildings. These buildings can be Airports, factories and houses.
When I click on a cell that shows an airport, I want the next view to be different than when I click on a cell that shows a factory.
How do I do this?
The problem can be easily solved by giving each cell a type. If you're implementing a custom cell class add it as an enumerated type (improves readability)
// cell header file
typedef enum {
CellTypeAirports,
CellTypeFactories,
CellTypeHouses
} CellType;
#interface CustomTableCell
// ...
#property(assign) CellType type;
#end
Set the type when you're instantiating (dequeueing) the cell and then in your tableView:didSelectRowAtIndexPath: method check the cell's type
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// ...
if ([cell type] == CellTypeAirports) {
// push airports vc
} else if ([cell type] == CellTypeFactories) {
// factories
}
// and so on
}
If you're using standard UITableViewCells then you can utilize their tag property to store the type.
Related
I have a layout in which I will have 2 UITableViews with custom cells. The second UITableView must be inside the first.
My question is: how to delegate second UITableView?
Can I delegate both to my ViewController? In that case it will use the same methods and I have to find out which UITableView is managed right now.
Or I have to delegate it inside custom UITableViewCell of the first UITableView?
Any recommendations are appreciated.
EDIT: I don't know how to implement solutions here, because I have Storyboard. Inside my current UIViewController I set delegate and dataSource of the first UITableView to my View Controller.
My problem is that I don't know how to set the same properties of the second Table View (which will be inside UITableViewCell). I can not set them to UITableViewCell (IB does not allow that).
Where and how to set then in the IB?
A far better solution would be to abstract the DataSource and Delegate implementations away from your view controller so that they can be personalised per tableview as required (please note that the code is taken from the objc.io article Lighter View Controllers.
E.g.
#implementation ArrayDataSource
- (id)itemAtIndexPath:(NSIndexPath*)indexPath {
return items[(NSUInteger)indexPath.row];
}
- (NSInteger)tableView:(UITableView*)tableView
numberOfRowsInSection:(NSInteger)section {
return items.count;
}
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
forIndexPath:indexPath];
id item = [self itemAtIndexPath:indexPath];
configureCellBlock(cell,item);
return cell;
}
#end
Then you could utilise it as follows:
void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) {
cell.label.text = photo.name;
};
photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos
cellIdentifier:PhotoCellIdentifier
configureCellBlock:configureCell];
self.tableView.dataSource = photosArrayDataSource;
The same process could be followed with the UITableViewDelegate implementations to provide you with a very clean, separated and de-coupled code base. Your requirement for two tableviews will then be intrinsically easier to implement.
My answer is
For identifying two table view data source and delegate method is,better to set tag for the table views.
Set this below coding in your tableview delegates method.
if(tableView.tag==0)
{
}
else
{
}
Also you can vary this by assigning different name to these table view.
if(tableView==FirstTableView)
{
}
else
{
}
You just check table condition for every delegate method
Use this code to register custom cell.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableView == self.yourFirstTable)
{
CustomCell *cell=[tableView dequeueReusableCellWithIdentifier:#"cellModifier"];
// your code
}
else
{
// second table cell code
}
return cell;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(tableView == self.yourFirstTable)
{
// first tableView number of row return
}
else
{
// second table number of row return
}
}
And create prototype cell in TableView
And Set CellReusableId like this way
I would like to call a method when a UITableViewCell is selected/tapped. I could do it easily with a static table view, but it requires a UITableViewController which is not good for me in this case, therefore I'm using a normal vc.
I have 10 specified methods like this:
- (void) methodOne {
NSLog(#"Do something");
}
- (void) methodTwo {
NSLog(#"Do something");
}
....
And I would like to call the methodOne when the first cell was tapped, call the methodTwo when the second cell was tapped and so on..
As a first step I set the numberOfRowsInSection to return 10 cells, but have no idea how could I connect the selected cells with the methods. Is there any quick way to do it? It would be a dirty solution to create 10 custom cells and set the every method manually for the custom cells, and there is no free place for it.
You can create an array of NSStrings with method names in the order they should be called from their corresponding UITableViewCells.
NSArray *selStringsArr = #[#"firstMethod", #"secondMethod", #"thirdMethod];
Then create a selector in tableView:didSelectRowAtIndexPath: from the strings array and call it using performSelector:.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *selString = selStringsArr[indexPath.row];
SEL selector = NSSelectorFromString(selString);
if ([self respondsToSelector:#selector(selector)]) {
[self performSelector:#selector(selector)];
}
}
Of course, there are some limitations to using performSelector: which you can read here.
You can use this method for whenever any cell is tapped on the table view
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger selectedRow = indexPath.row; //this is the number row that was selected
switch (selectedRow)
{
case 0:
[self methodOne];
break;
default:
break;
}
}
Use selectedRow to identify which row number was selected. If the first row was selected, selectedRow will be 0.
Don't forget to set the table view's delegate to your view controller. The view controller also has to conform to the UITableViewDelegate protocol.
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
As long as the table view has a data source and a delegate, it doesn't matter what kind of view controller it is on. All a UITableViewController really is is a UIViewController that already has a table view on it and is that table view's delegate and data source.
I'm presenting a lot of data in format of a table with multiple columns. Almost each column has a button (up to 4 in total) and each row is a UITableViewCell.
How could I detect that the buttons were touched and where should I handle touch events of the buttons? I'm certain, it shouldn't be a didSelectRowAtIndexPath method though.
As soon as, I could detect which button was pressed, I would fetch the data in that particular row and manipulate it. So, I need to know the indexPath of the row, as well as what button was pressed on it.
You can subclass UIButton with two properties row and column and implement the logic below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"
forIndexPath:indexPath];
MyButton *button1 = (MyButton *)[cell viewWithTag:1];
button1.row = indexPath.row;
button1.column = 1; // view tag
[button1 addTarget:self
action:#selector(clickAction:)
forControlEvents:UIControlEventTouchUpInside];
// button2,button3...
return cell;
}
-(void)clickAction:(MyButton *)sender {
// now you can known which button
NSLog(#"%ld %ld", (long)sender.row, (long)sender.column);
}
Generalized undetailed answer:
Create UITableviewcell subclass, link cell ui elements to this class.
Add method configureWithModel:(Model*)model; //Model being the information you want the cell to represent
Manipulate that information or
If you need to manipulate the screen or other objects. You need to give the table view cell subclass a reference to the other objects when the cell is created. (in code or in storyboard or in nib).
how to handle button presses in ios 7: Button in UITableViewCell not responding under ios 7 (set table cell selection to none)
how to link a button: http://oleb.net/blog/2011/06/creating-outlets-and-actions-via-drag-and-drop-in-xcode-4/
If those four views are UIButton then you will receive the tap events on each button or if they are not UIButton then you should add UITapGestureReconiser on each of this views
Several options here. But I would do the following:
Adopt a Delegate Protocol in your custom cell class (see here: How to declare events and delegates in Objective-C?) . This will handle the target selector for the buttons. Pass this message back to your view controller with the sender. To detect which cell it was in do the following:
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
CGRect senderFrame = CGRectMake(buttonPosition.x, buttonPosition.y, sender.frame.size.width, sender.frame.size.height);
From here you can decide what the do. Use the buttons .x coordinate to determine which button it was or specify a different tag for each button in cellForRowAtIndexPath:. Or if you want to grab the index path of the cell you can do:
NSArray *indexPaths = [YOUR_TABLE_VIEW indexPathsForRowsInRect:senderFrame];
NSIndexPath *currentIndexPath = [indexPaths lastObject];
Because each button has a different action, the only thing you need to get at runtime is the indexPath of the button. That can be done by looking at the button's superviews until a cell is found.
- (IBAction)action1:(UIButton *)button
{
UITableViewCell *cell = [self cellContainingView:button];
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
MyDataModel *object = self.objects[indexPath.row];
// perform action1 on the data model object
// Now that the data model behind indexPath.row was done, reload the cell
[self.tableView reloadRowsAtIndexPaths:#[indexPath]
withRowAnimation: UITableViewRowAnimationAutomatic];
}
- (id)cellContainingView:(UIView *)view
{
if (view == nil)
return nil;
if ([view isKindOfClass:[UITableViewCell class]])
return view;
return [self cellContainingView:view.superview];
}
There: no delegates, no tags, and the action doesn't care about the internals of the cell.
You will still want to subclass UITableViewCell with the four buttons (call them button1, button2, button3, and button4 if you don't have better names). You can make all the connection is Interface Builder. This will only be needed for populating object data into the cell during -tableView:cellForRowAtIndexPath:.
Ideally, you should create a custom cell by subclassing UITableViewCell and implement the actions for each of these buttons in that cell. If your view controller needs to know about these actions, you can define a protocol, MyCustomCellDelegate or similar, and have your view controller conformed to that protocol. Then MyCustomCell will be able to send messages to the view controller when user interacts with its buttons or other controls.
As in the example code below, you can create a cell in storyboard or nib and hook one of the button's action to firstButtonAction method of CustomTableCell class.
Also, you need to set your view controller as delegate property of CustomTableCell object created and implement the method buttonActionAtIndex: of CustomTableCellDelegate in your view controller class. Use controlIndexInCell param passed to this method to determine which button might have generated the action.
#protocol CustomTableCellDelegate <NSObject>
- (void) buttonActionAtIndex:(NSInteger)controlIndexInCell
#end
In CustomTableCell.h class
#interface CustomTableCell: UITableViewCell
#property (nonatomic, weak) id <CustomTableCellDelegate> delegate
- (IBAction) firstButtonAction:(id)sender
#end
In CustomTableCell.m class
#implementation CustomTableCell
#synthesize delegate
- (IBAction) firstButtonAction:(id)sender{
if ([delegate respondToSelector:#selector(buttonActionAtIndex:)])
[delegate buttonActionAtIndex:0];
}
#end
This is a personal preference on how I like to handle situations like these, but I would first subclass UITableViewCell because your table cells do not look like a default iOS UITableViewCell. Basically you have a custom set up, so you need a custom class.
From there you should set up your 4 IBActions in your header file
- (IBAction)touchFirstButton;
- (IBAction)touchSecondButton;
- (IBAction)touchThirdButton;
- (IBAction)touchFourthButton;
You do not need to pass a sender in these actions, because you will not be using that object in these methods. They are being created to forward the call.
After that set up a protocol for your UITableViewSubClass
#protocol UITableViewSubClassDelegate;
Remember to put that outside and before the #interface declaration
Give your sell a delegate property
#property (nonatomic, strong) id<UITableViewSubClassDelegate> delegate;
and finally define your actual protocol, you will need to set up 4 methods, 1 for each button and take your subclass as a parameter
#protocol UITableViewSubClassDelegate <NSObject>
- (void)forwardedFirstButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedSecondButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedThirdButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedFourthButtonWithCell:(UITableViewSubClass*)cell;
#end
This will be placed outside of the #interface #end section at the bottom
After that create a configureWithModel: method in your #interface and #implementation as well as a property for your model
#interface:
#property (nonatomic, strong) Model *model;
- (void)configureWithModal:(Model*)model;
#implementation:
- (void)configureWithModal:(Model*)model {
self.model = model;
// custom UI set up
}
From here you should configure your action methods in your #implementation file to call the delegate methods, i'm only showing the first one, but you would do this with all of the IBActions
- (void)configureWithModal:(Model*)model {
[self.delegate forwardFirstButtonWithCell:self];
}
From here your custom cell set up is done and we need to go back to the UIViewController that is displaying the UITableView. First go into the header file of the view controller, and import your custom UITableViewCellSubClass and then setup the class to implement this protocol.
It should look something like this
#interface MYViewController : UIViewController <UITableViewSubClassDelegate>
from there you should go into your cellForRowAtIndexPath: method and configure your custom UITableViewCell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCellSubClass *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
cell.delegate = self;
Model *cellModel = self.tableData[indexPath.row];
[cell configureWithModel:cellModel];
return cell;
}
Now go into your cell class and copy paste all of the protocol methods into your viewController class. I will display one as an example.
In your UIViewController:
- (void)forwardedFirstButtonWithCell:(UITableViewSubClass*)cell {
Model *cellModel = cell.model;
// do stuff with model from outside of the cell
}
do that for all methods and you should be good.
Remember to have all your #imports in so there's no forward declarations and remember to link up the IBActions to your storyboard or xib files. If you want a custom xib for your table cell you will have to check if the cell is nil and then allocate a new one, but if you are using prototype cells then this should be sufficient.
For simplicity sakes i put forwardFirstButtonWithCell: but i would encourage making the name something that describes what it's doing such as, displayPopOverToEnterData or something similar. From there you could even change the parameters of the delegate protocol methods to take models instead so instead of
- (void) displayPopOverToEnterDataWithCell:(UITableViewSubClass*)cell;
make it
- (void) displayPopOverToEnterDataWithModel:(Model*)model;
but, i don't know what type of information you need to access from the cell. So update these methods as you see fit.
I am using a static cell layout in a UITableView. During the workflow I need to address the attributes of a specific cell. All cells have an identifier but i did not found a way to actually address the cell using the identifier.
Thanks,
em
The reason why cellForRowAtIndexPath: is not reliable is probably this (from the documentation):
An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range.
If you ask for a cell that is not visible on screen, it may have been purged from the table view.
Since you mention that you use a static cell layout in your tableview (I assume you don't rely on cell reuse), you could consider keeping the cells as private properties:
In the private interface:
#interface MyViewController ()
#property (nonatomic, readonly) MyTableViewCellClass *myStaticCell;
#end
In the implementation:
#implementation MyViewController {
MyTableViewCellClass* _myStaticCell;
}
- (MyTableViewCellClass*) myStaticCell {
if (!_myStaticCell) {
// Initialize _myStaticCell
}
return _myStaticCell
}
You can then call this lazy loaded property when tableView:cellForItemAtIndexPath:is called and whenever you need to modify it.
Note that this approach is only recommended if you have a tableview with static content and don't rely on cell reuse.
Here indexPathSelected is the selected specific cell
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:indexPathSelected inSection:0]];
indexPathSelected = indexPath.row;
}
I have an app that lists some data in a tableview, in cells. I want the user to be able to select a table view cell, any one, and have the app cycle through the 5 lower cells. Here is what I have so far:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
// Navigation logic may go here. Create and push another view controller.
[self fetchCellsToProcess:indexPath];
}
Here is the fetchCellsToProcess: method:
-(void)fetchCellsToProcess:(NSIndexPath*)indexPath{
for (int cellsToProcess = 0; cellsToProcess < 5; cellsToProcess++) {
//process each cell
//...
}
}
I need use the indexPath to get its indexPath.row. Then add 5 to that indexPath.row and only process the tweets between indexPath.row passed in and indexPath.row+5. What programming logic should I use to cycle through cells x -> x+5?
You should not be using cellForRowAtIndexPath: here: that's part of the presentation logic, while you are working on the model-level logic here.
Look at your cellForRowAtIndexPath: method, and see from where does the text of the cell's labels come. Usually it is an NSArray or some other collection. Your didSelectRowAtIndexPath: method should go directly to that same collection, and grab the info from there.
There is no direct approach to achieve this as dasblinkenlight mentioned
but there is a work around which works for me.
Please follow below approach:
Create your custom button within your custom cell
Create your custom cell class and put button(change type to custom ) do necessary connection custom cell class
create and connect action method named "actionSelectedCell" as below from that button in class you are implementing this logic.
And cellForRowAtIndexPath cell.customButton.text = data from your array or dictionary
- (IBAction)actionSelectedCell:(UIButton *)sender {
// below code for getting selected cell row and its section
UIView *view = sender.superview;
while (view && ![view isKindOfClass:[UITableViewCell self]]) view = view.superview;
UITableViewCell *cell = (UITableViewCell *)view;
indexPathForSelectedCell = [self.tableViewListViewVC indexPathForCell:cell];
NSLog(#"cell is in section %d, row %d", indexPathForSelectedCell.section, indexPathForSelectedCell.row);// so now you are getting indexpath for selected cell
// now you can create simple loop logic to get required data and do whatever you like post it to something or store it for any other use
NSString *strFifthCellContent = [NSString stringWithFormat:#"%#",[dataArray ObjectAtIndex:indexPathForSelectedCell.row+5]]; // in this way you can get data from any cell
}