Adding functionality to all subclasses of UIViewController - objective-c

Here is my situation and I'm confused as to the best way to do this (SubClass,Category,Extension, just write it in all the classes I want). I want to basically add a managedObjectContext to a bunch of my view controllers and have it passed around. I've had to add the following code to a majority of my controllers and I realize there is probably an easier way to do this:
Header Files:
#interface
#property(weak, nonatomic) NSManagedObjectContext *managedObjectContext;
#end
.m files:
// Synthesize the variable
#synthesize managedObjectContext;
and
// Pass on managedObjectContext
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// If the destination VC is able to take teh setManagedObjectContext method the current objectContext will be passed along.
if ([segue.destinationViewController respondsToSelector:#selector(setManagedObjectContext:)]) {
[segue.destinationViewController performSelector:#selector(setManagedObjectContext:)
withObject:self.managedObjectContext];
} else {
NSLog(#"Segue to controller [%#] that does not support passing managedObjectContext", [segue destinationViewController]);
}
}
#end
So here is where the question is: I have
UIViewControllers
UITableViewControllers
Probably-another-uiController
What is the best method of adding these functions to my class?
As a side not, however, I do want my class to be able to add additional methods to the prepareForSegue call which makes me think perhaps subClassing things would be best. That way in my individual class implementation i can call [super prepareForSegue...]. That begin said, however, I assume I'd have to make a single class per view controller because you can't do multiple inheritance or have I managed to confuse myself?
-- EDIT --
Various Comments have suggested a Category is the right way to go, however, if I make a category for prepareForSegue am I still able to make a call somehow equivalent to [categoryVersion prepareForSegue] I realize I could probably use a different function name but I do want to modify the default functionality of prepareForSegue

Related

RESideMenu Dependency injection

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.

Simple Passing of variables between classes in Xcode

I am trying to do an ios app but I am stuck at passing data between classes .
This is my second app . The first one was done whit a global class , but now I need
multiple classes . I tried many tutorials , but did not work or the passed value was always zero . Can someone please write me a simple app to demonstrate passing of variables in IOS 5 .
Nothing special , storyboard whith 2 view controllers , one variable .
Thank you for your help .
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
FirstViewController *fv;
fv.value = indexPath.row;
NSLog(#"The current %d", fv.value);
FirstViewController *detail =[self.storyboard instantiateViewControllerWithIdentifier:#"Detail"];
[self.navigationController pushViewController:detail animated:YES];
}
here is the code from my main view and i need to send the indexPath.row or the index of the cell that i pressed to the next view
There are several things to do. Depending on the app, you could either add a variable to the AppDelegate class, making it availible to all classes through a shared instance. The most common thing (I think) is to make a singleton. For that to work, you can make a class, say StoreVars, and a static method that returns the object, which makes the class "global". Within the method, you initialize all your variables, like you always would. Then you can always reach them from wherever.
#interface StoreVars : NSObject
#property (nonatomic) NSArray * mySharedArray;
+ (StoreVars*) sharedInstance;
#implementation StoreVars
#synthesize mySharedArray;
+ (StoreVars*) sharedInstance {
static StoreVars *myInstance = nil;
if (myInstance == nil) {
myInstance = [[[self class] alloc] init];
myInstance.mySharedArray = [NSArray arrayWithObject:#"Test"];
}
return myInstance;
}
This will make a singleton. If you remember to import "StoreVars.h" in your two viewControllers, you can access the now shared array like this;
[StoreVars sharedInstance].mySharedArray;
^
This is a method returning a StoreVars object. Within the StoreVars class, you can implement any object and initialize it in the static method. Just always remember to initialize it, or else, all your object will be 0/nil.
If you are not a fan of the UINavigationController and would rather use segues, it's a lot easier, but can make your app rather "messy" imo. There is a method implemented in UIViewController you should overload:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"YOUR_SEGUE_NAME_HERE"])
{
// Get reference to the destination view controller
YourViewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setMyObjectHere:object];
}
}
source: How to pass prepareForSegue: an object
Do some research before asking questions like this. Read some tutorials, and try out yourself, and then ask questions related to what you are really looking for. It's not everyday people want to do all the work for you, but sometimes you're lucky. Like today.
Cheers.
if you use segue between 2 controllers you must overload prepareToSegue method
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// check if it's the good segue with the identifier
if([[segue identifier] isEqualToString:#"blablabla"])
{
// permit you to get the destination segue
[segue destinationViewController];
// then you can set what you want in your destination controller
}
}
The problem you face is quite perplexing for beginners. "Solving" it wrong way can result in learning a ton of bad habits.
Please have a look at Ole Begemann's excellent tutorial on Passing Data Between View Controllers - it's really worth reading.

Xcode (storyboards and segues): why delegates instead of references?

I have read the following tutorial regarding storyboard.
Basically the sample App created in this tutorial let the user navigate between various views and it's created using segue.
In order to navigate between views the tutorial say to create two UITableViewController and when "going" from one to another to specify a delegate:
First controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"AddPlayer"])
{
UINavigationController *navigationController = segue.destinationViewController;
PlayerDetailsViewController *playerDetailsViewController = [[navigationController viewControllers] objectAtIndex:0];
playerDetailsViewController.delegate = self;
}
}
Second controller:
#protocol PlayerDetailsViewControllerDelegate <NSObject>
- (void)playerDetailsViewControllerDidCancel: (PlayerDetailsViewController *)controller;
- (void)playerDetailsViewController: (PlayerDetailsViewController *)controller didAddPlayer:(Player *)player;
#end
#interface PlayerDetailsViewController : UITableViewController
#property (nonatomic, weak) id <PlayerDetailsViewControllerDelegate> delegate;
When "going back":
- (IBAction)cancel:(id)sender
{
[self.delegate playerDetailsViewControllerDidCancel:self];
}
My simple question is why this complication? Why use delegates and protocols?
I have changed the code using a "Java" style and now I'm passing to the second controller a reference to the first one, everything is working.
First controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
...
playerDetailsViewController.playerViewController = self;
}
Second controller:
#property (strong, readwrite) PlayerViewController *playerViewController;
So, what are the benefits to use delegates instead of simply passing references between ViewControllers?
Thanks!
Guido
Several reasons:
As Leonardo says, using references you couple the two view controllers together unnecessarily. You should just pass the data that's required and not the whole class
This is just how Objective-C apps tend to be constructed. By using a different method you'd be making your app harder to understand by seasoned developers
(You don't always need the delegate in your second class -- for example when it's display only -- so your example code is more complex than is often the case)
Related to the last point, your code is already harder than it needs to be. UIStoryboardSegue has properties pointing to the source and destination view controllers; there's no need to mess with the navigation controller.
To put in a Java manner, if you use a strong type you are tied to a single class.
Instead the delegate is in the end a class conform to a protocol.
So you could pass many class as delegates to playerDetail, as long as they are conform to the #protocol.
Is like casting and passing interface instead of concrete class in java.
You may well know the List interface and all the ArrayList, LinkedList... etc concrete implementations.
One thing I don't understand is why they get destination controller by passing trough the navigation. I always used:
MyDestinationViewController *dvc = [segue destinationViewController];
so that you can use in many situation where you do not have a navigation controller.

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.

