I'm new to iPhone development. I've been reading several questions on how to make a google maps annotation callout window accept line breaks. Every tutorial I've read requires me to fire the mapView:didSelectAnnotationView method. But I have no idea how to trigger this. things I've tried include
putting the method in my MapViewController.m file which extends UIViewController
putting the method in a MapView.m file which extends MKMapView, then have my Mapview element in my storyboard reference it as the class to use
There's so much about xcode, objective c, and iphone development that I don't understand, so i can't tell where my problem lies.
At the moment, my map does plot my desired marker on the desired location. I just need to understand how to fire the mapView:didSelectAnnotationView and mapView:viewForAnnotation functions before I can start customizing the call out box.
Does anyone have step by step instructions on how to trigger these functions?
A bit of background
A few things to note:
You don't call mapView:didSelectAnnotationView. The MKMapView calls that function on it's delegate. In other words, when you set up an MKMapView, you tell it: "hey, listen, anytimme you need to tell me what's happening on the map, go tell this guy, he'll handle them for you". That "guy" is the delegate object, and it needs to implement mapView:didSelectAnnotationView (that's also why its name "did select", ie, it already happened, as opposed to "select"). For a simple case, the delegate is often the UIViewController that owns the MKMapView, which is what I'll describe below.
That method will then get triggered when the user taps on one of your annotations. So that's a great spot to start customizing what should happen when they tap on an annotation view (updating a selection, for instance).
It's not, however, what you want if you want to customize what annotation to show, which is what it sounds like you're actually after. For that, there's a different method just a few paragraphs earlier on the same man page: mapView:viewForAnnotation. So substitute this method if you find that mapView:didSelectAnnotationView isn't what you were looking for.
What you can do
If you got as far as a map with a marker, I'm guessing you have at least:
* a view controller (extendeding from UIViewController, and
* an MKMapView that you've added to the view for that view controller, say named mapView
The method you want to fire is defined as part of the MKMapViewDelegate protocol.
The easiest way to get this wired is to:
make your UIViewController the delegate for you MKMapView
in code, say in your viewDidLoad, of your MapViewController.m you could do mapview.delegate = self, OR
in Interface Builder, you could drag the connection from the the MKMapView delegate property to the file's owner
then, define a method on your UIViewController called mapView:didSelectAnnotationView, declaring it just like the protocol does, in your MapViewController.m file:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
// whatever you need to do to your annotation and/or map
}
Good luck!
mapView:didSelectAnnotationView is a delegate method of the map view, you can read about it here:
MKMapViewDelegate Protocol Reference
You don't need to call it, the map view will call it "by it self" and send it to every view/view controller that registered as it's delegate.
What do you need to do
Basically you need to add the MKMapViewDelegate on your .h file, what will look something like this:
#interface someViewController : UIViewController <MKMapViewDelegate>
Then in the .m file, after you instantiate the map view you should add:
mapView.delegate = self;//were self refers to your controller
From this point and on your controller will be able to "receive messages" from the map view which are the methods that you can see on the MKMapViewDelegate reference I linked to.
So to implement the mapView:didSelectAnnotationView you need to add in your .m file
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
//if you did all the steps this methosd will be called when a user taps the annotation on the map.
}
What is happening
What happens in the background is:
The map view has a method (Apple codded) that handles the AnnotationView touch events.
When a touch event take place it sends a "message" to all of it's delegates saying "Hey a user did Select Annotation View on this map, Do with it what ever you need".
usually it looks like that:
[self.delegate mapView:someMapView didSelectAnnotationView:someAnnotationView];
Then every view/controller that assigned itself as a delegate and implemented the method will cal this method.
Good luck
Place *place = [[Place alloc] init];
PlaceMark *placeMark = [[PlaceMark alloc] initWithPlace:place];
[self.mapView selectAnnotation:placeMark animated:YES];
Related
I'm new to Cocoa programming.
In Xcode 6.1 I created an OS X app using storyboard. There's a Window Controller in the Outline out of the box. I changed its class in the "Identity Inspector" to a customized subclass of NSWindowController, which is named WindowController.
I wrote the following code in the #implementation of WindowController:
- (instancetype)initWithWindow:(NSWindow *) window {
NSLog(#"window");
window.titleVisibility = NSWindowTitleHidden;
return [super initWithWindow:window];
}
But the Output panel shows nothing.
if I override the windowDidLoad method in the class,
- (void)windowDidLoad {
[super windowDidLoad];
self.window.titleVisibility = NSWindowTitleHidden;
NSLog("#Cool...");
}
The message shows up.
The doc of NSWindowController says that -initWithWindow: is the Designated Initiailzer of the class. How can it be skipped?
In the How Window Controllers Work section of Window Programming Guide, it says:
For simple documents—that is, documents with only one nib file containing a window—you need do little directly with NSWindowController objects. AppKit creates one for you. However, if the default window controller is not sufficient, you can create a custom subclass of NSWindowController.
That's exactly what I am doing. But I don't know what I'm missing here.
Thanks!
As far as I know you should first call super with initWithWindow and then set the title. Otherwise the call to super initializes the window without the previous settings.
When you override the windowDidLoad method, you set the title visibility after initialization and everything is okay.
I have a UIButton that I create in my sub class ViewController, and add it to my MainViewController.
Now, I added a target method to this button that should push another view controller to my Navigation controller (the one that in the MainViewController).
I know that the method did call when I push the button, but the view wasn't push to the Navigation Controller.
I scanned this drawing - this is the drawing (I also added part of my code):
This is the code I'm using in my button:
(remember it's in a deferent ViewController).
- (void)buttonPressed:(UIButton *)sender
{
Photo_ScreenGlobalView *photo = [[Photo_ScreenGlobalView alloc] init];
[self.navigationController pushViewController:photo animated:YES];
}
Usually I solve these situations with delegation. If there is a view controller which is subordinate to another (i.e. a "sub" view controller) but should have the ability to trigger navigation changes, etc... then it should have a weak pointer back to it's 'parent'. Then the parent VC should implement an appropriately named protocol with a callback for the child to use. The names of these things can be generic, such as #property navigationDelegate and requestNavigationToViewController: or they can be more semantic, such as #property userFormDelegate and userFormDoneButtonPressed:
Generally speaking, a subordinate view controller should not be able to directly modify navigation at it's parent's level; but it can trigger it via more loosely-coupoled interfaces like these.
i came back to let you all know how i actually did it.
after googling a lot found this nice and quick guide how to make DELEGATE
and working with delegate solved all my problems. if you need any help don't hesitate to send me PM.
this is the guide:
http://css.dzone.com/articles/do-not-publishcreating-your
I have a simple test app (in OSX) that has a view controller with a button in its view. The button's action method is in the view controller's class, and that IBAction is connected in IB (through File's Owner). When the button is clicked, I get an EXC_BAD_Access error (except occasionally I get -[NSRunLoop buttonClick:] instead). I've read a bunch of posts here on SO having to do with NSViewControllers not being in the responder chain, but also that specifically hooking the action method up in IB should work. The only code I have is this:
In the app delegate:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
TestController *controller = [[TestController alloc] initWithNibName:#"TestController" bundle:nil];
[self.window.contentView addSubview:controller.view];
}
And, in the TestController class, just this:
-(IBAction)buttonClick:(id)sender {
NSLog(#"%#",sender);
}
I have 2 questions. Why is this happening, and where is the correct mvc place to put IBActions for button methods (shouldn't controller classes handle these)?
First off, I believe in the App Delegate there is already a property called viewController already defined for you. You should use this code self.viewController = [[TestController alloc] initWithNibName:#"TestController" bundle:nil]; instead of TestController *controller = [[TestController alloc] initWithNibName:#"TestController" bundle:nil];. If that doesn't fix it, then I'm not sure what's wrong. Your code should return the attributes of the button you clicked. I created my own sample project and tested out your code (although my app delegate looked different than yours) and it worked fine.
Second, you should elaborate on your second question "Why is this happening, and where is the correct mvc place to put IBActions for button methods (shouldn't controller classes handle these)?". It's not very clear what you mean.
Hope this helps a little.
NSViewController will not respond to IBActions on mac os x on iPhone It is the correct place to put your IBAction as the view should only draw its content (data) not change it. But on Mac os x the NSViewController is for setting up the NSView it's no ment to respond to IBActions you have two choices one is to put your IBAction in your NSView or is create a NSWindowController .
On mac osx you have plenty of screen space and you will alway have views within a window you'll use NSViewController to add them to your window and to setup your view but the window is first in your responder chain then your NSWindowController . eg: you may have one window and two view controller showing the same view which may have 5 test fields in it and view controller one loads that with data but you can not edit the data then view controller two loads the same view with the same data but enable editing for the text fields so. but all action methods will go to the WindowController .
I recommend you to check your viewController's identity inspector.
It may indicate wrong custom class.
viewController created basically usually has name ViewController on the section.
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.)
This should be straight forward for a guru. I don't have any code really written out, just a couple of controllers and a custom UIView. All connected through nibs. The app loads without crashing, yet I can't see my NSLog() hit from my custom UIView.
My application delegate has default template code which calls for a class of mine called TabAnimationController. TabAnimationViewController has its view set to TabView. I made sure that in TabAnimationViewController's NIB that File's owner is set to TabAnimationViewController and that my instance of UIView has its class set to TabView.
In TabView.m I'm trying to see how NSLog is going to hit, and it's not showing up at all.
- (void)loadView {
NSLog(#"calling loadView");
}
- (id)initWithFrame:(CGRect)frame {
NSLog(#"Calling initWithFrame:");
return self;
}
Strange. I'm not sure why even after proper IB connections that my NSLog will not show up. Only anything put into drawRect: will invoke. Why isn't initWithFrame or loadView ever get hit? What if I want to customize this view programmatically?
First of all, when a view is dehydrated from nib file, instead of initWithFrame, initWithCoder is invoked. So you need to implement your initialization in initWithCoder as well. (It may be a good idea to keep the initWithFrame initialization as well, if you anticipate programmatically creating your TabView instead of hooking up in the IB. Just refactor your initialization to another method and call it from both implementations.)
Also in your initialization code above you must always call the super class's initialization. There is a boiler plate pattern all custom classes use in their init implementation for that:
if (self = [super initXXX]) { do your initialization }
return self;
Second, loadView which is actually a UIViewController method and not a UIView method is invoked only if the view outlet of the controller is nil.
Unless you are composing your view yourself programmatically using your controller, you do not need to override loadView. Instead you should override viewDidLoad, which is called after the view is loaded, to do additional initialization.
The simplest way to get this up and running is simply to use the "View based Application" template when you create a new project. It sets up everything you need to start with.
But, in short, you're looking at the wrong methods. First, you shouldn't override loadView unless you're creating your view programatically. If it's loading from a XIB file look at the initWithNibName method.
You might also want to look at the viewDidLoad, viewWillAppear and viewDidAppear methods that are triggered, well, it's fairly obvious when!