objective-c: Calling a void function from another controller - objective-c

i have a void, like -(void) doSomething in a specific controller.
i can call it in this controller via [self doSomething], but i don't know how to call this void from another .m file.
I want to call it in a -(IBAction) action:(id)sender
I've tried using performSelector, but i got a 'unreconiezd selector send' in the log.
I've no idea if i have to use the notification center, or delegate...
Thanks,
ronan.

You have to have an instance of the first controller (the one where you declare the function in) in your second controller.
FirstViewController *firstController = [[FirstViewController alloc] init];
[firstController doSomething];
If your first controller is declared somewhere else, and you want your second controller to know about it, have a property of FirstViewController type in your second controller, and initialize it when you need it with your FirstViewController instance.
Sometimes, this can be quite complicated to do if you have multiple controller instances and you want every one of them to know about all the others, so I would rather suggest rewriting your method to a class method:
+ (void) doSomething;
so you could call it from anywhere with
[FirstViewController doSomething];

if you have the object you want to call it on and the method is public, then you just:
[object doSomething];

The problem is that you have two controllers who don't know about each other's existence. The notification center certainly is the way to deal with this situation: the advantage is that you need not establish a formal connection between the two.
The delegate method is another possibility, but then you have to either establish the connection via an IBOutlet in Interface Builder, or you must have one controller create the other controller and pass itself as the delegate. This ties them together more closely, which may or may not be appropriate. If the only connection between the two controllers is the calling of one -(void) method, then I'd go with NSNotification.

Related

Correct method to present a different NSViewController in NSWindow

I am developing an app that is a single NSWindow and clicking a button inside the window will present a NSViewController, and a button exists in that controller that will present a different NSViewController. I know how to swap out views in the window, but I ran into an issue trying to do this with the multiple view controllers. I have resolved the issue, but I don't believe I am accomplishing this behavior in an appropriate way.
I originally defined a method in the AppDelegate:
- (void)displayViewcontroller:(NSViewController *)viewController {
BOOL ended = [self.window makeFirstResponder:self.window];
if (!ended) {
NSBeep();
return;
}
[self.box setContentView:viewController.view];
}
I set up a target/action for an NSButton to the AppDelegate, and here's where I call that method to show a new view controller:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self displayViewcontroller:newVC];
}
This does work - it presents the new view controller's view. However if I then click any button in that view that has a target/action set up that resides within its view controller class, the app instantly crashes.
To resolve this issue, I have to change didTapContinue: to the following:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self.viewControllers addObject:newVC];
[self displayViewcontroller:[self.viewControllers lastObject]];
}
First of all, can you explain why that resolves the issue? Seems to be related to the way the controller is "held onto" in memory but I'm not positive.
My question is, how do I set this up so that I can swap out views from within any view controller? I was planning on getting a reference to the AppDelegate and calling displayViewcontroller: with a new controller I just instantiated in that class, but this causes the crash. I need to first store it in the array then send that reference into the method. Is that a valid approach - make the viewControllers array public then call that method with the lastObject, or how should this be set up?
What is interesting in your code is that you alloc/init a new view controller every time that you call the IBAction. It can be that your view its totally new every time you call the IBAction method, but I would think that you only have a limited number of views you want to show. As far as my knowledge goes this makes your view only to live as long as your IBAction method is long. That the view still exists, is because you haven't refreshed it. However, calling a method inside a view controller that is not in the heap anymore (since you left the IBAction method and all local objects, such as your view controller are taken of the heap thans to ARC) makes the app crash, because you reference a memory space that is not in use or used by something else.
Why does the app work when you ad the view to the viewcontrollers array? I assume this array is an array that has been initiated in the AppDelegate and now you add the view controller with a strong reference count to the viewcontrollers array. When you leave the IBAction method, the view controller still has a strong reference and ARC will not deallocate the view controller.
Is this the proper way? Well, it works. I would not think it is considered very good programming, since you don't alloc/init an object in a method that needs to stay alive after leaving the method. It would be better practice to allocate and initialize your view controller(s) somewhere in an init, awakeFromNIB or a windowDidLoad method of your AppDelegate. The problem with your current solution is that you are creating an endless array of view controllers of which you only use the last. Somewhere your program will feel the burden of this enormously long array of pretty heavy objects (view controllers) and will run out of memory.
Hope this helps.
By the way, this is independent of whether you use Mavericks or Yosemite. I was thinking in a storyboard solution, but that wouldn't answer your question.
Kind regards,
MacUserT

