How to properly remove a pin from another viewController - objective-c

What I've done so far is working but I would like to know whether this is the proper way or not.
I have a map that shows an annotation when this is pressed shows a callout.
The next view shown is a table view. This table has a button to remove that annotation.
I created one property in the table View of type MKMapView. After this view is initialized when the callOut accessory is tapped, I set the MKMapView property.
When the button is pressed in the table view, I delete the annotation through the map property.
Is this the right way?

Rather than the detail view directly manipulating the parent (map) controller view's controls, a more "right" approach might be to use delegate+protocol.
Define a protocol with the methods that the map controller needs to implement (eg. deleteAnnotation, detailViewDone, etc).
The detail view will have a delegate property for that protocol and call the protocol methods via the delegate property instead of directly accessing and modifying another view's controls.
The map controller would set itself as the delegate of the detail view and actually implement the protocol methods.
This way, each controller/class doesn't have to know the internal details of how the others work and let's you more easily change how each one works internally without affecting code in the others (as long as the protocol doesn't change). It improves encapsulation and reusability.
For example, in the detail view .h, define the protocol and declare the delegate property:
#protocol DetailViewControllerDelegate <NSObject>
-(void)deleteAnnotation:(id<MKAnnotation>)annotation;
-(void)detailViewDone;
//could have more methods or change/add parameters as needed
#end
#interface DetailViewController : UIViewController
#property (nonatomic, assign) id<DetailViewControllerDelegate> delegate;
#end
In the detail view .m, wherever you handle the delete button, call the delegate method instead:
if ([delegate respondsToSelector:#selector(deleteAnnotation:)])
{
[delegate deleteAnnotation:annotation];
}
In the map controller .h, declare that it implements the protocol and declare the methods:
#interface MapViewController : UIViewController<DetailViewControllerDelegate>
-(void)deleteAnnotation:(id<MKAnnotation>)annotation;
-(void)detailViewDone;
#end
In the map controller .m, in calloutAccessoryControlTapped where you create the detail view, set the delegate property instead of the map view property:
DetailViewController *dvc = [[DetailViewController alloc] init...
dvc.annotation = view.annotation;
dvc.delegate = self;
[self presentModalViewController:dvc animated:YES];
Finally, also in the map controller .m, implement the delegate method:
-(void)deleteAnnotation:(id<MKAnnotation>)annotation
{
[mapView removeAnnotation:annotation];
//dismiss the detail view (if that's what you want)...
[self dismissModalViewControllerAnimated:YES];
}
From the documentation, the articles Delegates and Data Sources and Using Delegation to Communicate with Other Controllers may be useful as well.

Related

Why is this delegate method automatically called in Objective-C?

I'm going through this book called "cocoa programming for mac os x" and I just started with delegates. This whole thing with delegates is still a little bit wacky to me but I think I just need to let it settle.
However there was this one exercise where I should implement a delegate of the main window so that if resized height is always 2xwidth.
So I got 4 files:
AppDelegate.h
AppDelegate.m
WindowDelegate.h
WindowDelegate.m
AppDelegate are just the two standard files that get created when you open a new Cocoa project. I had to look up the solution because I didn't quite know how to accomplish this task.
The solution was just to create a new cocoa class, "WindowDelegat.h/.m" and add this to it's implementation file:
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize {
NSSize newSize = frameSize;
newSize.height = newSize.width * 2;
return newSize;
}
Then I opened the interface builder, added a new object and made it my WindowDelegate. I then had to ctrl drag from the WindowDelegate to the actual window and made it the window's delegate.
Clicked run and it worked. Yay! But why?
First I thought that "windowWillResize" is just one of these callback functions that get's called as soon as the window is resized but it isn't. Normally methods get invoked because the general lifecycle of an program invokes them or because they are an #IBAction, a button or different control elements.
But "windowWillResize" is non of them. So why is it called?
EDIT: Problem solved! Thanks a lot!
Now I'm trying to connect the delegate to the window programmatically. Therefore I deleted the referencing outlet from WindowDelegate to the actual window in interface builder. It works but I just want to verify that this it the correct way how it's done:
AppDelegate.h
#import <Cocoa/Cocoa.h>
#import "WindowDelegate.h"
#interface AppDelegate : NSObject <NSApplicationDelegate>
#end
AppDelegate.m
#import "AppDelegate.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (strong) WindowDelegate *winDeleg;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (void)awakeFromNib {
[_window setOpaque:NO];
NSColor *transparentColor = [NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.5];
[_window setBackgroundColor:transparentColor];
NSSize initialSize = NSMakeSize(100, 200);
[_window setContentSize:initialSize];
_winDeleg = [[WindowDelegate alloc] init];
[_window setDelegate: _winDeleg];
}
#end
WindowDelegate.h
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#interface WindowDelegate : NSObject <NSWindowDelegate>
#end
WindowDelegate.m
#import "WindowDelegate.h"
#implementation WindowDelegate
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize {
NSSize newSize = frameSize;
newSize.height = newSize.width * 2;
return newSize;
}
- (id)init {
self = [super init];
return self;
}
#end
Why does the #property of WindowDelegate need to be strong?
And isn't my winDeleg an object? Why do I have to access it through _winDeleg when it's an object. I though the underscore is used to access variables?
Thank you for your help!
Clicked run and it worked. Yay! But why?
Because instances of NSWindow have a delegate property that can point to any object that implements the NSWindowDelegate protocol, and that protocol includes the -windowWillResize:toSize: method.
Read that a few times. The reason it's important is that you can create your own object, say that it implements NSWindowDelegate, implement -windowWillResize:toSize:, and set that object as your window's delegate. Then, whenever the user resizes the window, your method will be called and can modify the proposed new size.
Normally methods get invoked because the general lifecycle of an program invokes them or because they are an #IBAction, a button or different control elements. But "windowWillResize" is non of them. So why is it called?
This really isn't so different. Think of delegates as "helper objects." They let you customize the behavior of an object without having to create a whole new subclass. The NSWindowDelegate object is essentially a contract that the NSWindow promises to follow: whenever certain things happen, such as the user resizing the window, the window will call certain methods in its delegate object, if the delegate exists and implements those methods. In the case of NSApplication, a lot of those delegate methods are application lifecycle events, like the app starting up or quitting or getting a message from the operating system. In the case of NSWindow, delegate methods correspond to interesting events that can happen to a window, like the user moving it, hiding it, showing it, maximizing it, moving it to a different screen, etc. Other classes, like text views or network connections or movie players, have their own sets of interesting events and their own delegate protocols to match.
Note that methods marked IBAction really aren't delegate methods, they're just methods that get called by objects like controls that use a target/action paradigm. The IBAction keyword lets the IDE know which methods it should present as possible actions for things like buttons. You often find actions in window controllers and view controllers, and those objects frequently act as a delegate for some other object, but the actions themselves aren't part of the delegate protocol. For example, NSTableView takes a delegate object that determines how the table will act and what's displayed in it. It often makes sense for the view controller that manages the table to be the table's delegate, and that same view controller might also manage some buttons and contain the action methods that said buttons trigger, but the actions aren't part of the NSTableViewDelegate protocol and you therefore wouldn't call them delegate methods.

Passing data into the ViewController using the back button

I have multiple buttons in ViewA. When a button is clicked, it is redirected to ViewB. In ViewB, the user does some interactions, and then clicks the back button when he is done. How do I run a method and pass in a parameter from ViewB into ViewA and then continue working in ViewA?
Using the back button is strongly required, but I am keen to learn if there are other ways.
My idea was to get the ViewA from the stack, and when I am done with ViewB, just call upon it and redirect, but I couldn't figure out how to do it.
Thank you.
You want to define a delegate in ViewB and implement it in ViewA. At the appropriate time (e.g., when the back button is tapped) ViewB call the delegate method, passing the value as a parameter.
Something like this:
ViewB.h
// Define a delegate protocol allowing
#protocol ViewBDelegate
// Method of delegate called when user taps the 'back' button
-(void) backButtonSelected:(id) object;
#end
#interface ViewB : UIViewController
// property to hold a reference to the delegate
#property (weak)id<ViewBDelegate> delegate;
-(IBAction)backButtonSelected:(id)sender;
#end
ViewB.m:
#implementation ViewB
...
-(IBAction)backButtonSelected:(id)sender{
NSObject *someObjectOrValue = nil;
// Invoke the delegates backButtonSelected method,
// passing the value/object of interest
[self.delegate backButtonSelected:someObjectOrValue];
}
#end
ViewA.h:
#import "ViewB.h"
// The identifier in pointy brackets (e.g., <>) defines the protocol(s)
// ViewA implements
#interface ViewA : UIViewController <ViewBDelegate>
...
-(IBAction)someButtonSelected:(id)sender;
#end
ViewA.m:
-(IBAction) someButtonSelected:id
#implementation ViewA
...
// Called when user taps some button on ViewA (assumes it is "hooked up"
// in the storyboard or IB
-(IBAction)someButtonSelected:(id)sender{
// Create/get reference to ViewB. May be an alloc/init, getting from storyboard, etc.
ViewB *viewB = // ViewB initialization code
// Set `ViewB`'s delegate property to this instance of `ViewA`
viewB.delegate = self;
...
}
// The delegate method (defined in ViewB.h) this class implements
-(void)backButtonSelected:(id)object{
// Use the object pass as appropriate
}
#end
In iOS6 you can now use Unwind Segues. In Storyboard you might have noticed the new Exit buttons that can be used for this.
Unwind Segues will allow you to transition back from viewControllerB to viewControllerA and to provide info back through the prepareForSegue method.
Currently documentation is rather limited, but there is a useful WWDC2012 video on how to do this (Session 236)
If you don't want to implement protocol then other way is, you can create property in ViewB of type id like this
#property (nonatomic, unsafe_unretained) id* parentView;
And set it to ViewA when pushing ViewB like this
ViewB.parentView = ViewA;
//push ViewB
Then you can call ViewA's methods directly using this property.
You can define a protocol in ViewControllerA, example name ViewControllerADelegate, create a property delegate in ViewControllerB and in ViewControllerB implement the protocol defined in ViewControllerA.
When you are working in viewControllerB you can notify all your changes to viewControllerA calling methods defined in ViewControllerADelegate, when you will leave viewControllerB you will have viewControllerA updated.
If your ViewA is creating ViewB, you can create a protocol on your ViewB.h and set ViewA as ViewB's delegate. So whenever you change something in ViewB, you can notify ViewA directly.
Depending on your model, you can have a class that holds whichever settings you modify in ViewB. When you create ViewB, you pass the reference normally, and since it's an object, the instance will be shared, so when you go back to ViewA, all the changes are already applied.
viewB.settings = self.settings; // or whatever model object you have
Another option is to create a custom back button (you will lose the pointy look), have it call your custom method that notifies your ViewA and then does a [self.navigationController popViewControllerAnimated:YES];

What's the best way to get a bool value from a detailViewController?

In my navigation based app, there is a button that if pressed, will change the view to a detailViewController. Here the user can set several options. One of those options is a bool value. When I return from the detailViewController how can I see what this bool value is?
Create a protocol (DetailViewDelegate?) and create a delegate property in your DetailViewController. When you instantiate your view controller, set the delegate property to self and use that property to send messages back to your master view controller. The only tricky part is that you need to declare the delegate property as "assign" so that you don't create a retain loop between your detail view and master view.
DetailViewController.h:
#class DetailViewController; // Forward Declaration.
#protocol DetailViewDelegate
- (void)detailViewController:(DetailViewController *)controller didChangeBool:(BOOL)theBool;
#end
#interface DetailViewController : UIViewController {
id <DetailViewDelegate> delegate;
}
#property (assign) id <DetailViewDelegate> delegate;
#end
That's just the interface, but it should get you most of the way there. Set the delegate property of the detail view and implement a detailViewController:didChangeBool: method in your master view and that's about it.
To answer the questions in your comment:
Yes. Before you push the detail view controller set it's delegate property to self.
You need to declare that your master view controller implements the DetailViewDelegate protocol. Learn how to do that by reading Apple's Documentation.
After you declare that your master view controller will implement the protocol, you need to actually implement it. Add a detailViewController:didChangeBool: method to your master view controller.

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 :)

Managing multiple view controllers and data

My app has a main screen that the user always starts at, and from which I want to display other views programmatically. I set up the app identically to the approach in "Beginning iPhone Development" Ch. 6, which is to use a RootViewController that loads in other view controllers.
The book uses a button to trigger loading the next view controller, but in my app I need to swap controllers at the end of function calls and to share data (processed UIImages, etc) between views. I am not using a tab bar or navigation controller.
What I'm wondering is, should I just make my MainViewController the root controller and presentModalViewControllers from there? I'd like to keep the root model but I don't quite understand how to hook it all up and share data. I've seen posts that mention using protocols and notifications but I haven't wrapped my head around it yet. Any advice is appreciated.
What you want to do is add a Cocoa property in your main view controller that references the object instances which you want to share with subordinate view controllers.
For example, if we want to share an NSArray, we specify its property in the main view controller header:
#interface MainViewController : UIViewController {
NSArray *myArray;
}
#property (nonatomic, retain) NSArray *myArray;
#end
In the implementation, add the #synthesize directive and remember to release the array in -dealloc:
#implementation MainViewController
#synthesize myArray;
...
- (void) dealloc {
[myArray release];
[super dealloc];
}
#end
You also want to add this property to view controllers that are subordinate to the main view controller, in the exact same way. In their headers, specify the same variable name and property description.
In your main view controller, when you are ready to push a subordinate view controller, you set the subordinate view controller's property accordingly just before pushing:
- (void) pushSubordinateViewController {
SubordinateViewController *subVC = [[SubordinateViewController alloc] initWithNibName:#"SubordinateViewController" bundle:nil];
subVC.myArray = self.myArray; // this sets the sub view controller's myArray property
[self.navigationController pushViewController:subVC animated:YES];
[subVC release];
}
Likewise in your subordinate view controller, it will need to set its subordinate's array property accordingly, just before it pushes its own sub-sub-view controller.
By setting references in this way, each view controller is pointed at the same array, containing the desired elements.
To use the array, just call self.myArray, e.g. [self.myArray objectAtIndex:index] to get an object at a given index.