Pass a reference to ViewController in prepareForSegue - objective-c

I have 2 view controllers, call them ViewController1 and ViewController2. A modal segue is invoked from ViewController1 when I want to load ViewController2. I have a method in ViewController1 that needs to be called at some point when ViewController2 is showing. My idea is to have a property in ViewController2 that is a reference to ViewController1 so that I can get access to the method.
#property (strong, nonatomic) ViewController1 *vc1Reference;
This property would be set in the prepareForSegue method like so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // 1
if ([[segue identifier] isEqualToString:#"sequeToVC2"]) { // 2
ViewController2 *vc2 = segue.destinationViewController; // 3
vc2.vc1Reference = (ViewController1*)segue.sourceViewController; // 4
}
}
However line 4 gives me this error: Implicit conversion of an Objective-C pointer to 'int *' is disallowed with ARC.
How am I supposed to set the reference?

You are on the right track, but the correct way to do this is to use a delegate.
You declare a delegate property in your vc2 #interface:
#property (nonatomic, weak) id <vc2delegate> delegate //[1] (in vc2.h)
And you set the delegate in your prepareForSegue:
vc2.delegate = self; //[2] (in vc1.m)
('self' is the correct reference for vc1, from vc1)
In vc2 you define a protocol, which is the method that you expect vc1 to respond to from vc2. Put this in vc2.h, above your #interface
#protocol vc2delegate //[3] (in vc2.h)
- (void) delegateMethod;
#end
Then you have to ensure you implement that method in vc1. Also you need to let vc1 know to conform to the delegate. Import vc2 into vc1.h, and on your #interface line in vc1 add the protocol name in angle brackets:
#import vc2.h
#interface vc1 <vc2delegate> //[4] (in vc1.h)
This arrangement allows vc2 to pass a method to vc1 without having to #include vc1 or know anything else about it.
more detail...
This is the correct form of your
#property (strong, nonatomic) ViewController1 *vc1Reference;
Note the use of weak reference. You don't want to make a strong reference as you don't really want to have anything to do with the delegate except to know it can handle methods you specify in your protocol. The delegate is often the object that created the delegator, creating a strong reference back in the other direction can cause memory leaks as neither object can go out of existence.
this is the correct form of your line:
vc2.vc1Reference = (ViewController1*)segue.sourceViewController;
Note that we are NOT using type/casting in 1 or 2. For maximum code reuse/decoupling we dont want to make any suppositions on the type of object at either end of the segue.
I am assuming that your 'prepareForSegue' is in vc1. If it is not then the line would look like this:
vc2.delegate = segue.sourceViewController
This is the protocol declaration. It goes in the header file for vc2. vc2 is publishing it's expectations of any object that chooses to become its delegate. vc2 will be sending messages according to this protocol so any delegate needs to respond in the correct way. You can guard against failure in vc2 by using this kind of message-passing
if (self.delegate respondsToSelector:#selector('delegateMethod')
{
[self.delegate delegateMethod];
}
(that is an example of the kind of message passing you would use in vc2 to communicate back to vc1. you can obviously pass paremeters and get returned results back if need be)
this is a helper for the compiler which can issue you with warnings if you fail to implement the protocol.
Finally somewhere in your object definition you need to implement the method:
- (void) delegateMethod
{
// someaction;
}

I ran into something similar the other day. I ended up creating a delegate for vc2, and using
vc2.delegate = self;
in the segue instead. Would this solve your problem? If you need help setting up the delegate, let me know and I'll do my best to help!

Just add a delegate to your the ViewController that you are accessing the delegate on, XCode / Obj-C seems to do the right thing afterwards.
Before:
#interface ViewController2 : UIViewController
#end
After:
#interface ViewController2 : UIViewController
#property (nonatomic, weak) id <UIPageViewControllerDelegate> delegate;
#end

Related

setHidden with NSSlider doesn't work - Objective C

Hy guys, I'm new at ObjC and I'm still learning;
[sliderContrast setHidden:YES] (i also used slider.hidden = YES) doesn't make the slider invisible, instead it works fine with textfields. Do you know why?
I've also tried using property and synthesize but the result doesn't change
---Interface
#interface Controller : NSWindowController{
IBOutlet NSTextField *labelContrast;
IBOutlet NSTextField *valueContrast;
IBOutlet NSSlider *sliderContrast;
}
- (IBAction)changeContrast:(id)sender;
#end
---Implementation
#import "Controller.h"
#interface Controller ()
#end
#implementation Controller
- (void)windowDidLoad {
[super windowDidLoad];
[labelContrast setHidden:YES];
[valueContrast setHidden:YES];
[sliderContrast setHidden:YES];
}
- (IBAction)changeContrast:(id)sender {
}
#end
If you declare pointers for your objects but you don't allocate them yourself you can not set anything that is not there. Your setHidden: method calls end up in local void aka nowhere.
programmatically
If you go the coding way you would declare, allocate and initiate first. With
labelContrast = [NSTextField alloc] initWithFrame:NSMakeRect(x,y,w,h)];
before you call other methods of the object class (Or similar init methods).After that you can call methods on the object.
Almost all objects inherit an according -(instancetype)init or -(instancetype)initWith... method you can use. If there is no init method given, then there is another way to do it right and the moment to start reading again :).
With Interface Builder
By typing IBOutlet or IBAction in front of a declaration you just give a hint for Xcodes Interface Builder where to hook up and apply onto (associate) the placed object in (nib,xib,storyboard) with its object ID in the XML scheme to a reference in code.
So after you connected your code and the object in IB you can avoid allocation and init process for that particular object. Be aware that calling methods on objects that are not instanced yet is not working. Which is why you code in - (void)windowDidLoad, -(void)viewDidLoad or -(void)awakeFromNib because those are the methods that get called after "IB" has done its job for you.