How should I implement [almost] identical actions used throughout various VCs? (Answer: use a category)

Right now I am using the delegate, but I suspect it's bad form.
I programatically create an identical UIBarButtonItem in numerous, different view controllers.
My button's target is myAppDelegate, and its action is defined therein.
What is a better way of doing this? Or am I just supposed to copy and paste the identical action into no-matter-how-many view controllers that instantiate the identical bar button?
Okay, now suppose the action is identical in all respects but one: It varies only in that it should send a presentViewController message to the view controller that instantiated the button that sent the action. Thus, in the action, I can send a presentViewController message to sender, which is an instance of the button, but I know no means of reaching the view controller that instantiated that instance of the button.
Each view controller simply needs to set itself as the target of the button when the button is created. The button's action selector will then be sent to the correct controller.
As for the selector itself, create a category on UIViewController to contain the button's action:
#implemenation UIViewController (MKCBarButtonItemAction)
- (void)MKCDoThatThingIDo
{
// Do things.
[self presentViewController:[self theOtherViewControllerWhatINeedToDisplay]
animated:YES
completion:^{
// Do more things.
}];
// Do yet other things.
}
#end
If you've common methods for multiple view controllers defined in AppDelegate file, then you can make call to those actions using delegate object something like,
#define appDelegateObj (AppDelegate *)[UIApplication sharedApplication].delegate;
Where, AppDelegate is your delegate file class name, also define this macro at global so you can access it from anywhere,
Now where ever you want to call any function, just use like [appDelegateObj functionName];
Here's one thing, if you can make class methods into delegate file then, it can used with using class name, like [AppDelegate functionName]; 
class methods defined like +(void)functionName{...} and it can't access to class instance variables and methods.

The right way of setting up MapKit's delegate in a separate class

What is the proper way of setting up a separate delegate class for MapKit?
I have MapView class subclassing MKMapView and bare MapDelegate class conforming MKMapViewDelegate protocol having only one initializer method.
Here is the extract from MapView initialization method I use:
# MapView.m ...
#implementation MapView
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// [self setShowsUserLocation:YES];
[self setDelegate:[[MapDelegate alloc] initWithMapView:self]];
The only method MapDelegate class has is
# MapDelegate.m ...
- (id)initWithMapView:(MapView *)aMapView {
self = [super init];
self.mapView = aMapView;
return self;
}
Having [self setShowsUserLocation:YES]; commented, all works fine - I see the map. If I uncomment this line, my application begins to crash.
What my MapDelegate class is missing?
UPDATE 1: if I don't use a separate class MapDelegate and set just setDelegate:self - all works.
UPDATE 2: Now I understand, that the problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that I need MapDelegate class to live longer than it does now (delegate property has weak attribute). If I do the following:
#property (strong) id delegateContainer;
....
[self setDelegateContainer:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateContainer];
...it works! Is there a better way of retaining MapDelegate life cycle along with the one of MKMapView?
Thanks!
After waiting enough for any answers that could appear here and ensuring original problematic behavior twice more times, I am posting my own answer based on the second update from the question:
The problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that MapDelegate class should be able to be kept alive outside of the scope of question's initWithFrame method because delegate property has weak attribute. The possible solution is to create an instance variable serving as a container for a delegate class, for example:
#property (strong) id delegateClass;
....
[self setDelegateClass:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateClass];
This solves the original problem.
LATER UPDATE
Though it is possible to set MKMapView's delegate in a separate class, I now realize that such model should not be used:
Currently I always prefer to use my controllers (i.e. controller layer in MVC in general) as delegates for all of my View layer classes (map view, scroll view, text fields): controller level is the place where all the delegates of different views can meet - all situated in controller layer, they can easily interact with each other and share their logic with the general logic of your controller.
On the other hand, if you setup your delegate in a separate class, you will need to take additional steps to connect your separate delegate with some controller, so it could interact with a rest part of your logic - this work have always led me to adding additional and messy pieces of code.
Shortly: do not use separate classes for delegates (at least view classes delegates provided by Apple), use some common places like controllers (fx for views like UIScrollView, MKMapView, UITableView or models like NSURLConnection).
I think viewDidLoad would be a better place to set up the map view. It's just a guess, but perhaps the crash is due to the view not being loaded yet.
Of course subclassing MKMapView isn't recommended at all. You would generally put your map as a subview, and set the main view to be the delegate. From the docs:
Although you should not subclass the MKMapView class itself, you can get information about the map view’s behavior by providing a delegate object.
Finally, if you really want to have a separate delegate class, you don't need to set its mapView, as all delegate methods pass the map as an argument.

