Basic concept: communicating between two views? - objective-c

How do i send information between two views (and hence, two classes)? Am I looking for my app delegate? Is there a better or alternative way?

If you want to send information back, you can use target-action (the way UIControl does), or you can send NSNotifications, or use a generic delegate protocol. Unless this is information of use throughout your application, putting it into your app delegate may be overkill.

I would use the Application Delegate. Or, if one view owns the other, you can initialize them together and keep the main reference to it in the class.
I always find it useful to have a global Context object to keep global information among views. This information could be, configuration information, device current orientation, database handlers, etc.
For the variables you need cross-access for, you can use Properties.
class VC1 : UIViewController {
NSString* v1;
NSString* v2;
}
#property (copy) NSString *v1;
#property (copy) NSString *v2;
And then, in the other view:
class VC2 : UIViewController {
VC1 *vc1;
}
And in you message implementations in VC2 you can use VC1's v1 and v2 like this:
- (void) someMessage {
NSLog(#"VC1's v1 value is %# and v2 value is %#", [vc1 v1], [vc1 v2]);
}
Hope it helps.

Related

Any way to apply Objective-C category only to current class (or equivalent effect)?

Let's say I have a custom subclass of UIView called MyCustomView. Let's also say that I have a category on UIView called UIView+Dictionary that adds an NSDictionary property called dictionary to every UIView.
If I were to import UIView+Dictionary.h into MyCustomView.m then every view referenced within MyCustomView.m would have this added dictionary property, which in many situations is exactly the desired behavior.
However, if I wanted UIView+Dictionary applied only to MyCustomView itself and not to every UIView referenced within MyCustomView.m, is there a way to do so (or achieve a similar effect)?
I'd like to avoid making MyCustomView a subclass of another custom subclass (e.g., MyViewWithDictionary), as I'd ideally like to be able to import multiple categories for something akin to multiple inheritance (e.g., UIView+Dictionary, UIView+Border, UIView+CustomAnimations).
In my actual own scenario, I've written a category to automatically implement a custom UINavigationBar in a view controller, but I'd like that category to apply only to the view controller into which I am importing the category and not any other view controllers that may be referenced in that file.
Any and all insights are appreciated! And I apologize in advance as I am fairly certain there are more correct terminologies for the effect described above.
However, if I wanted UIView+Dictionary applied only to MyCustomView itself [...] is there a way to do so [...]?
Only by changing the category to be on MyCustomView and not UIView.
The header has nothing to do with whether the category's methods are present on any given instance. If the category is compiled into your program, the methods are there, no matter where the instance is created. This is the reason that prefixes are so important on methods that are added to framework classes: categories have global effect, and name collisions are undefined behavior.
The header only affects the visibility of the methods as far as the compiler is concerned. You can use the usual tricks to call them at runtime regardless.
The category takes effect on the class itself, when the runtime is initialized at launch. If you want the methods of the category to be available only on a certain class, the category must be defined on that class.
As Josh pointed out, any methods added in categories are basically inert unless you call them. The issue that I was having was for generated properties and swizzled methods in categories (since, as Josh also pointed out, there are no mixins in Objective-C).
I was able to solve this by adding in a custom BOOL in my category that defaults to NO and acts as a "switch" for whatever category methods and properties I want to specify.
E.g., if I wanted my dictionary property to be lazily instantiated but only within MyCustomView, I could do the following:
// UIView+Dictionary.h
#interface UIView (Dictionary)
#property (nonatomic) BOOL enableDictionary;
#property (nonatomic, strong) NSDictionary *dictionary;
#end
// UIView+Dictionary.m
#import "UIViewController+CustomNavigationBar.h"
#import <objc/runtime.h>
#implementation UIView (Dictionary)
- (void)setEnableDictionary:(BOOL)enableDictionary {
objc_setAssociatedObject(self, #selector(enableDictionary), #(enableDictionary), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)enableDictionary {
NSNumber *enableDictionaryValue = objc_getAssociatedObject(self, #selector(enableDictionary));
if (enableDictionaryValue) {
return enableDictionaryValue.boolValue;
}
objc_setAssociatedObject(self, #selector(enableDictionary), #NO, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return self.enableDictionary;
}
- (void)setDictionary:(NSDictionary *)dictionary {
objc_setAssociatedObject(self, #selector(dictionary), dictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSDictionary *)dictionary {
if (!self.enableDictionary) {
return nil;
}
NSDictionary *dictionary = objc_getAssociatedObject(self, #selector(dictionary));
if (dictionary) {
return dictionary;
}
objc_setAssociatedObject(self, #selector(dictionary), #{}, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return self.dictionary;
}
#end
And then within -[MyCustomView viewDidLoad] I could simply call self.enableDictionary = YES. That way, only instances of MyCustomView will have a non-nil lazily instantiated NSDictionary. (Note that, in this example, all instances of UIViews will still respond to the selector #selector(dictionary), but our behavior will differ based on whether enableDictionary is YES or NO.)
While that is a trivial example, the same strategy can be used for methods that are swizzled within categories. (Again, swizzling methods within categories is probably bad form but a necessary evil in certain scenarios.)

Unable to access App Delegate property

I'm trying to access a property in my app delegate from another class (something I thought would be rather simply) but I'm having troubles in doing so. My files currently look like this:
LTAppDelegate.h
#import <Cocoa/Cocoa.h>
#import "Subject.h"
#interface LTAppDelegate : NSObject <NSApplicationDelegate, NSOutlineViewDelegate, NSOutlineViewDataSource, NSMenuDelegate> {
}
#property Subject *selectedSubject;
#end
LTAppDelegate.m
#synthesize selectedSubject;
The value for selectedSubject is then set inside applicationDidFinishLaunchingin LTAppDelegate.m. Now I'm wanting to get access to this from another class that I have, which is called LTTableViewController and is setup like so:
LTTableViewController.h
#import <Foundation/Foundation.h>
#import "LTAppDelegate.h"
#import "Subject.h"
#import "Note.h"
#interface LTTableViewController : NSObject{
NSMutableArray *notesArray;
LTAppDelegate *appDelegate;
Subject *s;
}
-(IBAction)currentSubjectDetails:(id)sender;
#end
LTTableViewController.m
#import "LTTableViewController.h"
#implementation LTTableViewController
- (id)init
{
self = [super init];
if (self) {
appDelegate = ((LTAppDelegate *)[[NSApplication sharedApplication] delegate]);
s = [appDelegate selectedSubject];
NSLog(#"Test Subject: %#", [s title]);
}
return self;
}
-(IBAction)currentSubjectDetails:(id)sender{
NSLog(#"Selected Subject: %#", [s title]);
}
After inserting various NSLog() messages it would appear that the init method of LTTableViewController is called before applicationDidFinishLaunchingis called in LTAppDelegate. Based on that it makes sense that the "Test Subject" NSLog() in LTTableViewController.m init displays null; however, the 'currentSubjectDetails' method is linked to a button on the interface and when that is pressed after the app is finished loading, the NSLog() message still returns null.
Is there anything obvious I'm missing here. I feel like I'm being a little stupid and missing something really basic.
Similar issue is described here http://iphonedevsdk.com/forum/iphone-sdk-development/11537-viewcontroller-called-before-applicationdidfinishlaunching.html Adding this kind of functionality in the constructor is usually not recommended. Generally, I'd suggest using parameters and not relying on hidden dependencies as those will necessarily depend on the order of execution and you lose the help of the compiler to avoid invalid values. View controller initializers should not be used to store mutable references since view controllers are initialized automatically by predefined constructors, and you cannot pass parameters to them this way.
If you need to access the app delegate, then obtain it, perform operations on it and drop the reference. Try not to cache it, you'll very likely introduce hidden issues. I suggest you hook into the appear-disappear cycle if the viewed contents depend on any kind of current state.
Well, s does not exist, since it is set to null in init, so -currentSubjectDetails prints null. It is not a good idea to set your private variables in the constructor if they depend on other objects.
Rather, let the other objects explicitly tell your controller that it should use that Subject (e.g., treat s as a property).
Or, just query ((LTAppDelegate *)[[NSApplication sharedApplication] delegate]); every time.
-applicationDidFinishLaunching called when e.g. all nib's object initialized, so launching will be ended after construction of views related stuff. This means that constructors of nib's objects wouldn't use any other nib's objects (your delegate and controller initializing with nib, right?).
Try to use -awakeFromNib instead of constructors, I think it will called after construction of both objects.
If you are trying to avoid often calls of ((LTAppDelegate *)[[NSApplication sharedApplication] delegate]) I'll recommend to pass it as method parameter, in function stack. Cyclic references defense and some flexibility.

Passing a dictionary between two viewcontrollers?

I have an application in which I have a webservice call that returns data as a dictionary. I want to access this dictionary in another view controller for loading the values into a table.
Can anybody demonstrate how to pass this response dictionary from one view controller to another?
You could define an NSDictionary property in your AnotherViewController and set it from the previous controller. I'll give a brief example below.
//.h
#interface AnotherViewController {
NSDictionary *data;
}
#property(nonatomic,retain) NSDictionary *data;
#end
//.m
#implementation AnotherViewController
#synthesize data;
Now from the current controller, after initializing AnotherViewController you set the dictionary before presenting it.
AnotherViewController *controller = [[AnotherViewController alloc] init];
controller.data = myCurrentDictionary;
Now AnotherViewController has a data property with the value of the previous controller.
Hope this helps.
I am assuming that the webservice is called because something happened (button clicked, viewDidLoad/viewDidAppear). If this is the case, passing a reference of the UIViewController to the webservice class is a perfect valid option. Keep in mind that for this relationship you should create a protocol, so on your webservice class you have something like this:
id<ViewControllerResponseProtocol> referenceToViewController;
This ViewControllerResponseProtocolwould define a method like this:
-(void)responseFromWebservice:(NSDictionary*)myDictionary;
So when the webservice class has build the NSDictionary you can the above method from the referenceToViewController:
[referenceToViewController responseFromWebservice:myDictionary];
If there isn't any kind of relationship between both, you use could NSNotificationCenter for it.
P.S: The solution of skram is perfectly valid if you already have the NSDictionary from the webservice on the initial UIViewController and now you want to pass it to a new UIViewController. Although I don't think that's what you want.

Singleton NSMutableArray accessed by NSArrayController in multiple NIB's

Early warning - code sample a little long...
I have a singleton NSMutableArray that can be accessed from anywhere within my application. I want to be able to reference the NSMutableArray from multiple NIB files but bind to UI elements via NSArrayController objects. Initial creation is not a problem. I can reference the singleton NSMutableArray when the NIB gets loaded and everything appears fine.
However, changing the NSMutableArray by adding or removing objects does not kick off KVO to update the NSArrayController instances. I realize that "changing behind the controller's back" is considered a no-go part of Cocoa-land, but I don't see any other way of programmatically updating the NSMutableArray and letting every NSArrayController be notified (except it doesn't work of course...).
I have simplified classes below to explain.
Simplified singleton class header:
#interface MyGlobals : NSObject {
NSMutableArray * globalArray;
}
#property (nonatomic, retain) NSMutableArray * globalArray;
Simplified singleton method:
static MyGlobals *sharedMyGlobals = nil;
#implementation MyGlobals
#synthesize globalArray;
+(MyGlobals*)sharedDataManager {
#synchronized(self) {
if (sharedMyGlobals == nil)
[[[self alloc] init] autorelease];
}
return sharedMyGlobals;
}
-(id) init {
if(self = [super init]) {
self.globals = [[NSMutableArray alloc] init];
}
return self
}
// ---- allocWithZone, copyWithZone etc clipped from example ----
In this simplified example the header and model for objects in the array:
Header file:
#interface MyModel : NSObject {
NSInteger myId;
NSString * myName;
}
#property (readwrite) NSInteger myId;
#property (readwrite, copy) NSString * myName;
-(id)initWithObjectId:(NSInteger)newId objectName:(NSString *)newName;
#end
Method file:
#implementation MyModel
#synthesize myId;
#synthesize myName;
-(id)init {
[super init];
myName = #"New Object Name";
myId = 0;
return self;
}
#end
Now imagine two NIB files with appropriate NSArrayController instances. We'll call them myArrayControllerInNibOne and myArrayControllerInNib2. Each array controller in the init of the NIB controller sets the content of the array:
// In NIB one init
[myArrayControllerInNibOne setContent: [[MyGlobals sharedMyGlobals].globalArray];
// In NIB two init
[myArrayControllerInNibTwo setContent: [[MyGlobals sharedMyGlobals].globalArray];
When each NIB initializes the NSArrayController binds correctly to the shared array and I can see the array content in the UI as you would expect. I have a separate background thread that updates the global array when content changes based on an external event. When objects need to be added in this background thread, I simply add them to the array as follows:
[[[MyGlobals sharedMyGlobals].globalArray] addObject:theNewObject];
This is where things fall apart. I can't call a willChangeValueForKey and didChangeValueForKey on the global array because the shared instance doesn't have a key value (should I be adding this in the singleton class?)
I could fire off an NSNotification and catch that in the NIB controller and either do a [myArrayControllerInNibOne rearrangeObjects]; or set the content to nil and reassign the content to the array - but both of these seems like hacks and. moreover, setting the NSArrayController to nil and then back to the global array causes a visual flash within the UI as the content is cleared and re-populated.
I know I could add directly to the NSArrayController and the array gets updated, but I don't see a) how the other NSArrayController instances would be updated and b) I don't want to tie my background thread class explicitly to a NIB instance (nor should I have to).
I think the correct approach is to either fire off the KVO notification somehow around the addObject in the background thread, or add something to the object that is being stored in the global array. But I'm at a loss.
As a point of note I am NOT using Core Data.
Any help or assistance would be very much appreciated.
Early warning - answer a little long…
Use objects that model your domain. You have no need for singletons or globals, you need a regular instance of a regular class. What Objects are your storing in your global array? Create a class that represents that part of your model.
If you use an NSMutableArray as storage it should be internal to your class and not visible to outside objects. eg if you are modelling a zoo, don't do
[[[MyGlobals sharedMyGlobals].globalArray] addObject:tomTheZebra];
do do
[doc addAnimal:tomTheZebra];
Dont try to observe a mutable array - you want to observe a to-many property of your object. eg. instead of
[[[MyGlobals sharedMyGlobals].globalArray] addObserver:_controller]
you want
[doc addObserver:_controller forKeyPath:#"animals" options:0 context:nil];
where doc is kvo compliant for the to-many property 'anaimals'.
To make doc kvo compliant you would need to implement these methods (Note - you don't need all these. Some are optional but better for performance)
- (NSArray *)animals;
- (NSUInteger)countOfAnimals;
- (id)objectInAnimalsAtIndex:(NSUInteger)i;
- (id)AnimalsAtIndexes:(NSIndexSet *)ix;
- (void)insertObject:(id)val inAnimalsAtIndex:(NSUInteger)i;
- (void)insertAnimals:atIndexes:(NSIndexSet *)ix;
- (void)removeObjectFromAnimalsAtIndex:(NSUInteger)i;
- (void)removeAnimalsAtIndexes:(NSIndexSet *)ix;
- (void)replaceObjectInAnimalsAtIndex:(NSUInteger)i withObject:(id)val;
- (void)replaceAnimalsAtIndexes:(NSIndexSet *)ix withAnimals:(NSArray *)vals;
Ok, that looks pretty scary but it's not that bad, like i said you don't need them all. See here. These methods dont need to be part of the interface to your model, you could just add:-
- (void)addAnimal:(id)val;
- (void)removeAnimal:(id)val;
and write them in terms of the kvc accessors. The key point is it's not the array that sends notifications when it is changed, the array is just the storage behind the scenes, it is your model class that send the notifications that objects have been added or removed.
You may need to restructure your app. You may need to forget about NSArrayController altogether.
Aaaaaannnnnyyywaaayyy… all this gets you nothing if you do this
[[[MyGlobals sharedMyGlobals].globalArray] addObject:theNewObject];
or this
[doc addAnimal:tomTheZebra];
from a background thread. You can't do this. NSMutableArray isn't thread safe. If it seems to work then the best that will happen is that the kvo/binding notification is delivered on the background as well, meaning that you will try to update your GUI on the background, which you absolutely cannot do. Making the array static does not help in any way i'm afraid - you must come up with a strategy for this.. the simplest way is performSelectorOnMainThread but beyond that is another question entirely. Threading is hard.
And about that static array - just stop using static, you don't need it. Not because you have 2 nibs, 2 windows or anything. You have an instance that represents your model and pass a pointer to that to you viewControllers, windowControllers, whatever. Not having singletons/static variables helps enormously with testing, which of course you should be doing.

iPhone SDK: Accessing methods in other classes

In my iPhone application I have multiple class files, I have my main application's class files, then I have my UIView class files. I have a simple -(void) method declared in my UIView class files, how can I access it from my main applications class files?
A bit more detail: In my application a video is played, when this video finishes playing a notification is sent and actions are preformed, which I have already successfully set up, however when the movie finishes I would like a method declared in another class file to be preformed. If the method was declared in the same class file I would simply use this code: [self mySimpleVoidMethod]; But obviously this doesn't work If the method is declared in a different class file. I believe it is possible to access a method declared in a different class file, but I just haven't got a clue about how to do it. Sorry if I'm using completely incorrect terms to name things. But I am relatively new to programming all together.
You've got a couple of options, depending on your setup. Here are a few:
1) Add a reference to the class with the function (the callee) as a property in the caller's class:
Caller.h
#interface Caller : SomeObject {
Callee *myCallee;
...
}
#property(nonatomic, retain) Callee *myCallee;
Caller.m
#synthesize myCallee;
-(void)someAction {
[myCallee doSomething];
}
Something that sets up Caller after initializing both classes:
caller.myCallee = callee;
2) Use another notification event, like it looks like you already know how to do.
3) Use a protocol if you've got a bunch of different classes that Caller might need to call that all support the same method:
DoesSomething.h
#protocol DoesSomething
-(void)doSomething;
#end
Callee.h
#interface Callee : NSObject<DoesSomething> { // NSObject or whatever you're using...
...
}
-(void)doSomething;
Caller.h
#interface Caller : SomeObject {
id<DoesSomething> *myCallee;
...
}
#property(nonatomic, retain) id<DoesSomething> *myCallee;
... Then as per example 1.
4) Use performSelector to send a message to the class.
Caller.h
#interface Caller : NSObject {
SEL action;
id callee;
}
-(void)setupCallbackFor:(id)target action:(SEL)callback;
Caller.m
-(void)setupCallbackFor:(id)target action:(SEL)callback {
callee = target;
action = callback;
}
-(void)someAction {
if([callee respondsToSelector:action]) {
[callee performSelector:action];
}
I'm sure there are other ways, and there are pros and cons to each of these, but something in there should fit your needs and/or give you enough to scan the documentation to fill in any gaps...
I did a blog post a few weeks ago that outlines one way to do this. It is similar to the previous answers, and includes some sample code you can download and look at. It is based on using table view controllers, but you should be able to adapt the ideas to your application without too much difficulty.
Passing values and messages between views on iPhone
You'll need an instance of the other class, accessible from the code that runs when the movie finishes. Often, this is accomplished by storing an instance of the other class as a field in the class, set either via a "setter", or during construction. You could also use key-value observing, watching a key representing the playstate of the movie; an instance of the other class can register to observe the changes to this key.
Specifically for patterns using UIView, your UIViewController for the view will have access to it (through the view method). If your "main application's class files" have a pointer to the controller - which they probably will, setup via Interface Builder - then that's an easy way to get to a UIView instance.