Object becomes Nil when pushing controller - objective-c

I have a the first application controller, MAViewControllerMenu, and when that controller loads, I already allocate the next controller, imageControllerView.
- (void)viewDidAppear{
[super viewDidAppear:(YES)];
if (!imageControllerView)
imageControllerView = [[self storyboard] instantiateViewControllerWithIdentifier:#"chosenImageController"];
}
Then, I select an image from the image picker, and want to move to the next controller,imageControllerView, where the image would be displayed. I set the next window's image property as follows:
imageControllerView.image = [[self.pageViews objectAtIndex:(centered_image_ind)] image];
This line works, I checked that there's a value in imageControllerView.image.
However, when I move to the next controller,imageControllerView , I notice that the memory address of imageControllerView changes, or in other words, imageControllerView's properties that I change before moving to that controller, specifically image, reset when I move there.
Instead of throwing code here, I was hoping you could let me know what I should provide.
I think it's a common problem people know of:
Controller's objects re-init'ing when moving from one controller to another.
Here's a screen shot that might give a hint of what Im trying to do
Left most one is where I select pictures which in turn go into the slide show scrollview. Then I click next, and the image is supposed to appear in the centered ImageView
Thanks

OK...
You cannot "already allocate the next view controller" this won't work. There is no point in creating it like this at all. You can delete the imageViewController property (or iVar) completely.
The arrows that you have between the view controllers in your storyboard are segues. In Interface Builder you can select a segue and give it an identifier. For instance you would use something like #"ImageViewSegue".
I guess the segue is attached to the Next button. This is fine.
Now, in your MAViewControllerMenu you need to put this method...
- (void)prepareForSegue:(UIStoryBoardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ImageViewSegue"]) {
// the controller is CREATED by the segue.
// the one you create in view did load is never used
ImageViewController *controller = segue.destinationController;
controller.image = [[self.pageViews objectAtIndex:(centered_image_ind)] image];
}
}
Now for the segues in the other direction...
You appear to be using segues to dismiss the modal views. You can't do this. What it will do is create a new view controller and present that instead of dismissing the presented view.
i.e. you'll go...
A -> B -> C -> B -> A -> B
// you'll now have 6 view controllers in memory
// each segue will create a fresh view controller with no values set.
What you want is...
A -> B -> C
A -> B
A
// now you only have one view controller because the others were dismissed.
// when you dismiss a view controller it will go back to the original one.
// the original one will have all the values you set previously.
To do this you need to create a method something like...
- (IBAction)dismissView
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Then whatever the button is for your dismiss action attach it to this method.
Now delete all the backwards curly segues.
Passing info back
To pass info back to the original view controller you need a delegation pattern or something similar.
You can read more about creating a delegate at This random Google Search
Create a delegate method something like...
- (void)imageViewSelectedImage:(UIImage *)image;
or something like this.
Now when you do prepareForSegue you can do...
controller.delegate = self;
and have a method...
- (void)imageViewSelectedImage:(UIImage *)image
{
// save the method that has been sent back into an array or something
}

I might be wrong, but seems you go to your second view controller using a segue, it is normal your controller instance isn't the same than the one retrieved by [[self storyboard] instantiateViewControllerWithIdentifier:#"chosenImageController"]
you should take a look at - (void) prepareForSegue:(UIStoryboardSegue *)segue
(UIViewController method)
inside this method set your image property to the segue destination controller (check the identifier of the segue)

Related

Pass variable to view controller before viewDidLoad

