I used the RESideMenu story board example project download here https://github.com/romaonthego/RESideMenu however I have no idea how I inject my managedObjectContext created in AppDelegate into the main view controllers e.g. HomeViewController displayed by RESideMenu.
First of all, my advice is to avoid problems and do not use storyboards if you want to use DI.
In case you want to use them, the view controllers are created by the storyboard hence constructor injection is not possible so I assume you are using setter injection.
A simple solution is to get the app delegate's context in the in your view controller's context getter like this:
// .h
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
// .m
- (NSManagedObjectContext *) managedObjectContext
{
if(_managedObjectContext == nil)
{
_managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
return _managedObjectContext;
}
Yes, this is not DI since the view controllers are looking for the context instead of asking for it but is not so bad and it still allows you inject a different context with ease in your tests.
Another solution would be to override instantiateViewControllerWithIdentifier: in your UIStoryboard subclass and inject the context there, but you would need some way to check if the requested view controller needs the context or not (you could use respondsToSelector:#selector(setManagedObjectContext)), and also you would need the context in your storyboard subclass (you could either inject it or access it like in the code above). Check this question to see a similar approach but using Typhoon.
Last thing to say is that injecting a context in a view controller is prone to end up having huge view controllers, I would inject the context in other model objects and then inject those in your view controllers.
Related
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.
I'm trying to make interfaces for a cocoa app in xcode's interface builder. The view of one of the windows I made in Interface builder depends on data from another view, so it is necessary to message this view from the class which must pass it data. However I can't seem to find a way to get a reference for this view object from the owner of the nib file. Here is roughly the code I'm using:
controller = [[NSWindowController alloc] initWithWindowNibName:#"Somenibname"];
[[controller window] display];
theOtherView = [[[[controller window] contentView] subviews] objectAtIndex:1];
[theOtherView setObjectwhichneedstobemessaged:self];
[theOtherView sendAMessage:self];
The object that this code is in never receives the message. Initially I actually thought that the contentview was the view that appears in interface builder and tried to get a reference to it like this
theOtherView = [[[controller window] contentView]];
but that didn't work either. Thanks for reading.
It sounds like you may need a better understanding of how the View-Controller structures work with nib files and without more code/detail its difficult to know exactly what you are trying to do, but a quick way to solve your problem could be to use NSNotification's instead of trying to locate the other view and instigate a message send via call chain.
You can register to handle a notification from the receiving view and send the notification from the instigating view (and vice versa if you need it two ways).
Read up on IBOutlet and consider linking the views that you need directly from Interface Builder.
For example, your NSWindowController subclass might have:
#interface MyWindowController : NSWindowController
{
/* can also use more specific classes if you need them, e.g. NSButton if it's really an NSButton */
IBOutlet NSView* firstViewIWant;
IBOutlet NSView* secondViewIWant;
}
. . .
#end
Your implementation might have:
- (void)
windowDidLoad
{
[super windowDidLoad];
/* make sure the views were connected properly */
assert(nil != firstViewIWant);
assert(nil != secondViewIWant);
. . .
}
Then in Interface Builder, hook up these outlets from "File's Owner" to the exact views that you need them to be.
So it seems to me like there's a Catch-22 situation here. Note the following widely (and smartly) held stances on application architecture:
Apple (and most alpha devs) recommend not using a singleton or accessing the app delegate singleton for retrieving a NSManagedObjectContext. Rigidity, poor design, etc. OK-- I agree!
Iterating over a UITabbarController's child view controllers and giving each child VC a reference to the context (dependency injection) is also stupid, because you are loading every tab in your application at application launch time, just to pass a reference. This also goes against everything Apple recommends for application architecture.
How do you resolve this? Do you use NotificationCenter to post a notification when a view controller awakes from a nib, and have the app delegate pass in the context reference then? That's about the only way I can think of that jives with both #1 and #2, but it also feels like some contortion to me.
Is there a more elegant way?
Edit: doing a notification when initializing a view controller can be a race condition, since if you're doing things with Storyboard, your tabbar's child view controllers tend to be initialized (though sans-view loading) at launch. So you'd have to do such a notification in viewDidLoad, which is a bad idea vis-a-vis MVC convention. It also ties your hands from doing anything with data models (like pre-caching for performance) before the user does anything view-related.
When you pass NSManagedObject instances to a view controller, that view controller can retain those objects. It can then get to the NSManagedObjectContext through those managed objects by calling
-[NSManagedObject managedObjectContext]
I'm not sure if this will work in your particular case, but often it will. The app delegate or the root view controller creates a context and then passes along managed objects.
Update
If you need to use the context in multiple places, there's another pattern that I've found useful:
Subclass NSManagedObjectContext:
#interface MyManagedObjectContext : NSManagedObjectContext
+ (MyManagedObjectContext *)mainThreadContext;
#end
i.e. a singleton context for the UI / main thread. This is cleaner than using the App delegate, since other classes won't have to get to the App delegate, but can use this class directly. Also the initialization of the store and model can be encapsulated into this class:
#implementation MyManagedObjectContext
+ (MyManagedObjectContext *)mainThreadContext;
{
static MyManagedObjectContext *moc;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
moc = [[self alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
// Setup persistent store coordinator here
});
return moc;
}
#end
I see that some time has passed since you posted your question, but I have a different approach that I would like to share with you and everybody else.
I assume that you want to inject the managed object context in all/some of the view controllers that are shown as tabs of the UITabViewController and that you are using a Storyboard with a UITabBarController as the rootViewController.
In your AppDelegate header make your AppDelegate to implement the UITabBarControllerDelegate protocol.
#interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
...
In your AppDelegate implementation add the following UITabBarControllerDelegate method. It will take care of setting the managed object context in any view controller that has the property.
- (BOOL) tabBarController:(UITabBarController *)tabBarController
shouldSelectViewController:(UIViewController *)viewController {
if ([viewController respondsToSelector:#selector(setManagedObjectContext:)]) {
if ([viewController performSelector:#selector(managedObjectContext)] == nil) {
[viewController performSelector:#selector(setManagedObjectContext:) withObject:self.managedObjectContext];
}
}
return YES;
}
In your application:didFinishLaunchingWithOptions: set self as the UITabBarController delegate.
UITabBarController *tabBarController = (UITabBarController *) self.window.rootViewController;
tabBarController.delegate = self;
Sadly the first view controller to be loaded is not ready at that time (in tabBarController.selectedViewController), and doesn't invoke the delegate either. So the easiest way to set the first one is to observe the selectedViewController property of the TabBarController
[tabBarController addObserver:self forKeyPath:#"selectedViewController"
options:NSKeyValueChangeSetting context:nil];
and set it there.
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[object removeObserver:self forKeyPath:#"selectedViewController"];
UIViewController *viewController = [change valueForKey:NSKeyValueChangeNewKey];
if ([viewController respondsToSelector:#selector(setManagedObjectContext:)]) {
if ([viewController performSelector:#selector(managedObjectContext)] == nil) {
[viewController performSelector:#selector(setManagedObjectContext:) withObject:self.managedObjectContext];
}
}
}
I would create a core data provider class, which is a singleton. This class could either just provide the persistent store so that each view controller can create it's own context if needed. Or you could use the provider class more like a manager and return new (if needed) context. It should of course also setup a merge notification to each context so that you can listen to changes between threads.
With this setup each view controller can ask the provider/manager for the context and the provider/manager will handle everything internally and return a context for the view controller.
What do you think?
IMHO I find it more elegant not to do Core Data heavy lifting in a ViewController.
I'd suggest that you encapsulate data manipulation into an object and let the object reference/encapsulate your Core Data stack.
When your application and datamodel grows, you want to be really strict with how you pass MOCs. This becomes even more important if youre using concurrency and have to deal with multiple MOCs. Besides, your ViewControllers only need a MOC for FetchedResultsControllers. In most other cases youre fine passing ManagedObjects.
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.)
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.