I have a Master-Detail tableview and would like take the user to a different Detail VC depending on properties of the object being displayed.
For example, if an actor is in a movie, I'd like to go to a movie View Controller, but if the actor is in a TV show, a view controller optimized for a TV show.
I have wired the detail VCs to the view controller, not the table view so that using didSelectRowAtIndexPath, I can send the user to different detail VCs using logic and performSegue.
However, the information needed to implement the logic is in the data object in cellforrowatindexpath. So my question is how can pass this info to didSelectRow or alternatively place the logic for the segue in cellforrowatindexpath.
Thanks for any suggestions.
Here is my current code:
- (void)tableView: (UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath {
UITableViewCell *theCellClicked = [self.tableView cellForRowAtIndexPath:indexPath];
NSString *segueName = nil;
if ([_credit.type isEqual: #"movie"]) {
segueName = #"goToMovie";
}
else if ([_credit.type isEqual: #"television"]) {
segueName = #"goToTVShow";
}
[self performSegueWithIdentifier: segueName sender: self];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Credit *credit = [self.fetchedResultsController objectAtIndexPath:indexPath];
}
cell.textLabel.text = credit.name;
return cell;
}
Your fetched results controller will still be in scope inside didSelectRowAtIndexPath. So you can:
Credit *credit = [self.fetchedResultsController objectAtIndexPath:indexPath];
if ([credit.type isEqual: #"movie"]) {
segueName = #"goToMovie";
}
else if ([credit.type isEqual: #"television"]) {
segueName = #"goToTVShow";
}
Related
I have an array of user displayed in a table view when pushing the send button the cell dosen't selected right object. It can be quit random:). How do i make send the object displayed on the selected cell?
This is how i send my message
- (void)sendMessage:(id)sender {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
PFObject *object = [self.objects objectAtIndex:indexPath.row];
self.SendToUsername = [object objectForKey:#"username"];
self.SendToName = [object objectForKey:#"name"];
}
And this is my cell, where the send button is located.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
static NSString *simpleTableIdentifier = #"LampCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
UIButton *sendbutton = (UIButton*) [cell viewWithTag:105];
[sendbutton addTarget:self action:#selector(sendMessage:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
It is quite easy. Tableview itself provide method.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
variable=[array objectAtIndex:indexPath.row];
}
In "Variable" you have value which is selected. If your array is accessible in whole controller then you can save "indexPath.row" and use on click event of send button to fetch selected record.
tableView indexPathForSelectedCell will not give you the index path you are expecting in the action method of a button that is in a cell. The cell wasn't selected - you touched the button.
To get the index path of the row for that button, there are a couple of different methods.
My preferred method is to traverse the view hierarchy to find the cell that contains the button, and use that to get the index path. See this question for more info:
Button in custom cell in dynamic table: how to know which row in action method?
My answer from this question is as follows. You could add these two methods to a category on UITableViewController, or you could just add them to your view controller, if you like.
- (NSIndexPath *)indexPathForCellSubview:(UIView *)subview
{
if (subview) {
UITableViewCell *cell = [self tableViewCellForCellSubview:subview];
return [self.tableView indexPathForCell:cell];
}
return nil;
}
- (UITableViewCell *)tableViewCellForCellSubview:(UIView *)subview
{
if (subview) {
UIView *superView = subview.superview;
while (true) {
if (superView) {
if ([superView isKindOfClass:[UITableViewCell class]]) {
return (UITableViewCell *)superView;
}
superView = [superView superview];
} else {
return nil;
}
}
} else {
return nil;
}
}
You could then get the index path in your button action method like so:
NSIndexPath *indexPath = [self indexPathForCellSubview:sender];
You don't need to set the tag for buttons to get the indexpath. You can simply get it using this piece of code:
- (void)sendMessage:(id)sender {
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *clickedButtonIndexPath = [self.tableView indexPathForCell:clickedCell];
PFObject *object = [self.objects objectAtIndex:indexPath.row];
self.SendToUsername = [object objectForKey:#"username"];
self.SendToName = [object objectForKey:#"name"];
}
I am new to iOS development, i've been following apple intro to iOS and implemented the to-do list app.
I have tableview that shows the list of the current to-do list, and another view controller that allows the user to add a new item in a textfield then add to the list.
I have a little + on top of my tableview that performs a segue action to the add to-do item view.
I want to be able to perform the same action if the user taps on an empty cell
I tried to put this code in and it works but I want to put it in the right position so it only triggers when the user hits empty space in the table:
[self performSegueWithIdentifier:#"showAddItem" sender:self];
Thank you.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"ListPrototypeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
// Configure the cell...
ABCTodoItem *toDoItem = [self.toDoItems objectAtIndex:indexPath.row];
cell.textLabel.text = toDoItem.itemName;
if (toDoItem.completed) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
In my opinion this solution is the cleanest:
Add a tap gesture to your UITableView's backgroundView. If the backgroundView is nil, add a transparent UIView, and add a gesture to it.
Now hook up your action to this gesture recognizer.
I can see at least two solutions here.
Solution 1. Adding en empty cell by modifying the data source
After you have populated your toDoItems add this:
[self.toDoItems addObject:#""];
Notice that toDoItems should be type of NSMutableArray.
Now let's handle creating cells:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"ListPrototypeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
NSObject *objItem = [self.toDoItems objectAtIndex:indexPath.row];
if([objItem isKindOfClass: [ABCTodoItem class]]) {
ABCTodoItem *toDoItem = (ABCTodoItem*) objItem;
cell.textLabel.text = toDoItem.itemName;
}
else if([objItem isKindOfClass: [NSString class]]) {
NSString *item = (NSString*) objItem;
cell.textLabel.text = item;
}
return cell;
}
Touch action:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSObject *objItem = [self.toDoItems objectAtIndex:indexPath.row];
if([objItem isKindOfClass: [ABCTodoItem class]]) {
ABCTodoItem *toDoItem = (ABCTodoItem*) objItem;
// Open your toDoItem
}
else if([objItem isKindOfClass: [NSString class]]) {
self performSegueWithIdentifier:#"showAddItem" sender:self];
}
}
Solution 2. Adding an empty cell without modifying the data source
This makes it possible to create one extra cell:
- (NSInteger)numberOfRowsInSection:(NSInteger)section {
return [self.toDoItems count] + 1;
}
Now again let's handle creating cells:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"ListPrototypeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if([indexPath.row < [self.toDoItems count]) {
NSObject *objItem = [self.toDoItems objectAtIndex:indexPath.row];
if([objItem isKindOfClass: [ABCTodoItem class]]) {
ABCTodoItem *toDoItem = (ABCTodoItem*) objItem;
cell.textLabel.text = toDoItem.itemName;
}
}
else {
cell.textLabel.text = #"";
}
return cell;
}
And then handling the touch action:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if([indexPath.row < [self.toDoItems count]) {
NSObject *objItem = [self.toDoItems objectAtIndex:indexPath.row];
if([objItem isKindOfClass: [ABCTodoItem class]]) {
ABCTodoItem *toDoItem = (ABCTodoItem*) objItem;
// Open your toDoItem
}
}
else {
self performSegueWithIdentifier:#"showAddItem" sender:self];
}
}
I hope I have understood your question correctly; correct me if I am wrong. You have a to-do List populated in a TableViewController which has some tasks and one (or more) empty cells. When you click on these cells, you would want to call [self performSegueWithIdentifier:#"showAddItem" sender:self]; a method that presents a ViewController that allows you to add more items into the list.
Once you have populated your TableViewController in such a way that you have an empty cell, you can always get the NSIndexPath of the empty cell.
When you tap on a cell, a method will be called, called: (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath. As you can see, you will get the index information of the tapped cell. There, you can add the code like so:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Add your logic for the rest of the items
//If you have 7 items in the to-do list, you would have 8 cell,
//the eight one being empty. If indexPath.row is equal to 8 then,
//it says that it MUST be the last item, that is empty
if(indexPath.row == self.itemsInTheList.count) {
[self performSegueWithIdentifier:#"showAddItem" sender:self];
}
}
Alternatively, you can set the tag property of the cell; and that's the story for a different day.
I am trying to call selectRowAtIndexPath on a UITableView that is a subview to the UIViewController I am calling it from.
I have set it up so that when you select a cell it goes grey and thats fine, however I am loading different data sets in and out of the UITableView and when ever a selection is made I am sending that selected NSIndexPath back to the UIViewController. Then when the view is next loaded with the correct data set for the NSIndexPath I call this method from my UIViewController.
if (codeIndexPath != nil) {
[filterViewController.tableView selectRowAtIndexPath:codeIndexPath animated:NO scrollPosition:UITableViewScrollPositionMiddle];
}
Then in the UITableView class my cellForRowAtIndexPath and didSelectRowAtIndexPath look like this.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
NSString *projectDescriptionString = [currentFilterMutableArray objectAtIndex:indexPath.row];
cell.textLabel.text = projectDescriptionString;
if (indexPath == currentlySelectedIndex) {
cell.highlighted = YES;
} else if (indexPath == currentlySelectedIndex) {
cell.highlighted = NO;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleGray;
// send this data back in the delegate so you can use it to show where the tick is again if you need too.
currentlySelectedIndex = indexPath;
[[self delegate] updateInstallTableWithFilter:[currentFilterMutableArray objectAtIndex:indexPath.row] FilterType:filterType InstallIndex:indexPath];
}
When It loads on the screen the correct cell will highlight for a second then go back to white.
Update
New if statment inside cellForRowAtIndexPath
if ([indexPath isEqual:currentlySelectedIndex]) {
cell.highlighted = YES;
} else if (![indexPath isEqual:currentlySelectedIndex]) {
cell.highlighted = NO;
}
I am still receiving the same error.
UITableViewController has a property called clearsSelectionOnViewWillAppear. From the doc:
When the table view is about to appear the first time it’s loaded, the
table-view controller reloads the table view’s data. It also clears
its selection (with or without animation, depending on the request)
every time the table view is displayed. The UITableViewController
class implements this in the superclass method viewWillAppear:. You
can disable this behavior by changing the value in the
clearsSelectionOnViewWillAppear property.
So in that table view controller subclass, in viewDidLoad...
- (void)viewDidLoad {
[super viewDidLoad];
self.clearsSelectionOnViewWillAppear = NO;
}
I'm seeking how create several cells to go to different ViewControllers.
For my TableView, I'm using a subclass of UITableViewController.
And when I choose 2 in the following method, I just see 2 identical cells which are doing exactly the same thing. I'm not interested by this. I don't even know their IndexPath in order to change their title.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return 2;
}
And When I try to put another UITableViewCell in my TableView, it doesn't appear on iOS simulator, even with the same option (same subclass) than my first UITableViewCell which I can see.
Thanks for your help.
Edit : Here is my new code to create 2 cells but doesn't work :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell2";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[customCell alloc] init];
}
static NSString *CellIdentifier1 = #"Cell1";
UITableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
if (cell1 == nil) {
cell1 = [[customCell alloc] init];
}
// Configure the cell...
return cell;
}
You define your cells in tableView:cellForRowAtIndexPath:, so you should provide an implementation for that method.
tableView:numberOfRowsInSection: only returns the number of cells in the table.
If you need more help, please provide your implementation for tableView:cellForRowAtIndexPath:. This is how a typical implementation looks like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
... customize your cell ...
}
EDIT:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell2";
static NSString *CellIdentifier1 = #"Cell1";
if(indexPath.row == 0 ) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[customCell alloc] init];
}
} else {
UITableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
if (cell1 == nil) {
cell1 = [[customCell alloc] init];
}
}
return cell;
}
This method gets called when a cell has been selected. You can decide what you wanna do according to the selected row
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0)
[self goToFirstViewController];
else
if(indexPath.row == 1)
[self goToSecondViewController];
}
Use the following:
- (NSInteger) tableView: (UITableView*) tableView numberOfRowsInSection: (NSInteger) section
This delegate method returns the number of rows you want in that particular section. So if you want more than 2 rows, or you want the number of rows to be dynamic, you can create a NSArray in the AppDelegate or in the init method of the viewController class, and return the number in the numberOfRowsInSection method like
return [delegate numberOfNames];
In my example above, I created an array in my AppDelegate and also a method to return the number of objects I have in that array so that I can create the number of rows for my table.
- (UITableViewCell*) tableView: (UITableView*) tableView cellForRowAtIndexPath: (NSIndexPath*) indexPath
This delegate method will show what you want to display in each cell. Therefore, following on from my array created in my AppDelegate, I first create the cell, then I will set the text I want to display on the cell with a method I created in my AppDelegate that will return a NSString while taking in a NSInteger so that I can loop through my array and display the text accordingly.
static NSString* MyIdentifier = #"Default";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if( cell == nil )
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
cell.textLabel.text = [delegate nameAtIndex:indexPath.row];
}
nameAtIndex is the name of the method I created in my AppDelegate that will return the NSString object at the specific index (ie. the row number) from the NSArray I created to store all the items of my table.
When the user clicks on any of the rows in the table created, this delegate method will be called
- (void) tableView: (UITableView*) tableView didSelectRowAtIndexPath: (NSIndexPath*) indexPath
And in here, I will check if the text displayed matches any of the items in my array from the AppDelegate that stores the items in the table, and create the view that is necessary.
UIViewController* viewController = nil ;
NSString* nameInArray = [delegate nameAtIndex:indexPath.row] ;
if( [nameInArray isEqualToString:#"firstName"] )
{
viewController = [[FirstViewController alloc] init];
}
else if( [nameInArray isEqualToString:#"secondName"] )
{
viewController = [[SecondViewController alloc] init];
}
else if( [nameInArray isEqualToString:#"thirdName"] )
{
viewController = [[ThirdViewController alloc] init];
}
So with these 3 delegate methods, you will be able to create the table using a NSArray created, and be able to redirect the user to a viewController according to which option in the table he chooses. You will not have to keep editing the delegate methods if you choose to add more rows to the table as well since you are returning the count of the array when setting up the table.
The array and methods to get the data of the array can be created in the viewController as well, not necessarily in the AppDelegate, in case you were wondering.
The methods are as follows:
-(NSInteger) numberOfNames
{
return [myArray count];
}
-(NSString*) nameAtIndex: (NSInteger) index
{
return [myArray objectAtIndex:index] ;
}
Hope this helps! :)
I am having issues with my tableView not firing the didSelectRowAtIndexPath method. I have implemented the delegates as such:
#interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate,UIScrollViewDelegate>
And in my storyboard the tableView's data source and delegate are both pointed at the base View Controller. I have User Interactions enabled as well as Selection set to Single Selection, and it is not the TapGesture problem since my tap gestures are not bound to the view and I have checked and they do not fire.
This is the code for setting up the table:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return menuArray.count;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"menuCell"];
NSDictionary *menuItem = [menuArray objectAtIndex:indexPath.row];
cell.textLabel.text = menuItem[#"Title"];
cell.detailTextLabel.text = menuItem[#"Subtitle"];
return cell;
}
-(void)showMenu{
[UIView animateWithDuration:.25 animations:^{
[content setFrame:CGRectMake(menuTable.frame.size.width, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}];
}
-(void)hideMenu{
[UIView animateWithDuration:.25 animations:^{
[content setFrame:CGRectMake(0, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}];
}
-(IBAction)showMenuDown:(id)sender {
if(content.frame.origin.x == 0)
[self showMenu];
else
[self hideMenu];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//whatever
}
The table is initially out of view on the storyboard (origin.x is set to -150), then when the user clicks on a button in the navigationBar, the view slides over to reveal it, which is what might be causing the problem I think.
Is there anything wrong with my code or implementation that would be causing this to not work?
If you already see your table populated with values from your dictionary then you can rule out data source and delegate as being the problem. i.e. your storyboard connections are working.
Your code looks fine to me. the only difference I see is I usually define my table like this. Try this and see if it helps.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//NSLog(#"Inside cellForRowAtIndexPath");
static NSString *CellIdentifier = #"Cell";
// Try to retrieve from the table view a now-unused cell with the given identifier.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// If no cell is available, create a new one using the given identifier.
if (cell == nil)
{
// Use the default cell style.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//Your code here
// ....
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"menuCell"];
This will return nil in case there was never a cell created.
so checking if cell is nil is mandatory and if so, you need to create a cell.
static NSString *CellIdentifier = #"menuCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
as you are using storyboard you can alternatively use
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
for prototype cells. Make sure you use the same identifier in the storyboard and that you registered your the cell's class
- (void) viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"menuCell"];
}