I am fresh in iOS and objective-c. I am learning how to use segues, especially unwind segue.
while reading, I got a bit confused about the usage of 'shouldPerformSegueForIdentifier' and 'performSegueForIdentifier'.
I created an example contains two 'ViewControllers', 'ViewController.m' as shown in the code posted below 'VC_1' and 'ServiceViewController'
my questions are:
-when and how should I use 'performSegueForIdentifier'
-when and how should I use 'shouldIPerformSegueForIdentifier'?
VC_1:
#import "ViewController.h"
#import "ServiceViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a
nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(IBAction)btnStartService:(UIButton *)sender {
if (sender.tag == 1) {
NSLog(#"CLICKED");
[self performSegueWithIdentifier:#"seguePassInterval" sender:(id)
sender];
}
}
-(IBAction)btnExitApp:(UIButton *)sender {
NSLog(#"EXIT_CLICKED");
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"seguePassInterval"]) {
((ServiceViewController*)segue.destinationViewController).data = #"testData"; //passing data to destinationViewController of type "TestViewController"
NSLog(#"SEGUE");
}
}
#end
img
The prepareForSegue method is called right before the segue is executed, and allow to pass data between ViewController among other things, you can by example check if the identifier of your segue is "XxX" and pass some data or if is "YYY" call for a method
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"seguePassInterval"]) {
((TestViewController*)segue.destinationViewController).data = #"testData"; //passing data to destinationViewController of type "TestViewController"
NSLog(#"SEGUE");
}
}
method performSegueWithIdentifier is used to as his name says execute a segue using his identifier, you can perform a segue when you need it
and finally shouldPerformSegue is used to avoid perform a segue if your app is in some state, for example if you don't have the destinationViewController data yet you can return false until you get that
Hope this helps
Related
This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 7 years ago.
I'm trying to pass an object (PFObject) from an view controller to another,
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
RestauCardViewController *restauCard = [[RestauCardViewController alloc]init];
RestaurantAnnotation *restoAnnotation = (RestaurantAnnotation *)view.annotation;
restauCard.restaurant = restoAnnotation.restaurant;
[self performSegueWithIdentifier:#"segueToCard" sender:nil];
}
When I try to display the object in the other view controller I got null:
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#interface RestauCardViewController : UIViewController
#property(nonatomic) PFObject *restaurant;
#end
This is my viewDidLoad function
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"The restaurant name is : %#",_restaurant[#"nom"]);
}
You need to use -prepareForSegue to manage this situation, and you'll need an iVar to keep the restaurant name.
So in the top of your map's .m file, add an ivar NSString
#implementation yourViewController{
NSString *sRestName; //This is empty until the user selects a restaurant
}
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
sRestName = //Set the name of your restaurent here, it's just a string.
//You could set any other type of object (a restaurent object or a PFOjbect or anything,
//just change the ivar accordingly
[self performSegueWithIdentifier:#"segueToCard" sender:nil];
}
What you're going to do is replace your old code with the code just above, you just need to perform the segue to call the following method.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"fromHomeToList"]){
RestauCardViewController *vc = (RestauCardViewController*)segue.destinationViewController;
vc.restaurant = sRestName; //here you're just giving the property of the new controller the content of your ivar.
}
That way you can pass an object from your map tap to your next controller. You're also sure it will never be nil because the user tapped it ; if it was nil, well, he couldn't have tapped it in the first place !
Note that I assumed you were using a string for your restaurant name, but if you change the ivar on the top, you can use anything you want, as long as you can retrieve it from tapping on the map. If you can't, I need more details to walk you through another solution.
Ask me if you have any quesiton, otherwise this should work !
You have to set the restaurent inside the UIViewController method "prepareSegue...". It's called after you performeSegueWithIdentifier, so the destination controller is accessible and you can test the segue.identifier and set the restaurent to the controller.
Example :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"segueRestaurentDetails"]) {
RestauCardViewController *destController = (RestaurentDetailsViewController *)segue.destinationViewController;
destController.restaurant = (RestaurantAnnotation *)view.annotation;
}
Implement prepareForSegue:sender: method in ViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"segueToCard"]) {
RestauCardViewController *controller = (RestauCardViewController *)segue.destinationViewController;
controller.restaurant = (RestaurantAnnotation *)view.annotation;
}
}
In -prepareForSegue:sender:, how do I check if the sender was the view controller in which the -prepareForSegue:sender: is written?
In other words, how do I compare the sender argument to see what was passed when calling -performSegueWithIdentifier:sender:
I tried:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"identifier"])
if ([(MyViewControllerClass *)sender isEqual:self]) {
// stuff
} else {...}
}
}
Also tried == in place of isEqual:.
The else part is always executed.
you can try this
if([sender isKindOfClass:[MyViewControllerClass class]){
//do stuff
}
Update: First below code was crap ofcourse, here the version that does make sense:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self performSegueWithIdentifier:#"segueIdentifier" sender:self];
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if (segue.sourceViewController == sender) {
NSLog(#"i have been the sender");
}
}
Stupid last version as reference what you should not do - answer to fast:
I think you don`t want to check the sender. The sender could also be a Button activating the segue - i guess that you want to check against the sourceViewController.
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if (segue.sourceViewController == self) {
NSLog(#"i have been the source");
}
}
This will check if the segue will transition from the current ViewController.
i've been fixing this problem for a few days. but can't seem to get it..
help me out ..
let me explain my situation. Basically, i have navigation controller that contains table view controller and view controller. and i'm making simple phone book app.
And, i have a directory entry declared in extension class
#interface DetailViewController ()
#property DirectoryEntry *dirEntry;
#end
And, in table view, when you click the button it will transfer some data through segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
DetailViewController *detailCV = [segue destinationViewController];
if ([segue.identifier isEqualToString:#"cellToDetail"]) {
[detailCV setDirEntry: [self.pbArray objectAtIndex:[self.tableView indexPathForSelectedRow].row]];
} else {
detailCV.dirEntry = nil;
}
//designate delegate !!!
detailCV.delegate = self;
}
My Problem occurs when it execute detailCV.dirEntry = nil; it will call my setter in viewController. it says EXC_BAD_ACCESS
-(void) setDirEntry:(DirectoryEntry *) dirEntry {
self.dirEntry = dirEntry;
}
Thank you in advance..
It's not an EXC_BAD_ACCESS so much as the OS killing your app for smashing the stack. This method is recursing infinitely:
-(void) setDirEntry:(DirectoryEntry *) dirEntry {
self.dirEntry = dirEntry;
}
Your use of dot notation expands to a setter which should make this more clear.
-(void) setDirEntry:(DirectoryEntry *) dirEntry {
[self setDirEntry:dirEntry];
}
Set the instance variable directly, or let the compiler handle it. Properties in class extensions are automatically synthesized.
I have a main list of articles, and upon clicking one it segues to a reading view controller, and there I keep track of the progress of the user's reading with that view controller having an NSNumber property holding the position. I want to update this position back to the root view controller when they press the back button (so I can show them their progress) but my delegate doesn't seem to be working.
In the reading view's .h file:
#property (nonatomic, weak) id<UpdateProgressDelegate> delegate;
...
#protocol UpdateProgressDelegate <NSObject>
#required
- (void)finishedReadingWithPosition:(NSNumber *)position;
#end
In the .m file:
- (void)viewDidDisappear:(BOOL)animated {
[super viewWillDisappear:YES];
if ([self.delegate respondsToSelector:#selector(finishedReading:)]) {
[self.delegate finishedReadingWithPosition:self.position];
}
}
In my root view (note it does indeed implement the protocol):
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"ReadBasicArticleSegue"] || [segue.identifier isEqualToString:#"ReadFullArticleSegue"]) {
ReadingViewController *destination = segue.destinationViewController;
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
self.rowOfLastSelectedCell = #(indexPath.row);
Article *article = self.articles[[self.rowOfLastSelectedCell intValue]];
// Set ReadingViewController's variables so the selected article can be read
destination.textToRead = [article.body componentsSeparatedByString:#" "];
destination.wordsPerMinute = #(1500);
destination.numberOfWordsShown = #(3);
destination.delegate = self;
}
}
and...
- (void)finishedReadingWithPosition:(NSNumber *)position {
Article *article = [self.articles objectAtIndex:[self.rowOfLastSelectedCell intValue]];
article.position = position;
[self.tableView reloadData];
}
I just don't see what I'm doing wrong. When I press the back button, the root view controller still has a 0% progress indicator.
Here:
- (void)viewDidDisappear:(BOOL)animated {
[super viewWillDisappear:YES];
if ([self.delegate respondsToSelector:#selector(finishedReading:)]) {
[self.delegate finishedReadingWithPosition:self.position];
}
}
viewDidDisappear: should pass the same 'did' method to super, not viewWillDisappear: ..
[super viewDidDisappear:animated];
The selector finishedReading: is not the same as the selector finishedReadingWithPosition:. As it is not implemented in the delegate, the conditional is not called.
Typos - or the solution...?
two quick things:
first, in your delegate respondstoSelector check, make sure you are testing for the correct method. You have "finishedReading:" in the check, then you call finishedREadingWithPosition:" in the method call. My guess is that it's skipping that line because its the wrong selector your checking for.
next make sure self.position have a value
and also:
I agree on the [super] call that #He Was mentioned - needs to be a call for the same method
I am trying to make my first iOS app after doing a few tutorials. I am making a simple headline/news reader that pulls New Orleans Saints headlines and stories as json from an ESPN API, displays the headlines in a table view, and then displays the text of the story in a detail view when the user taps a headline.
The app compiles and runs without errors or warnings. The prepareForSegue method executes when the user taps a headline in the top tableview. I can step thru this code in the debugger. If I step thru the prepareForSegue method, it calls the detailViewController's setStory method, which in turn calls the configureview method. But then it takes me to main.m and the app closes, without showing the detail view.
I am a noob. What am I missing? Why don't I see the detail view? Maybe I need an IBAction to load the IBOutlet?
saintsMasterViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetail"]) {
saintsDetailViewController *detailViewController = [segue destinationViewController];
saintsNewsStory* displayThisStory =[self.dataController objectInListAtIndex:[self.tableView indexPathForSelectedRow].row];
[detailViewController setStory:displayThisStory];
}
}
saintsDetailViewController.m
- (void)setStory:(saintsNewsStory *)newStory
{
// if (_story != newStory) {
_story = newStory;
// Update the view.
// [self configureView];
// }
}
- (void)configureView
{
self.storyText.text = self.story.storyText;
}
- (void)viewDidLoad //the app never gets here. if i put a breakpoint here it never gets here
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configureView];
}
saintsDetailViewController.h
#class saintsNewsStory;
#interface saintsDetailViewController : UIViewController
#property (weak, nonatomic) IBOutlet UITextView *storyText;
#property (retain, nonatomic) saintsNewsStory *story;
#end
The console shows this error:
SaintsHeadlineReader[9608:11303] * Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key detailDescriptionLabel.'
* First throw call stack:
(0x1c90012 0x10cde7e 0x1d18fb1 0xb7a711 0xafbec8 0xafb9b7 0xb26428 0x2320cc 0x10e1663 0x1c8b45a 0x230bcf 0xf5e37 0xf6418 0xf6648 0xf6882 0x102235 0x3013d2 0xff4f3 0xff777 0xff7b7 0x46afe2 0x45cad9 0x45cb54 0xc4899 0xc4b3d 0xacbe83 0x1c4f376 0x1c4ee06 0x1c36a82 0x1c35f44 0x1c35e1b 0x1bea7e3 0x1bea668 0x1565c 0x204d 0x1f75 0x1)
libc++abi.dylib: terminate called throwing an exception
It seems like textview has a detail label and maybe I needed an outlet for that?
Change this line :
saintsDetailViewController *detailViewController = [segue destinationViewController];
To this:
saintsDetailViewController *detailViewController = (saintsDetailViewController *)[segue destinationViewController];
EDIT:
You should show your view first and then call the configureView method. Try to call configureView in viewDidLoad of that ViewController.
- (void)setStory:(saintsNewsStory *)newStory
{
// if (_story != newStory) {
_story = newStory;
// Update the view.
// [self configureView];
// }
}
-(void) viewDidLoad {
[self configureView];
}
- (void)configureView
{
self.storyText.text = self.story.storyText; //the app gets to here but does not show the view. why?
}
Test my theory. Put NSLog(#"%d", [self.yourTableView indexPathForSelectedRow].row); in your current prepareForSegue method immediately before or after the line you currently use the indexPathForSelectedRow, and see what it is returning in your console. Probably 0.
Instead of connecting the segue from the UITableViewCell Prototype to the DetailViewController, connect it from the UITableViewController to the DetailViewController and then use the methods below:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SaintsDetailViewController *detailViewController = [[SaintsDetailViewController alloc] init];
saintsNewsStory* displayThisStory =[self.dataController objectAtIndex:indexPath.row];
[detailViewController setStory:displayThisStory];
[self performSegueWithIdentifier:#"showDetail" sender:indexPath];
}
and your prepareForSegue: method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString:#"showDetail"]) {
saintsDetailViewController *detailViewController = [segue destinationViewController];
}
}