It is possible to use an existing ViewController with PerformSegueWithIdentifier? - objective-c

I use the method performSegueWithIdentifier:sender: to open a new ViewController from a storyboard-file programmatically. This works like a charm.
But on every time when this method is being called, a new ViewController would be created. Is it possible to use the existing ViewController, if it exista? I don't find anything about this issue (apple-doc, Stack Overflow, ...).
The Problem is:
On the created ViewController the user set some form-Elements and if the ViewController would be called again, the form-elements has the initial settings :(
Any help would be appreciated.
Edit:
I appreciate the many responses. Meanwhile, I'm not familiar with the project and can not check your answers.

Use shouldPerforSegueWithIdentifier to either allow the segue to perform or to cancel the segue and manually add your ViewController. Retain a pointer in the prepareForSegue.
... header
#property (strong, nonatomic) MyViewController *myVC;
... implementation
-(BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
if([identifier isEqualToString:#"MySegueIdentifier"]){
if(self.myVC){
// push on the viewController
[self.navigationController pushViewController:self.myVC animated:YES];
// cancel segue
return NO;
}
}
// allow the segue to perform
return YES;
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"MySegueIdentifier"]){
// this will only be called the first time the segue fires for this identifier
// retian a pointer to the view controller
self.myVC = segue.destinationViewController;
}
}