I perform a segue which is defined in the Storyboard to open a new view controller. I need to configure the destination view controller of the segue in a special state where some of it's buttons does not needed to be displayed.
I know that I can do this by setting a variable on this controller in my source view controller's -prepareForSegue:sender:. The problem with this is, that firstly it instantiates the controller, so it's -viewDidLoad: will run, then only after can I set anything on it.
I can't create the controller entirely from code, because it's user interface is in Storyboard. -instantiateViewControllerWithIdentifier: also calls -viewDidLoad first obviously.
I could probably use a semaphore and add the initialization code into my destination controller's -viewWillAppear, but that's ugly, it has to be some more elegant way to do this than doing a check every time the view appears. (My settings need to be done only once.)
Is there some way to pass variables into the controller before it's -viewDidLoad runs?
EDIT: It looks like this happens only if I trigger the segue from code using -performSegueWithIdentifier:.
On my machine and on iOS 8.0 and iOS 9.0, viewDidLoad is called after prepareForSegue. So, something like the following worked for my test case of your answer.
In your source controller:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
TimViewController * controller = segue.destinationViewController;
if( [controller isKindOfClass:[TimViewController class]] )
controller.name = #"tim";
}
In your destination controller (TimViewController):
- (void)viewDidLoad
{
[super viewDidLoad];
// Do view setup here.
NSLog( #"view did load %#", self.name );
}
Add a segue (a show segue) from your source control to the destination view controller.
Output:
2015-09-17 19:09:04.351 Test[51471:7984717] view did load tim
I think there is some confusion here. -prepareForSegue:sender: gets called before -viewDidLoad gets called. Please double check your implementation.
Edit:
May be this thread will help you understand this and one of the mentioned cases fall in your case.

Passing NSIndexPath to New View

I am having real troubles passing an NSIndexPath to my new view. This is how the app works:
I have a UIBarButtonItem in my nab br, tap that and you get a popover view, this shows a bunch of stuff. I need to get an NSIndexPath from my main view, to this popover view.
I have a property for the NSIndexPath in my popover view class and the popover transition is connected up in my storyboard.
Then I have this code to pass the index path across views:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"statsPopover"])
{
StatsViewController *statsVC = [segue destinationViewController];
statsVC.selectedIndex = stageSelectionTable.indexPathForSelectedRow;
}
}
However, while this gets called, the index path isn't actually sent between views. My index path on the popover is always the default, 0,0 row and section.
You say you know from debugging/logging that the method is running, your if statement is triggered, and stageSelectionTable.indexPathForSelectedRow has the value you expect.
Doing that kind of diagnosis puts you on the right path to solving your issue. Keep at it -- if you test some other things with NSLog or the debugger you should be able to find the problem.
Do you know that statsVC is non-nil? If it's nil, your message to set selectedIndex does nothing.
Is statsVC the class you expect? If, say, the (table?) view in your popover is embedded in a navigation controller, segue.destinationViewController will point to the nav controller, and you'll need to look into its child view controllers array to find the one you're looking for.
Is whatever accessor method your StatsViewController class is using for its selectedIndex property working right? (This shouldn't be a problem if it's a synthesized accessor for a strong/retain property.)
In your StatsViewController class, are you trying to work with selectedIndex before it actually gets set? Setting a property in prepareForSegue:sender: and then using it in viewDidLoad should work fine, but using it in awakeFromNib might not, and using it in an init... method definitely won't.

prepareForSegue is not called after performSegue:withIdentifier: with popover style

I have a universal app, where I am sharing the same controller for a IPad and IPhone storyboard.
I have put a UILongPressGestureRecognizer on a UITableView, that when a cell is pressed on iPhone it calls an action that perform a segue:
-(IBAction)showDetail:(id)sender {
UILongPressGestureRecognizer *gesture = (UILongPressGestureRecognizer*)sender;
if (gesture.state == UIGestureRecognizerStateBegan) {
CGPoint p = [gesture locationInView:self.theTableView];
NSIndexPath *indexPath = [self.theTableView indexPathForRowAtPoint:p];
if (indexPath != nil) {
[self performSegueWithIdentifier:SEGUE_DETAIL sender:indexPath];
}
}
}
the segue is a detail view performed as a 'push'. The first thing you should notice is that the sender is an NSIndexPath, is the only way I found for passing the selected cell. Maybe there's a better solution.
Everything works fine, in a sense that the segue is performed, and before the prepareForSegue is called too.
However it happens that on iPad, I have changed the segue identifier to Popover.
Now things are working in part, the segue is performed, but prepareForSegue is not called and therefore the destination view controller is not set up as it should be.
What am I doing wrong ?
What I have discovered so far, is that with any segue identifier that is not popover these are the invocations made by iOS:
prepareForSegue (on source controller)
viewDidLoad (on destination controller)
while in popover segue the invocation order is:
viewDidLoad (on destination controller)
prepareForSegue (on source controller)
just because I put all my logic in viewDidLoad, the controller was not properly initialized, and a crash happened. So this is not exactly true that prepareForSegue is not called, the truth is that I was getting an exception, and I wrongly mistaken as prepareForSegue not getting called.
I couldn't put everything in viewWillAppear because a call to CoreData had to be made and I didn't want to check if entities were ok each time the view display.
How did I solve this ? I created another method in destination controller
-(void)prepareViewController {
// initialization logic...
}
and changing the prepareForSegue method in source controller itself:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
MyViewController *mvc = (MyViewController*)[segue destinationViewController];
// passing variable
// with segue style other than popover this called first than viewDidLoad
mvc.myProp1=#"prop1";
mvc.myProp2=#"prop2";
// viewWillAppear is not yet called
// so by sending message to controller
// the view is initialized
[mvc prepareViewController];
}
don't know if this is expected behavior with popover, anyway now things are working.
I've noticed that the boiler plate code for Xcode's Master-Detail template (iPhone) uses the following pattern for configuring the detail VC's view:
the detail VC's setters (for properties) are overwritten in order to invoke the configureView method (configureView would update all your controls in the view, e.g., labels, etc.)
the detail VC's viewDidLoad method also invokes the configureView method
I did not follow this pattern the other day when I was trying to re-use a detail VC in my movie app, and this gave me trouble.
I don't have much experience with popovers; however, if the pattern above is used with a detail VC that is displayed inside a popover, then wouldn't the detail VC's view get configured when you set the detail VC's properties from within the prepareForSegue method?

