I have a segue that is being called progmatically by performSegueWithIdentifier: but it will not trigger. However, when I create the segue with a button press the segue works without a problem. I have also tried changing the name of my segue in code, and it produces a no segue with identifier error.
Here is my code (Note: the segue is called in two different places to check if it would work somewhere else in the code.)
#import "SignUpViewController.h"
#import "ProgressHUD.h"
#interface SignUpViewController ()
#end
#implementation SignUpViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self performSegueWithIdentifier:#"profilePic" sender:self];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (IBAction)createAnAccount:(id)sender {
[ProgressHUD show:#"Please Wait"];
if ([self.passwrod.text isEqualToString:self.confirmPassword.text]){
// Register User
PFUser *user = [PFUser user];
user.username = self.username.text;
user.password = self.passwrod.text;
user.email = self.eMail.text;
// other fields can be set if you want to save more information
NSString *name = [self.firstName.text stringByAppendingString:#" "];
NSString *fullName = [name stringByAppendingString:self.lastName.text];
user[#"name"] = fullName;
user[#"posts"] = #0;
user[#"score"] = #5;
user[#"followers"] = #0;
user[#"following"] = #0;
[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
// Hooray! Let them use the app now.
[self performSegueWithIdentifier:#"profilePic" sender:self];
[ProgressHUD showSuccess:nil];
NSLog(#"Perform Segue");
} else {
NSString *errorString = [error userInfo][#"error"];
// Show the errorString somewhere and let the user try again.
[ProgressHUD showError:errorString];
}
}];
} else {
// Alert User
[ProgressHUD showError:#"Please check your passwords as they do not match."];
}
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"Preparing for segue");
NSLog(#"Segue: %#", segue.identifier);
}
#end
Update for clarification: The prepareForSegue method is being called and logging.
Thanks for your help!
You shouldn't call performSegueWithIdentifier in viewDidLoad. In fact, I'm pretty sure you'll see a warning or error in the console if you do. Move the call to viewDidAppear instead.
The solution that worked for me was to delete all the segues to the view controller in question and then re-added them. This porbably won't work for everyone, but it is worth a shot.
Related
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
I have two views :
View1 (Shop) : URL stocked in NSString for displaying image.
View2 (ModifyShop) : Text field with URL from view1.
I can pass data from view1 to view2 : The URL stocked in NSString appears in Text field.
Now I would like to modify this URL with Text field from view2 and that modify the NSString in view1. How can I make that ?
Here is my code :
Shop:
- (void)viewDidLoad {
[super viewDidLoad];
[self.modifyButton setHidden:YES];
dispatch_async(dispatch_get_global_queue(0,0), ^{
self.imageButtonURL = #"http://bundoransurfshop.com/wp-content/uploads/2015/02/72-torq-pink.jpg";
imageButtonData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:self.imageButtonURL]];
if ( imageButtonData == nil )
return;
dispatch_async(dispatch_get_main_queue(), ^{
self.imageButton.imageView.image = [UIImage imageWithData: imageButtonData];
});
});
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"modifyShop"]) {
ShopModify *viewcontroller = (ShopModify *)segue.destinationViewController;
viewcontroller.imageButtonURL = self.imageButtonURL; }
}
-(IBAction)prepareForUnwind:(UIStoryboardSegue *)segue {
NSLog(#"%#", self.imageButtonURL);}
ModifyShop:
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
self.photoURL.text = [NSString stringWithFormat:#"%#", self.imageButtonURL];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
Shop *viewcontroller = (Shop *)segue.destinationViewController;
viewcontroller.imageButtonURL = self.photoURL.text;
}
That makes my app crashes :
[Reports setImageButtonURL:]: unrecognized selector sent to instance
The error is saying that you are tying to set imageButtonURL on an instance of Reports, not on Shop which is what you think your destination view controller is. It appears that your unwind is going to the wrong controller. You must have hooked up the unwind segue incorrectly. You say that you have 2 views (view controllers actually), but you must also have a Reports class in your app.
I have a table view with different information the user can update. One of them is "pilot information". When the user clicks on this row they are taking to another view controller where they fill out information on the pilot. (name phone number etc)
They can then hit save or cancel both of which return them to the previous table view controller. What I want to do is have the user be able to click on pilot information again, and view the data they just entered and edit it if they choose. My solution to this was to add another segue and give it a unique identifier using the following code. (pilot is the name of the entity in my core data model, updatepilot is the name I gave the segue.)
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"UpdatePilot"]) {
NSManagedObject *selectedPilot = [self.pilots objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
PilotViewController *destViewController = segue.destinationViewController;
destViewController.pilot = selectedPilot;
}
}
!http://tinypic.com/r/2ihbera/8
However, I get the error use of undeclared identifier 'destviewcontroller" did you mean UIViewController? Is there something I'm not seeing here? any help would be appreciated!
#import "PilotViewController.h"
#interface PilotViewController ()
#end
#implementation PilotViewController
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
#synthesize pilot;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (self.pilot) {
[self.nameField setText:[self.pilot valueForKey:#"pilotName"]];
[self.emailField setText:[self.pilot valueForKey:#"pilotEmail"]];
[self.phoneField setText:[self.pilot valueForKey:#"pilotPhone"]];
[self.insuranceField setText:[self.pilot valueForKey:#"pilotInsurance"]];
}
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//Code created for cancel and save buttons
- (IBAction)cancel:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)save:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
if (self.pilot) {
// Update existing Pilot
[self.pilot setValue:self.nameField.text forKey:#"pilotName"];
[self.pilot setValue:self.phoneField.text forKey:#"pilotPhone"];
[self.pilot setValue:self.insuranceField.text forKey:#"pilotInsurance"];
[self.pilot setValue:self.emailField.text forKey:#"pilotEmail"];
} else {
// Create a new pilot
NSManagedObject *newPilot = [NSEntityDescription insertNewObjectForEntityForName:#"Pilot" inManagedObjectContext:context];
[newPilot setValue:self.nameField.text forKey:#"pilotName"];
[newPilot setValue:self.phoneField.text forKey:#"pilotPhone"];
[newPilot setValue:self.insuranceField.text forKey:#"pilotInsurance"];
[newPilot setValue:self.emailField.text forKey:#"pilotEmail"];
}
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
Try casting the segue.destinationViewController (which is type id) to PilotViewController.
PilotViewController *destViewController = (PilotViewController *)segue.destinationViewController;
In previous versions of Xcode I believe I saw this as a warning; your build settings may be set to treat all warnings as errors.
I've built a custom UITabBarController with Storyboards/Segues and UIViewController containment. Here is a link to it: https://github.com/mhaddl/MHCustomTabBarController
The UIViewControllers which will be presented by the Container are stored in a NSMutableDictionary (keys are the segues' identifiers). Everything is working fine until the point is reached where I come back to a earlier presented ViewController. At this moment "dealloc" gets called on this ViewController before it is presented.
How can I prevent "dealloc" from getting called so it can be used to unsubscribe from Notifications, and nil delegates.
MHCustomTabBarController:
#implementation MHCustomTabBarController {
NSMutableDictionary *_viewControllersByIdentifier;
}
- (void)viewDidLoad {
[super viewDidLoad];
_viewControllersByIdentifier = [NSMutableDictionary dictionary];
}
-(void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.childViewControllers.count < 1) {
[self performSegueWithIdentifier:#"viewController1" sender:[self.buttons objectAtIndex:0]];
}
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
self.destinationViewController.view.frame = self.container.bounds;
}
#pragma mark - Segue
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if (![segue isKindOfClass:[MHTabBarSegue class]]) {
[super prepareForSegue:segue sender:sender];
return;
}
self.oldViewController = self.destinationViewController;
//if view controller isn't already contained in the viewControllers-Dictionary
if (![_viewControllersByIdentifier objectForKey:segue.identifier]) {
[_viewControllersByIdentifier setObject:segue.destinationViewController forKey:segue.identifier];
}
for (UIButton *aButton in self.buttons) {
[aButton setSelected:NO];
}
UIButton *button = (UIButton *)sender;
[button setSelected:YES];
self.destinationIdentifier = segue.identifier;
self.destinationViewController = [_viewControllersByIdentifier objectForKey:self.destinationIdentifier];
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([self.destinationIdentifier isEqual:identifier]) {
//Dont perform segue, if visible ViewController is already the destination ViewController
return NO;
}
return YES;
}
#pragma mark - Memory Warning
- (void)didReceiveMemoryWarning {
[[_viewControllersByIdentifier allKeys] enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
if (![self.destinationIdentifier isEqualToString:key]) {
[_viewControllersByIdentifier removeObjectForKey:key];
}
}];
}
#end
MHTabBarSegue:
#implementation MHTabBarSegue
- (void) perform {
MHCustomTabBarController *tabBarViewController = (MHCustomTabBarController *)self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *) tabBarViewController.destinationViewController;
//remove old viewController
if (tabBarViewController.oldViewController) {
[tabBarViewController.oldViewController willMoveToParentViewController:nil];
[tabBarViewController.oldViewController.view removeFromSuperview];
[tabBarViewController.oldViewController removeFromParentViewController];
}
destinationViewController.view.frame = tabBarViewController.container.bounds;
[tabBarViewController addChildViewController:destinationViewController];
[tabBarViewController.container addSubview:destinationViewController.view];
[destinationViewController didMoveToParentViewController:tabBarViewController];
}
#end
"At this moment "dealloc" gets called on this ViewController before it is presented." -- no, not really. Dealloc is being called on a controller that never gets on screen, not the one you came from initially or are going back to. The way your segue is set up, and the fact that you keep a reference to your controllers in the dictionary, means that they never get deallocated. Segues (other than unwinds) ALWAYS instantiate new view controllers, so what's happening is that a new instance of, say VC1 is created when you click on the first tab (and a segue is triggered), but you never do anything with that controller (which would be self.destinationViewController in the custom segue class) so it's deallocated as soon as the perform method exits.
Depending on where you setup any delegates or notification observers, this might not be a problem -- this controller that's created, and then immediately deallocated never has its viewDidLoad method called, so if you do those things in viewDidLoad, they won't ever happen for this transient view controller.
If you don't want this to happen, then you need to make your transitions in code without using segues.
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