Update UI from another Class Method - Cocoa

I would like to update the UI in my application from the AppDelegate, but whenever I call it as so:
Controller *object = [[Controller alloc] init];
[object methodHere];
It doesn't seem to update the UI. What am I doing wrong here? I have put in a NSLog to see if it was being called, and it is. Here is a sample project that shows the error.
Edit: Can someone just show me what to change to the project I provided. I just don't know what to type into my project so that I can change the value of a simple NSTextField from another class.
When you write [[Controller alloc] init], you are not accessing the Controller object that is in your nib. You are creating a new Controller object that is unconnected to anything else in your application.
Remember, every Controller object is not the same any more than every NSArray is the same. Just because you made one Controller in your nib that's connected to an NSTextField does not mean some random Controller that you just created shares that controller's connections.
What you need to do is give the delegate a reference to the Controller that's in the nib.
This is really simple, and Chuck's comments basically explain what you need to do, but I will lay out the code explicitly for you. In testAppDelegate.h:
#interface testAppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
// You can make an IBOutlet to any kind of object you
// want; it's just a way for you to get a reference
// in code to an object that has been alloc'd and
// init'd already by the xib mechanism.
IBOutlet Controller *controller;
}
Then go into your xib in InterfaceBuilder and hook up that outlet from your Test App Delegate object to your Controller object (these objects are already present in the xib).
In testAppDelegate.m:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// This is the key:
// _Don't_ alloc/init a new controller object. The
// objects in your xib are allocated and initialized
// by virtue of being in that file. You just need to
// give your AppDelegate a pointer to it, as above.
[controller setTextValue:#"hello"];
}
It's being called all right, but it's not connected to the interface. There should be a view controller of some sort defined in your appDelegate.h file, call the method on that object instead.
Update for more detail:
One way you could pull this off would be to simply save the Controller when you originally create it (and not release it until later.)
Simply put your own controller object into your .h file
Controller* myController;
And when you create the new view controller you want to flip to, simply set myController to reference that object, and later when you want to update the UI, simply call
[myController methodHere];
A bit clumsy, but it works. Just don't forget to release myController when you're done with that view.
The other idea I'd suggest looking into would be to alter the method you're passing to your delegate. That is, instead of having the method as
-(returnType)callDelegateToDoSomething;
put it in as
-(returnType)callDelegateToDoSomething:(id) sender;
You call the new method the same way, but your controller should automatically pass itself as an argument. Then, inside the method, simply use
[sender methodHere];
and it should hopefully work. (You may need to play around with it a little. I'm not an expert on delegates or the sender argument, but it's worth a shot.)

Class instances differ, which one to use?

I'm stuck with the following. In a program, I'm trying to communicate between different classes (View Controllers with NIB files attached in a TabBar application etc). I want to call a method 'OMFG' in a class called 'ProductViewDetailController'. This class is a UIViewController (SplitViewDelegate). It's loaded programmatically.
Anyways, I've been trying to get the right call to this controller, and I came up with 2 solutions. One is declaring the productviewdetailcontroller in the caller's .h file and .m file, making an IBOutlet, linking it in the Interface builder and calling it directly by the line
[productDetailController OMFG];
When I call this method, it calls the right method in the ProductViewDetailController, but the instance of this viewcontroller differs from the one I programmatically can reach with this code:
for (UIViewController *controller in self.tabBarController.viewControllers) {
NSLog(#"%#", [controller class]);
if ([controller isKindOfClass:[UISplitViewController class]]) {
UISplitViewController *cell = (UISplitViewController *)controller;
for (UIViewController *controller2 in cell.viewControllers) {
NSLog(#"%#", [controller2 class]);
if ([controller2 isKindOfClass:[ProductViewDetailController class]]) {
[controller2 OMFG];
}
}
}
Which one should I use, and why?
edit: When I try to add a SubView to both viewcontrollers, the one where the call is [controller2 OMFG]; actually shows the newly added view, where the [productDetailController OMFG]; doesn't show the newly added view... Why is that? Is there a shorter (and more chique) way to get access to the right ViewController?
You should use a IBOutlet. This makes sure your app can still call the correct target if you later decide to change the hierarchy of view controllers, for example if creating an iPhone compatible setup without a UISplitViewController.
Calling isKindOfClass: in Objective-C is a sure sign that what you are doing is probably wrong. Firstly in Cocoa Touch what you do is always more important than who you are. Secondly what you try to do is probably peeking inside something that should be left private.