presentModalViewController on Parent from UITableView inside UIViewController

This one is probably something simple, still learning the ins-and-outs on this but I've run out of searches for this one with no available answer.
I've got a UIViewController with several elements displayed on it, one such element is a UITableView. The UITableView has it's own class and is allocated in the UIViewControllers viewWillAppear
- (void)viewWillAppear:(BOOL)animated
{
UITableView *insideTableView = [[UITableView alloc] init];
tableView.delegate = insideTableView;
tableView.dataSource = insideTableView;
}
Everything is working fine in regards to the tableview. Today I am experimenting with a few additions, one of which is a new view popup on cell selection within that tableview.
Inside my TableView Class, I have the following:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Cell Pressed, Present View");
PopupView *popupView = [[PopupView alloc] initWithNibName:#"PopupView" bundle:nil];
popupView.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:popupView animated:YES];
}
Now it gets called fine, verified by the NSLog, however the view doesn't appear. I know the problem is related to the fact that I want PopUp to appear over the TableViews Parent rather than itself.
I'm just not sure how to properly call it in this instance.
The delegate is a UIViewController which doesn't have its view property set, which is why presentModalViewController:: doesn't work.
You need the view controller containing the table view to present the modal view controllers, but note that that view controller is not the parent of the table view delegate. This is because you have no view controller hierarchy in place.
The easiest way to fix this is to put those methods inside the view controller whose view contains the table view. Alternatively the table view delegate needs to hold a reference to the view controller so it can call presentModalViewController:: on it.
The latter approach can lead to retain cycle, so you have to use a non-retaining reference. The nicest implementation is the delegate pattern.
Also, you don't want to do the instantiation in viewWillAppear: because that can be called multiple times during the lifecycle of a view controller. Put the code in viewDidLoad and balance it in dealloc. Right now you are leaking memory every time your view appears, which when your modal view controller is working will be every time the modal view controller is presented and dismissed.

Reload data from table view after come back from another view

I have a problem in my application. Any help will be greatly appreciated. Basically it is from view A to view B, and then come back from view B.
In the view A, it has dynamic data loaded in from the database, and display on the table view. In this page, it also has the edit button, not on the navigation bar. When user tabs the edit button, it goes to the view B, which shows the pick view. And user can make any changes in here. Once that is done, user tabs the back button on the navigation bar, it saves the changes into the NSUserDefaults, goes back to the view A by pop the view B.
When coming back to the view A, it should get the new data from the UIUserDefaults, and it did. I used NSLog to print out to the console and it shows the correct data. Also it should invoke the viewWillAppear: method to get the new data for the table view, but it didn't. It even did not call the tableView:numberOfRowsInSection: method. I placed a NSLog statement inside this method but didn't print out in the console.
As the result, the view A still has the old data. the only way to get the new data in the view A is to stop and start the application.
Both view A and view B are the subclass of UIViewController, with UITableViewDelegate and UITableViewDataSource.
Here is my code in the view A :
- (void)viewWillAppear:(BOOL)animated {
NSLog(#"enter in Schedule2ViewController ...");
// load in data from database, and store into NSArray object
//[self.theTableView reloadData];
[self.theTableView setNeedsDisplay];
//[self.theTableView setNeedsLayout];
}
In here, the "theTableView" is a UITableView variable. And I try all three cases of "reloadData", "setNeedsDisplay", and "setNeedsLayout", but didn't seem to work.
In the view B, here is the method corresponding to the back button on the navigation bar.
- (void)viewDidLoad {
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:#selector(savePreference)];
self.navigationItem.leftBarButtonItem = saveButton;
[saveButton release];
}
- (IBAction) savePreference {
NSLog(#"save preference.");
// save data into the NSUSerDefaults
[self.navigationController popViewControllerAnimated:YES];
}
Am I doing in the right way? Or is there anything that I missed?
When a view is first loaded, it calls the viewDidLoad method. If you create a stack, drill down into it (from A to B) and then return (B to A) the viewDidLoad does not get called again on A. What you may want to try is passing A into B (by passing in self) and call the viewDidLoad method to get the new data and then reloadData method on the the tableView to refill the table view.
What you may want to try is taking the data fetching and setting functionality out of the viewDidLoad method and place it in its own getData method. At the end of the getData method, you could place a [self.tableView reloadData]; to reset/refill the table view. From class B, you could call [self getData] and minimize the amount of work you would do in class B. This would help increase reuse-ability of that code and may prevent side effects from calling the viewDidLoad method.
You could also use viewDidAppear. It is called every time the screen appears. For performance reasons, set a flag so you don't repeat the same functionality in viewDidLoad with viewDidAppear for the first screen view.