Simple passing of data through delegation in objective C

I'm using Xcode to write an app in objective c. I am trying to pass data from a container view controller to the parent view controller using delegation. I have successfully passed the data to the parent view controller, but all of the documentation sets what I have sent to the .h header file in the .m implementation file using viewDidLoad or viewDidAppear. I was wondering, since the view is already present, if there is a way to detect that data has been changed in a view and automatically run a method or code to update the view with the new information. Something along the idea of didReceiveNewData or didEditExistingValues (of course those arent real methods). Thank you for your help!
Edit: What I have done so far:
I want to pass the data from MainFeedTableViewController to MainFeedViewController (The first is in a container inside of the second). I want to set the title of the custom navigation bar in MainFeedViewController to something described in the MainFeedTableViewController.
In the MainFeedTableViewController.m (the view sending data) I have:
#import "MainFeedTableViewController.h"
#import "FeedViewController.h"
#interface MainFeedTableViewController ()
#end
#implementation MainFeedTableViewController
- (IBAction)swipeLeftDetected:(UIGestureRecognizer *)sender {
UIStoryboard *mc = self.storyboard;
FeedViewController *fv = [mc instantiateViewControllerWithIdentifier:#"FeedViewController"];
fv.navigationBarTitleToSet = #"HOPING TO SET TITLE TO THIS";
[self performSegueWithIdentifier:#"MainToLocalFeed" sender:self];
}
and some other unrelated stuff..
In the MainFeedTableViewController.h I have:
#import <UIKit/UIKit.h>
#interface MainFeedTableViewController : UITableViewController
#end
In the MainFeedViewController.m (the one receiving the data) I have:
#import "FeedViewController.h"
#interface FeedViewController () <UINavigationBarDelegate>
#property (weak, nonatomic) IBOutlet UINavigationBar *navigationBar;
#end
#implementation FeedViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)setNavigationBarTitle:(NSString *)navigationBarTitle{
self.navigationItem.title = navigationBarTitle;
}
And in the MainFeedViewController.h I have:
#import <UIKit/UIKit.h>
#interface FeedViewController : UIViewController
#property NSString *navigationBarTitleToSet;
#end
I want to run the setNavigationBarTitle method with either data from the .h (navigationBarTitleToSet) or just from the sending view controller, if possible to run a method with delegation. Thanks a ton and I hope this is possible :)
It turns out I needed to add a second navigation bar to account for the container view, allowing me to navigate around the current stack with the parentViewController method and then navigationItem.title. For anyone who happens to find this with a container, make sure you add one immediately after the embed segue. I'm still not sure if you can use methods through delegation, but I can't ponder any situations where it would be necessary anymore, due to viewDidLoad. Thanks to #Tander for the help!

Communication between 2 ViewController via Protocol

From the AppDelegate I'm communicating with MyViewController via a Protocol. So when this method gets called in AppDelegate.m:
- (void)thisMethodGetsCalled:(CustomData *)data {
//Do stuff
//Then call method via Protocol
[_exampleDelegate exampleMethod:data];
}
It calls this method in MyViewController.m
- (void)thisMethodGetsCalledInsideViewController:(CustomData *)data {
//Do stuff with data
}
//ExampleDelegate.h
#import <Foundation/Foundation.h>
#protocol SMMessageDelegate <NSObject>
- (void)thisMethodGetsCalledInsideViewController:(CustomData *)data;
#end
Everything works fine and as predicted "thisMethodGetsCalledInsideViewController" gets called after "thisMethodGetsCalled". Say MyViewController has never been instantiated then "thisMethodGetsCalledInsideViewController" never gets called. However MyViewController is instantiated and later dismissed with:
[self dismissViewControllerAnimated:YES completion:nil];
my app crashes when "thisMethodGetsCalled" is called. It tries to call "thisMethodGetsCalledInsideViewController" but that method resides inside MyViewController which is dismissed. Does anybody know how to fix this?
First thought:
Somewhere you're setting MyViewController as the delegate of the object that it conforms to the protocol of?
You have to either:
set the delegate value to nil when MyViewController is dismissed, or
set the delegate property to be a weak reference, i.e.
#property (nonatomic,assign) id<TheProtocol> delegate;
Hope that helps.