refreshing mkannotation properties and refreshing the annotation

I have a mkannotation located on a mapview which has a mkannotationview as well as a calloutview which when clicked goes to a child uiviewcontroller. I am updating some properties from the callout's uiviewcontroller, but after I'm finished I want to move the annotation's position on the map and change the annotation title and subtitle. How can I easily do this from the callout's uiviewcontoller? What's the most elegant way to handle this? A code sample would be great if anyone has any.
Thanks
I'd create a protocol, say MapCallBackDelegate, to handle what you want to do. This avoids tightly coupled code. Put this in your map annotation view header file
#protocol MapCallBackDelegate
-(void)updateAnnotation:(id)whatEverParamsYouWant;
#end
Then make your Map View implement this protocol. When you create your map annotation view, give it a property
#property (nonatomic, retain) id<MapCallBackDelegate> callbackDelegate;
And when you add it to your map, set that property to self
myMapAnnotationView.callbackDelegate = self;
so when you want to change the title/subtitle/position, you just invoke that message on the callbkacDelegate.
This is elegant because it reduces tightly-coupled code, allows other objects to implement the same protocol for code reuse later, and promotes information hiding in your MapAnnotationView.
Remove the annotation from the map entirely, update it, and add it to the map again. That'll ensure that the map notices that the annotations location has changed.
Although you can remove and add the annotation back as #Caleb suggests, another option is to update the coordinate property directly on the annotation you want to move.
Note that this will only work if your annotation class implements setCoordinate which can easily be done by declaring the coordinate as assign (like the built-in MKPointAnnotation class does) instead of readonly. The map view will see the change via KVO and move the annotation.
To have the child view controller tell the map view controller which annotation to change and what the new coordinates are, I recommend using delegate+protocol as another answer suggests.
The easiest way would to actually not do it from the child view controller. Maybe your needs are different from what I understand from the question, but at first blush I would do something like this:
In the header:
#interface YourController
{
...
MKAnnotation *_latestDetailViewed;
}
...
#property(nonatomic, retain) MKAnnotation *latestDetailViewed;
#end
Then in the .m something like
#implementation YourController
...
#synthesize latestDetailViewed = _latestDetailViewed;
...
-(void) dealloc
{
...
self.latestDetailViewed = nil;
[super dealloc];
}
-(void) whereverYouLaunchYourDetailScreenFrom:(MKAnnotation*)detailAnnotation
{
self.latestDetailViewed = detailAnnotation;
// then create/push your view controller
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(_latestDetailViewed)
{
// Do whatever you want to the annotation here
self.latestDetailViewed = nil;
}
}
That way your changes will be made when you come back to the map. If you're really only launching a detail view for one annotation at a time, and always coming back to the map in between, then it should work without making you deal with writing a delegate protocol or firing off NSNotifications.
If I'm misunderstanding your situation let me know and I'll give you a different answer :)