I have a tableView populated with NSString values from an NSFetchedResultsController. I have the cells configured to check/uncheck cells. That works perfectly.
I'm trying to configure the cells so clicking one of the labels within the cell triggers a segue to another view controller. I configured the label and threw a log statement in where I'd like to do the segue and everything works properly. When I add the segue, get no compiler errors, but it crashes at runtime with the follow error:
2015-05-31 08:09:04.656 MyApp[17682:1240919] -[UIViewController selectedItemPhoto:]: unrecognized selector sent to instance 0x7fa42b65d4e0
2015-05-31 08:09:04.694 MyApp[17682:1240919] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewController selectedItemPhoto:]: unrecognized selector sent to instance 0x7fa42b65d4e0'
I'm fairly certain I'm doing something startlingly stupid, but I'm stumped as to what. I welcome suggestions.
MyCustomCell.h:
#property (strong, nonatomic) IBOutlet UILabel *itemDescription;
#property (strong, nonatomic) IBOutlet UILabel *itemGroup;
#property (strong, nonatomic) IBOutlet UIImageView *itemImage;
FirstVC.m methods:
cellForRowAtIndexPath method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// implement custom cell
MyCustomCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"customCell" forIndexPath:indexPath];
// Get a hold of myManagedObject from FRC
MyNSManagedObject *myObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
// Configures 1st label to look like a hyperlink
customCell.itemDescription.text = myObject.itemDescription;
customCell.itemDescription.textColor = [UIColor blueColor];
customCell.itemDescription.userInteractionEnabled = YES;
// Enables gesture and sets demoObject to pass along via the segue called from labelTap
UITapGestureRecognizer *labelTapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(labelTap)];
[customCell.itemDescription addGestureRecognizer:labelTapGesture];
self.demoObject = myObject;
// Configures 2nd label within customCell
customCell.itemGroup.text = myObject.itemGroup;
// add image to cell, already imported into Images.xcassets
NSString *imageName = [NSString stringWithFormat:#"%#-1.jpg", myObject.photo];
customCell.imageView.image = [UIImage imageNamed:imageName];
// The following code ensures random checkmarks don't appear when the user scrolls.
if ([self.selectedObjects containsObject:[self.fetchedResultsController objectAtIndexPath:indexPath]]) {
customCell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
customCell.accessoryType = UITableViewCellAccessoryNone;
}
return customCell;
}
didSelectRowAtIndexPath method
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.selectedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
// Set the checkmark accessory for the selected row.
if ([tableView cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryNone) {
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryCheckmark];
[self.selectedObjects addObject:self.selectedObject];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
} else {
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryNone];
[self.selectedObjects removeObject:self.selectedObject];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
// Enables save button if there are items in the selectedObjects array
if (self.selectedObjects.count > 0) {
[self.saveButton setEnabled:YES];
} else {
[self.saveButton setEnabled:NO];
}
}
labelTap method
- (void) labelTap {
// The "hyperlink" effect I'm trying to achieve works without the segue
NSLog(#"itemDescription tapped");
[self performSegueWithIdentifier:#"mySegue" sender:nil];
}
prepareForSegue method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"mySegue"] && self.demoObject != nil) {
// This line returns a value...
NSLog(#"self.demoObject = %#", self.demoObject.itemDescription);
// ...but it crashes here when it tries to set on the destinationViewController
SecondViewController *destinationViewController = [segue destinationViewController];
destinationViewController.selectedItemPhoto = self.demoObject.photo;
destinationViewController.selectedItemTitle = self.demoObject.itemDescription;
}
}
SecondViewController.h properties
// The photo is a string that references a filename in my app
#property (nonatomic, strong) NSString *selectedItemPhoto;
#property (nonatomic, strong) NSString *selectedItemTitle;
From the error it seems you did not set the class of your second view controller. It seems it is UIViewController and should be SecondViewController.
In your storyboard select the second view controller and set its class to SecondViewController.
Another advice is that in Objective C you should use introspection before casting an object. So in your code I would add:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"mySegue"] && self.demoObject != nil) {
// This line returns a value...
NSLog(#"self.demoObject = %#", self.demoObject.itemDescription);
// ...but it crashes here when it tries to set on the destinationViewController
if ([[segue destinationViewController] isKindOfClass:[SecondViewController class]]) {
SecondViewController *destinationViewController = (SecondViewController *)[segue destinationViewController];
destinationViewController.selectedItemPhoto = self.demoObject.photo;
destinationViewController.selectedItemTitle = self.demoObject.itemDescription;
}
}
}
In this way the segue won't happen but the app won't crash.
I figured it out! I had three issues (two were tied together):
Issue 1: Juan Catalan pointed out I didn't have my storyboard set for SecondViewController
Issue 2: Yuchen & luk2302 raised the issue of how I'm performing the segue.
Issue 3: I don't need prepareForSegue
Here's what I did:
1: I removed prepareForSegue.
2: I removed the storyboard segue.
2: I altered labelTap method to add a parameter for the labelTapGesture
- (void) labelTap:(UITapGestureRecognizer *)sender {
NSLog(#"stretchDescription tapped");
CGPoint location = [sender locationInView:self.view];
if (CGRectContainsPoint([self.view convertRect:self.tableView.frame fromView:self.tableView.superview], location)) {
CGPoint locationInTableview = [self.tableView convertPoint:location fromCoordinateSpace:self.view];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:locationInTableview];
if (indexPath) {
self.demoObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
DisplayStretchViewController *destinationViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondVC"];
// Set properties on destinationVC
destinationViewController.itemPhoto = self.demoObject.photo;
destinationViewController.itemTitle = self.demoObject.stretchDescription;
[self.navigationController pushViewController:destinationViewController animated:YES];
}
}
}
I also found this post extremely helpful:
UILabel with Gesture Recognizer inside UITableViewCell blocks didSelectRowAtIndexPath
Thank you to all for your input!
Related
I'm able to pass data between two view controllers using the prepareForSegue method. But in that way, the passed data cannot be used in the second view controller's init method.
Also, I'm using XLForm. So accessing data inside the init method is necessary.
Can anyone help me solve this problem.
Here's the first view controller's prepareForSegue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"SessionToWorkout"])
{
WorkoutsViewController *vc = [segue destinationViewController];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
UITableViewCell *selectedCell = [self.tableView cellForRowAtIndexPath:indexPath];
cellName = selectedCell.textLabel.text;
NSString *sectionTitle = [self tableView:self.tableView titleForHeaderInSection:indexPath.section];
sectionName = sectionTitle;
vc.sectionName = sectionName;
vc.cellName = cellName;
}
}
And here's the initWithCoder method of the second view controller:
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
//Retrieve Workouts from DB
NSString *day_id;
day_id = [[DBHandler database] getDayIdWhere:#[#"day_name"]
whereValues:#[cellName]];
workoutsArray = [[DBHandler database] getWorkoutsForDayWhere:#[#"day_id"]
whereValues:#[day_id]];
AppLog(#"Workouts array %#", workoutsArray);
[self initializeForm];
}
return self;
}
Inside the initWithCoder method, I need to use the cellName variable's value (which has been passed to this view controller from the previous view controller) to call the database method.
Any idea or suggestions how to do that?
Thanks in advance.
The didSet observer is called when a variable is modified (it's not called when the variable is initialized). Initialize the database when the cellName is set :
Swift:
var cellName : String = "" {
didSet {
// The cell name has been set, create the database here as in any other function
}
}
Objective-C:
It's quite similar in objective C, but you don't have a didSet observer, instead use a custom setter. The only difference is, since it's a setter, that you have to set your variable
#property(nonatomic, strong) NSString * cellName;
-(void)setCellName:(NSString *)newValue
{
// First, set the new value to the variable
_cellName = newValue;
// The cell name has been set, create the database here
}
if ([[segue identifier] isEqualToString:#"SessionToWorkout"])
{
UITableViewCell *cell = sender;
// Get reference to the destination view controller
WorkoutsViewController *vc = [segue destinationViewController];
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
//you can get cell value in didSelectRowAtIndexPath fun
cellName = cell.textLabel.text;
NSString *sectionTitle = [self tableView:self.tableView titleForHeaderInSection:indexPath.section];
sectionName = sectionTitle;
[vc setSectionName: sectionName];
[vc setCellName: cellName];
//check it have right value
NSLog(#"Cell name %# Section name %#", cellName ,sectionName);
//*** in viewControllerB set sectionName, cellName as #property and set it to #synthesize
}
The following code is in my menuViewController.m. Now I want to go to another view when touch on a specific cell. What should I do to go to contactViewController?(I am using storyboard)
storyboard image:
https://drive.google.com/file/d/0BwOYR2GMJ7l8U1lYYVFVTWVkOG8/edit?usp=sharing
code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
NSString *CellIdentifier = [menuItems objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.row==2)
{
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Set the title of navigation bar by using the menu items
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
UINavigationController *destViewController = (UINavigationController*)segue.destinationViewController;
destViewController.title = [[menuItems objectAtIndex:indexPath.row] capitalizedString];
if ( [segue isKindOfClass: [SWRevealViewControllerSegue class]] ) {
SWRevealViewControllerSegue *swSegue = (SWRevealViewControllerSegue*) segue;
swSegue.performBlock = ^(SWRevealViewControllerSegue* rvc_segue, UIViewController* svc, UIViewController* dvc) {
UINavigationController* navController = (UINavigationController*)self.revealViewController.frontViewController;
[navController setViewControllers: #[dvc] animated: NO ];
[self.revealViewController setFrontViewPosition: FrontViewPositionLeft animated: YES];
};
}
I want to go to another view when touch on a specific cell.
For this, I'd do it this way:
Step 1:
Drag from TableViewController to ContactViewController:
Step 2:
Select segue and specify the Segue Identifier (Show attributes Inspector tab in the right side bar)
I have named the Segue Identifier as SegueTestID
I chose Push as my style but it seems you might need Modal
And the corresponding code (in your MenuViewController.m) should be something like:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 2) {
[self performSegueWithIdentifier:#"SegueTestID" sender:self];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//following lines needed only if you need to send some detail across to ContactViewController
if ([segue.identifier isEqualToString:#"SegueTestID"]) {
ContactViewController *destinationViewController = segue.destinationViewController;
destinationViewController.strTest = #"Check";
//where strTest is a variable in ContactViewController. i.e:
//"#property (nonatomic, strong) NSString *strTest;"
//declared in `ContactViewController.h`
}
//...
}
PS: It seems you have alot in your -prepareForSegue: already.
Obviously... you'll need to hook things up properly.
In Storyboard , Do this ( In your case its contactviewcontroller ) give the name identifier name to contactViewController whatever you want as shown in image for showRecipeDetail
and you can go to the contactviewcontroller
and then
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showRecipeDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
RecipeDetailViewController *destViewController = segue.destinationViewController;
destViewController.recipe = [recipes objectAtIndex:indexPath.row];
}
}
In above method it shows how to pass the data from current viewcontroller to destviewcontroller
where We simply set the property (i.e. recipeName) in the RecipeDetailViewController to pass the recipe name. Obviously, you can add other properties in the detail view controller to pass other recipe-related values. ( In your case it will be data you want to pass to contactviewcontroller)
When a segue is triggered, before the visual transition occurs, the storyboard runtime invokes prepareForSegue:sender: method of the current view controller. By implementing this method, we can pass any needed data to the view controller that is about to be displayed. Here, we’ll pass the selected recipe object to the detail view controller.
I have a TableView that load JSON content from web.
I use AFNetworking and JSONModel. And I use this tutorial do Receive and Parse the Data
Here is the code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"CellIdentifier";
__weak ProgramacaoTableCell *cell = (ProgramacaoTableCell *)[self.tableView dequeueReusableCellWithIdentifier:identifier];
ProgramacaoModel* programacao = _programacao.programacaoArray[indexPath.row];
// NameLabel is the Label in the Cell.
cell.nameLabel.text = [NSString stringWithFormat:#"%#", programacao.atracao ];
return cell;
}
I want to know how pass this data to a Detail ViewController.
In my DetailViewController i have the properties to receive the data.
#property (nonatomic, strong) IBOutlet UILabel *programacaoNomeLabel;
You can access to your controller through your navigation:
NSArray* vcStack=[self appDelegate].myNavigationController.viewControllers;
UIViewController* vcUnder;
if(vcStack.count > 0)
vcUnder=[vcStack objectAtIndex:(vcStack.count-1)];
// -1 depends when you called your controller that's why we test the kind of class
if([vcUnder isKindOfClass:[DetailViewController class]]){
((DetailViewController*) vcUnder). programacaoNomeLabel = #"some data";
}
I find the answer
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Pass the selected object to the new view controller.
if ([[segue identifier] isEqualToString:#"pushDetalhesView"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
// Pega o objeto da linha selecionada
ProgramacaoModel *object = [_programacao.programacaoArray objectAtIndex:indexPath.row];
// Pass the content from object to destinationViewController
[[segue destinationViewController] getProgramacao:object];
}
}
In my Details ViewController I create an iVar
#interface ProgramacaoDetalhesViewController ()
{
ProgramacaoModel *_programacao;
}
And set two method, one to receive the content and another to set the Labels
- (void) getProgramacao:(id)programacaoObject;
{
_programacao = programacaoObject;
}
- (void) setLabels
{
programacaoNomeLabel.text = _programacao.atracao;
}
I had encounter a question. I used collection view set more button. I need click button and passs index value to "NextViewController". But when I click the button. It is show below error message.
I try to find where's error. But I can't find.
Have any one can give me some hint?
thank you very much.
==========error message ===========
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '- [UIViewController
itemsArray:]: unrecognized selector sent to instance 0x9e78220'
===================================
=========== ListViewController.m==========
#interface ListViewController ()
{
NSMutableArray * itemsArray ;
}
end
...
... //itemsArray had some data from webesrvice
...
-(UICollectionViewCell*) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomizedCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CustomizedCell" forIndexPath:indexPath];
NSInteger targetIndex = indexPath.row + indexPath.section*3;
if( targetIndex < itemsArray.count )
{
[cell.cateBtn setTitle:[[itemsArray objectAtIndex:targetIndex] itemName] forState:UIControlStateNormal];
cell.cateBtn.tag = targetIndex;
[cell.cateBtn addTarget:self action:#selector(jumpToNextView:) forControlEvents:UIControlEventAllEvents];
return cell;
}
-(void)jumpToNextView:(UIButton*)sender
{
[self performSegueWithIdentifier:#"MySegueName" sender:sender];
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NextViewController *nextViewController =
segue.destinationViewController;
nextViewController. itemsArray = itemsArray;
}
-(BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
if( targetIndex < itemsArray.count )
return YES;
else
return NO;
}
========== NextViewController.h =========
#interface NextViewController : UIViewController
#property (strong, nonatomic) NSMutableArray *itemsArray;
#end
I guess the Problem is with your button Action method name: jumpToNextView
in this line:
[cell.cateBtn addTarget:self action:#selector(jumpToNextView:) forControlEvents:UIControlEventAllEvents];
Rather Change jumpToDrScheduleList: in selector or Change Method name.
-(void)jumpToNextView:(UIButton*)sender
{
[self performSegueWithIdentifier:#"MySegueName" sender:sender];
}
check the class name for NextViewController in storyboard. it should be NextViewController. but as
i can see in crash log class name is UIViewController & UIViewController has nothing like itemsArray.
thats the reason you are getting crash here.
modify this
[cell.cateBtn addTarget:self action:#selector(jumpToNextView:) forControlEvents:UIControlEventAllEvents];
with
[cell.cateBtn addTarget:self action:#selector(jumpToDrScheduleList:) forControlEvents:UIControlEventTouchUpInside];
selector for button & event to UIControlEventTouchUpInside;
I am just trying to transfer a simple string from a UILabel in a prototype cell into a label in the next View Controller. Value of label.text in the viewDidLoad of the View Controller is returning (null).
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
mainCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (mainCell == nil) {
mainCell = [[dictionaryTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSString* date = [dateArray objectAtIndex:indexPath.row];
mainCell.viewLabel.text = date;
return mainCell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"View Segue"]) {
NSLog(#"View Load Segue Success");
ViewController *one = segue.destinationViewController;
one.label.text = mainCell.viewLabel.text;
}
}
What am I doing wrong here?
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"View Segue"]) {
NSLog(#"View Load Segue Success");
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
ViewController *one = segue.destinationViewController;
one.label.text = [dateArray objectAtIndex:indexPath.row];
}
}
And actually, assigning text to text label you should do in your viewController one's method(viewDidLoad or viewWillAppear). So, you need to make a property in viewController one for transferring NSString.
You can use indexPathForSelectedRow:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"View Segue"]) {
ViewController *one = segue.destinationViewController;
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
one.textProperty = [dateArray objectAtIndex:indexPath.row];
}
}
Or you can also use sender if your segue is from the cell to the next scene, e.g.:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"View Segue"])
{
ViewController *one = segue.destinationViewController;
NSAssert([sender isKindOfClass:[UITableViewCell class]], #"Not cell");
UITableViewCell *cell = sender;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
one.textProperty = [dateArray objectAtIndex:indexPath.row];
}
}
Two things to note:
As a matter of good programming style, I am not retrieving the text value from the cell. I'm retrieving the text value from the model. You should not be relying upon the view for information to be passed along. Go back to the model, the original source of the information.
Do not set the text property of the label in the destination controller directly. The controls of the destinationController have not been created yet. You should defer setting controls until the destinationController's viewDidLoad. So, instead, create a NSString property in the destination controller:
#property (nonatomic, strong) NSString *textProperty;
Clearly, you should use a more descriptive name than textProperty, but hopefully you get the idea. Anyway, prepareForSegue can set this new property and the viewDidLoad of the destination controller should then use that NSString property to populate the text property of the UILabel, e.g.:
- (void)viewDidLoad
{
[super viewDidLoad];
self.label.text = self.textProperty;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
YourViewController *controller =[[YourViewController alloc] init];
[self presentModalViewController:controller animated:YES];
one.label.text = [dateArray objectAtIndex:indexPath.row];
}
Change the label.text after presentModalViewController. Now what happens?
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion
I understand you are already using Segue. You should follow the other answer.