Setting a property from another property's setter from segue Objective-C

For reference, I'm trying to learn Objective-C through the Stanford iTunes-U course. I wanted to update one property from the setter of another (they are inherently connected. Also, is that bad style?). The property I am trying to update is a UILabel, but it doesn't work like I thought.
This code is in one of my view controllers:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
[segue.destinationViewController setProgram:self.brain.program];
}
Naturally, this code calls the setter for the Program property of the incoming viewController. Here's that setter code:
-(void)setProgram:(id)program {
_program = program;
self.title = [CalculatorBrain descriptionOfProgram:program];
[self.graphview setNeedsDisplay];
self.testLabel.text = #"Trying to update your text!";
}
and the header file:
#import <UIKit/UIKit.h>
#interface GraphViewController : UIViewController
#property (nonatomic, strong) id program;
#property (weak, nonatomic) IBOutlet UILabel *testLabel;
#end
Now, when this code is run, as the segue happens, the new view DOES have its title changed (the self.title line works). However, the UILabel DOES NOT get updated. Furthermore, if I call the setter again say in viewDidLoad, it does change the UILabel. Is this because of self being updated? What's going on here?
In one of the lectures, the professor explains that outlets aren't set yet in prepareForSegue. This means testLabel is nil, and you're sending a message to nil (which is allowed and doesn't crash your app). To solve your problem, set the textLabel's text in viewDidLoad or viewWillAppear.
I believe this is "fixed" in iOS 6, but it won't be backwards compatible, so if you want to support iOS 5 still, you'll have to use viewDidLoad or viewWillAppear to update outlets (which I think is better to do anyway).

Passing delegate to a viewcontroller through a viewcontroller

I have three viewControllers in my storyboard and three viewController classes for each of them. From my main viewController, I am opening a navigation viewController in a 'modal' type segue, which is a multi step form and has two views in it. When the user hits 'Finish' on the last (which is second) view, the modal is dismissed and user is back to the main screen.
I am doing this with delegates. and the code for the finish button is also in a delegate and is placed on the main viewController's implementation file. In achieving this I passed the delegate from main view to the navigation's first view, and then from the first view on clicking 'next', I passed the delegate to the second (last) view (which has the finish button).
the passing of delegate from main to navigation's first page is like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"CreateCast"])
{
UINavigationController *navigationController = segue.destinationViewController;
CreateCastStepOneVC *createCastStepOneVC = [[navigationController viewControllers] objectAtIndex:0];
createCastStepOneVC.delegate = self;
}
}
the passing of delegate from navigation's first view to second view is like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ToCastStepTwo"])
{
CreateCastStepTwoVC *createCastStepTwoVC =
segue.destinationViewController;
createCastStepTwoVC.delegate = delegate;
}
}
Things are done well and delegate is doing its job as required. But a warning pops up which is a concern:
Passing '_weak id' to parameter of
incompatible type 'id'
Property declaration in first navigation view is like this:
#property (nonatomic, weak) id <CreateCastStepOneVCDelegate> delegate;
Property declaration is second navigation view is like this:
#property (nonatomic, weak) id <CreateCastStepTwoVCDelegate> delegate;
How have you declared the delegate property on CreateCastStepTwoVC? Also,
are your delegates conforming to a protocol you have defined?
A typical declaration for a delegate property would look something like this:
#property (nonatomic, __unsafe_unretained) id<MyProtocol> delegate;
or if you're not using a protocol (not recommended):
#property (nonatomic, __unsafe_unretained) id delegate;
EDIT:
Having seen your property declarations, you need to change weak to __unsafe_unretained as per this question: Recommended way to declare delegate properties with ARC
You can subclass UINavigationController and add a custom protocol in your subclass. With this approach you will be able to call your delegate from all your view controllers inside your navigation controller. For example, this is the way I used to do that:
#class CustomNavigationController;
#protocol CustomNavControllerDelegate <NSObject>
- (void)editImageController:(CustomNavControllerDelegate *)controller
didFinishPickingMediaWithInfo:(NSDictionary *)info;
- (void)editImageControllerDidCancel:(CustomNavControllerDelegate *)controller;
#end
#interface CustomNavigationController : UINavigationController
#property (nonatomic, weak) id <UINavigationControllerDelegate, CustomNavControllerDelegate> delegate;
#end
In this example I've implemented a similar functionality to a UIImagePickerController. In fact, this is the way the picker is implemented if you look at it's declaration file.