To reuse an existing UIViewController instance with a segue create the segue from scratch and provide your own (existing) destination (UIViewController). Do not forget to call prepareForSegue: if needed.
For example:
UIStoryboardSegue* aSegue = [[UIStoryboardSegue alloc] initWithIdentifier:#"yourSegueIdentifier" source:self destination:self.existingViewController]
[self prepareForSegue:aSegue sender:self];
[aSegue perform];

Following code makes singleton view controller.
Add them to your destination view controller implementation, then segue will reuse the same vc.
static id s_singleton = nil;
+ (id) alloc {
if(s_singleton != nil)
return s_singleton;
return [super alloc];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
if(s_singleton != nil)
return s_singleton;
self = [super initWithCoder:aDecoder];
if(self) {
s_singleton = self;
}
return self;
}

I faced this problem today and what I have done is to create the view controller manually and store it's reference.
Then every time I need the controller, check first if exists.
Something like this:
MyController *controller = [storedControllers valueForKey:#"controllerName"];
if (!controller)
{
controller = [[UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:NULL] instantiateViewControllerWithIdentifier:#"MyControllerIdentifierOnTheStoryboard"];
[storedControllers setValue:controller forKey:#"controllerName"];
}
[self.navigationController pushViewController:controller animated:YES];
Hope it helps.

Create a property for the controller.
#property (nonatomic, weak) MyController controller;
And use some kind of lazy initialization in performSegueWithIdentifier:sender
if (self.controller == nil)
{
self.controller = [MyController alloc] init]
...
}
In this case, if controller was already created, it will be reused.

Firstly you would be going against Apple's design in Using Segues: "A segue always presents a new view controller".
To understand why it might help to know that what a segue does is create a new view controller and then the perform calls either showViewController or showDetailViewController depending on what kind of segue it is. So if you have an existing view controller just call those methods! e.g.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
Event *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
self.detailViewController.detailItem = object;
[self showDetailViewController:self.detailViewController.navigationController sender:self];
}

You would need to make the Viewcontroller into a singleton class.

Related

Performwithsegue and programatically switching views - Objective-C

I am having a problem, I want to change to another viewcontroller when a timer expires and this works with this code:
- (IBAction)Akkoord:(id)sender {
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [mainStoryboard instantiateViewControllerWithIdentifier:#"Innameformulier"];
[self presentViewController:vc animated:YES completion:nil];
[self performseguewithidentifier:#"nextcontroller"]
}
But when I use this, my variables in my prepareforsegue method are not passing. How can I do this? I already tried [self performseguewithidentifier] but this is not working.
my preparforsegue code is:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
Innameformulier *IForm = [segue destinationViewController];
IForm.SignatureTransport = _drawImage.image;
IForm.KlantnaamInname = _Klantnaamtransport;
}
How can I call this function to happen on the timer?
In Interface Builder, select the View Controller from which you want to fire a segue, CTRL-drag from it to the destination View Controller and create a segue.
Select the segue, open the Attributes Inspector and create an Identifier for it.
Go back to your View Controller Class that fires the segue and if you don't already have it commented, add the method prepareForSegue:
In your prepareForSegue: method you should have something like the following:
- (void)prepareForSegue:(UIStoryboardSegue *)segue {
if ([segue.identifier isEqualToString:#"XXXX"]) {
DestinationVC *dVC = segue.destinationViewController;
dVC.attribute1 = self.anAttribute;
dVC.attribute2 = self.anotherAttribute;
}
}
And now you can fire the segue by calling [self performSegueWithIdentifier:#"XXXX"].
Note: you can also fire an action segue from an UIControl subclass like UIButton
OK, what you are doing here is bypassing the segue completely. You need to do something like this...
- (IBAction)Akkoord:(id)sender {
[self performseguewithidentifier:#"nextcontroller"]
}
This is all you need to do. The segue will then create the nextController for you and then the code inside prepareForSegue will pass the variables across.
If you want it on a timer then you just need to set up a timer and when it expires you can run...
[self performseguewithidentifier:#"nextcontroller"]
As long as you have a segue with this identifier then it will work anywhere. Ever after a timer.

How do I catch when UIViewController displays after dismissViewControllerAnimated

I've got two UIViewControllers. I'm using modal segue to the second one, when coming back I use dismissViewControllerAnimated. I want to fire a method when I come back to the first one. How can I do that?
I tried to fire a custom notification before dismissViewControllerAnimated and catching it in the first UIViewController, but it doesn't catch anything, because it's still on the second one when it's fired.
There are easy options I can see.
Use the viewDidDisappear: method in the view you're dismissing.
dismissViewControllerAnimated:completion: method accepts a block that actually executes after viewDidDisappear executes in the dismissing view.
To pass a reference from one view controller to the next:
In the second view controller's .h file, add a property:
#property (nonatomic,strong) FirstViewController *firstVC;
In your first view controller, add the following method:
- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender {
if([#"segue_YOUR_SEGUE_NAME" isEqualToString:[segue identifier]]) {
if([[segue destinationViewController] isKindOfClass:
[SecondViewController class]]) {
SecondViewController *dest = (SecondViewController*)[segue
destinationViewController];
dest.firstVC = self;
}
}
}
Now, in your second view controller, you can do two things, as I already stated:
[self dismissViewControllerAnimated:YES
completion:^{
[self.firstVC someMethod];
}];
OR...
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.firstVC someMethod];
}

Passing data back and forth using AppDelegate

To start I am building an app to learn the basics of Objective-C. If there is anything unclear please let me know and I will edit my question.
The app is supposed to have the next functionality.
Open the camera preview when the app is executed. On the top there is a button to go to a TemplateController where the user can select an array of frames to select from a UICollectionView. User selects the Template and returns to the Camera Preview. User takes a picture and the picture with the frame selected is shown in the PreviewController. If the user doesn't like the frame and wants to switch it for another one. PreviewController has button on top to go to the TemplateController, select the frame and go back again to the PreviewController with the new frame.
I do not want to create an object for the frame everytime. I want the AppDelegate to hold that object. To keep it alive per say?(sorry, English is not my mother tongue).
I was thinking to use NSUserDefaults BUT I really want to do it using the AppDelegate. So at this point NSUserDefaults is not an option.
Now, I am using storyboards with a navigation controller. A screenshot is available here
Right now when I pass from the TemplateController to my PreviewController my code looks like this:
Reaching TemplateController from MainController or PreviewController
- (IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
Passing the data from TemplateController to its controller's destiny (Either MainController or PreviewController)
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
_selectedLabelStr = [self.frameImages[indexPath.section] objectAtIndex:indexPath.row];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
[self dismissViewControllerAnimated:YES completion:^{
if ([self.frameDelegate respondsToSelector:#selector(templateControllerLoadFrame:)])
{
[self.frameDelegate performSelector:#selector(templateControllerLoadFrame:) withObject:self];
}
}];
}
This loads the selected frame in PreviewController
- (void)templateControllerLoadFrame:(TemplateController *)sender
{
UIImage *tmp = [UIImage imageNamed:sender.selectedLabelStr];
_frameImageView.image = tmp;
}
My problem is, I don't have very clear what changes I have to do on the AppDelegate(it is untouched right now). What would be the best approach to accomplish this?
Main issue is when Tamplate is chosen before taking the still image. If I select the frame after taking the picture then it displays.
I am not certain that I understand your question. Stuffing an object into the app delegate solution may not be the best way forward. In fact I believe you ought to look at the delegation pattern that is used by Apple to communicate between view controllers. Please note that you appear to be doing half of the delegate pattern already. For example you make your PreviewController a frameDelegate of the TemplateController.
So I would think you'd have something like the following to transfer information from TemplateController back to the PreviewController. Note that I've included prepare for segue as that is a common pattern to push a data object forward (it will be called if you connect a segue from the PreviewController to the TemplateController and in your action method call performSegueWithIdentifier:#"SegueTitle"). Use of the "templateControllerDidFinish" delegation method is a common pattern used to push information back from TemplateController when it closes.
TemplateController.h
#class TemplateController;
#protocol TemplateControllerDelegate <NSObject>
-(void) templateControllerDidFinish :(TemplateController*)controller;
#end
#interface TemplateController : UIViewController
#property (nonatomic, weak) id <TemplateControllerDelegate>delegate;
...
#end
TemplateController.m
//! The internals for this method can also be called from wherever in your code you need to dismiss the TemplateController by copying the internal
-(IBAction)doneButtonAction:(id)sender
{
__weak TemplateController*weakSelf = self;
[self dismissViewControllerAnimated:YES completion:^{
[self.delegate templateControllerDidFinish:weakSelf];
}];
}
PreviewController.h
#import "TemplateController.h"
#interface PreviewController<TemplateControllerDelegate>
...
#end
PreviewController.m
#implementation
...
-(void) templateControllerDidFinish :(TemplateController*)controller
{
self.dataProperty = controller.someImportantData;
...
}
...
-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ( [[segue identifier]isEqualToString:#""] )
{
TemplateController *tc = [segue destinationViewController];
tc.delegate = self;
tc.data = [someDataObjectFromPreviewController];
}
}
To fix this situation a bit more:
Add a segue from the PreviewController to the TemplateController
(Ctrl-drag from Preview view controller to the Template Controller
in the document outline mode)
Name the segue identifier in the identity inspector
Change your code that presents the view controller from:
(IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
to
- (IBAction)showFrameSelector:(id)sender
{
[self performSegueWithIdentifier:#"SegueTitle"];
}
Add your data object to the target view controller as noted in prepareForSegue and you will be in good shape. Then use the delegate method to catch any data returned from your template (just add the data as properties to the controller and you should be golden)
You can see a better example of this delegation in a utility project template from Xcode (I just keyed this in..) I hope this information helps. You can get more information at these resources and also by searching Google and SO for iOS delegation :
Concepts in Objective C (Delegates and Data Sources)
Cocoa Core Competencies

Cannot access variable from another viewController

I have one viewController called "setTimeViewController". I have another view controller called "setEventViewController." users tap a row in a table in setTimeViewController and are sent to setEventViewController that only contains a UIPickerView and a save button. When the user taps the save button, it takes them back to setTimeViewController.
I want the value that is chosen in that picker from setEventViewController to be able to be accessed from setTimeViewController but is returning (null) to me. I have declared a NSString *theVariable in .h setEventViewController which is the variable I am trying to retrieve from the other view controller and retained its property but it is still null.
I have done a test (NSLog the variable) from viewDidDisappear in setEventViewController to see if it is null when the view is disappearing but it works as it should.
Here is my code, if anyone can help me I would forever be grateful. Thank you!
setEventViewController.m
- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
theVariable= [currentItemsInPicker objectAtIndex:row];
//[theVariable retain]; //Tried this as well but did not work
}
-(IBAction) Save
{
//simply dismisses the view controller back to setTimeViewController. Have also tried to set another NSString equal to theVariable but this did not work.
[self.navigationController popViewControllerAnimated:YES];
}
setTimeViewController
-(void) retrieveTheEvent
{
setEventViewController *eventViewController= [[setEventViewController alloc] init];
NSString *testString= eventViewController.theVariable;
NSLog (#"the event is %#", testString); //shows null
}
You are allocating different object of setEventViewController in retrieveTheEvent if I am not wrong. You are facing this problem because this newly allocated object is different than you have pushed.
Instead of use the same object that you have push to navigation controller.
One solution:
Create global object your setEventViewController(i.e. I mean create iVar for it) and use same reference to push view controller in didSelectRow. And use same iVar for accessing your theVariable.
Add below code in setTimeViewController.h
setEventViewController *eventViewController;
Please also create property for it.
Now in setTimeViewController.m
Now use existing reference of setEventViewController to push view controller. like
eventViewController= [[setEventViewController alloc] init];
[self.navigationController pushViewController: eventViewController animated:YES];
Change this method
-(void)retrieveTheEvent
 {
  NSString *testString= eventViewController.theVariable;
  NSLog (#"the event is %#", testString); //shows null
  }
Adding another solution to Armaan's list.
Create a delegate in setEventViewController.m and pass "theVariable" to setTimeViewController.m before calling
[self.navigationController popViewControllerAnimated:YES];
I'm giving an example.
setEventViewController.h
#protocol setEventViewControllerDelegate;
#interface setEventViewController : UIViewController
{
NSString* theVariable;
id<setEventViewControllerDelegate> delegate;
}
#end
#protocol setEventViewControllerDelegate <NSObject>
#optional
-(void)theVariableChanged:(NSString*)theNewValue;
#end
setEventViewController.m
#synthesize delegate;
- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
theVariable= [currentItemsInPicker objectAtIndex:row];
// this is where the new value is passed to setTimeViewController
if(delegate && [delegate respondsToSelector:#selector(theVariableChanged)])
{
[delegate theVariableChanged:theVariable];
}
}
-(IBAction) Save
{
[self.navigationController popViewControllerAnimated:YES];
}
-(void) dealloc
{
// very important
self.delegate = nil;
}
setTimeViewController.h
#import "setEventViewController.h"
#interface setTimeViewController : UIViewController <setEventViewControllerDelegate>
{
// your members
}
#end
setTimeViewController.m
-(void)openSetEventView
{
setEventViewController *eventViewController= [[setEventViewController alloc] init];
// set the delegate
eventViewController.delegate = self;
[self.navigationController pushViewController: eventViewController animated:YES];
[eventViewController release];
}
// get the new value here
-(void)theVariableChanged:(NSString*)theNewValue
{
NSLog (#"the event is %#", theNewValue);
}
Check out Singletons,
They can be your best friend when doing something like this.
Singletons link here
I tried all different suggestion to pass NSString from one viewController to the other, including customer initialise method, and change the property to strong, retain, non of them work for me.
At last singleton to share the data across the project fixed the problem. http://www.galloway.me.uk/tutorials/singleton-classes/
Thanks #David Evans

Passing variables (or similar) to newly loaded view

I am loading new views for a small iphone app, and was wondering how to pass details from one to another?
I am loading a tableview full of data from and xml file, then once clicked a new view is brought in via:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SubInfoViewController *subcontroller = [[SubInfoViewController alloc] initWithNibName:#"SubInfoView" bundle:nil];
[self presentModalViewController:subcontroller animated:YES];
[subcontroller release];
}
Next step would be to tell the newly loaded view which row had just been loaded?
Any idea, thoughts more than welcome, and please be gentle big newbie...
I typically create my own init method to do things like this. I think it would likely be better to pass in the corresponding "model" object represented by the tableView row, rather than the row number itself, like this:
In SubInfoViewController.h
#interface SubInfoViewController : UIViewController {
YourObject *yourObject;
}
#property (nonatomic, retain) YourObject *yourObject;
Then in SubInfoViewController.m:
- (SubInfoViewController*)initWithYourObject:(YourObject*)anObject {
if((self = [super initWithNibName#"SubInfoView" bundle:nil])) {
self.yourObject = anObject;
}
return self;
}
You'd create and present it this way:
// assuming you've got an array storing objects represented
// in the tableView called objectArray
SubInfoViewController *vc = [[SubInfoViewController alloc] initWithYourObject:[objectArray objectAtIndex:indexPath.row]];
[self presentModalViewController:vc animated:YES];
[vc release];
This could be adapted pretty easily to allow you to pass in any type of object or value (such as a row number if you still want to do that).
Add an instance variable to your view controller and declare a property corresponding to it, so after you alloc, init it, set it like subcontroller.foo = Blah Blah.