Can I implement a custom target-action in a NSView subclass? - objective-c

I currently have a custom view class which is drawing a 2d game board representation on a window.
When the user clicks on the board I am using the mouseDown: event to calculate the cell co-ordinates from the mouse position (I am doing this within the custom view class).
- (void)mouseDown:(NSEvent*)theEvent {
// Get position of mouse click:
NSPoint point = [theEvent locationInWindow];
NSPoint mousePos = [self convertPoint:point fromView:nil];
// Calculate which cell has been clicked:
int cellX = mousePos.x / gridSize.width;
int cellY = mousePos.y / gridSize.height;
}
(In the above snippet 'gridSize' is an NSPoint instance variable that I am using to store the height and with of each cell in the game board)
Is it possible for me to create an IBAction style message in my controller class that can be used to send this information?
The way I was imagining this could work was:
Declare instance variables for clicked co-ordinates in the custom view.
Implement an (IBAction) in the controller class:
Use the (id)sender pointer to access the instance variables that hold the co-ordinates
Link the IBAction to the custom view in interface builder
To try this I declared 'cellX' and 'cellY' as instance variables in my custom view. I then implemented the following in my controller class:
-(IBAction)cellClicked:(id)sender {
[self setCellAtPosX:[sender cellX] PosY:[sender cellY];
}
(cellX and cellY are accessor methods to the instance variables)
As I was expecting it is not that simple, and it will not let me link the IBAction to the custom view. Is there anything missing from my approach to implementing this?
I can imagine that all this would need is some kind of notifier that tells interface builder where to send out a target-action message - in my case, at the end of the mouseDown: implementation, but I cannot find where to begin with this!

You may have less trouble implementing this with a delegate pattern, i.e. set a IBOutlet delegate property on your board view and have the controller implement this protocol. In Interface Builder you will link your view and controller via the outlet, and the view will call it's delegate method in the mouseDown implementation, for instance:
- (void)mouseDown:(NSEvent*)theEvent {
...
[self.delegate boardView:self didSelectCellAtX:x Y:y];
}

Related

Object under mouseDown COCOA

I have a pretty simple question for which I could not find a simple answer.
When using cocoa (osx, xcode) and a method called "mouseDown" which detects if mouse has clicked on a view, how to detect on which object mouse has clicked? I just need a class name so I can know if the user has clicked on, for example NSImageView, WebView, NSTextView or on a NSView it self? Or even better, if I have two NSImageViews on my NSView, how to detect on which one it was clicked?
Cheers.
In your view mouseDown method, you can call the hitTest: method to get the farthest descendant of the receiver in the view hierarchy that was clicked:
So in your view subclass, you could do something like:
- (void)mouseDown:(NSEvent *)theEvent
{
id clickedObject = [self hitTest:[theEvent locationInWindow]];
if ([clickedObject isKindOfClass:[NSImageView class]]) {
NSLog(#"Clicked an ImageView");
} else if ([clickedObject isKindOfClass:[WebView class]]) {
NSLog(#"Clicked a WebView");
}
}
Your question seems a bit odd though, because normally you don't need to do this hit testing yourself.
If you're trying to get a click event when a particular image is clicked, a better way would be to use a borderless button with an image set and then implementing an action method and connecting that to the button.

How do you access the outlets of the main view controller from a logic class?

I have my viewcontroller class for my only current View and another class with static methods for my mathematical logic. ViewController class has an IBOutlet for a label. How can I reference this outlet from within the functions of my Logic class?
You could pass a pointer to the logic class just like any other variable, but I wouldn't recommend directly accessing the IBOutlet property.
What I'd recommend, is either having the logic class return the values and have the controller update the label as needed, or if it involves background processing that doesn't return immediately, use the delegate pattern. This way, the logic class will inform the controller when the data is ready, or the calculations are finished, and then the controller can update the UI as needed.
Look into iOS Protocols to define the structure of a delegate class :)
You shouldn't allow your Logic class to access a UI control because it does not follow the Model-View-Controller pattern, which is a smart way to keep your code organized so that it's easier to follow as your project becomes more complicated. Instead, you would want your ViewController to communicate between the UI and the Logic class for you.
For example, if you had a Calculate button at the bottom of your view that the user taps, the tap should be handled by the ViewController. The ViewController would call a function in your Logic class that might return a value. Then the ViewController would take that value and set it as the text of the label. Here's a snippet of code that illustrates the idea:
- (IBAction) calculateSomeValue: (id) sender {
int result = [Logic calculateValue];
[self.label setText: [NSString stringWithFormat: #"Your result is: %d", result]];
}

get instance from Controller object in NSImageView (cocoa)

I'm new to Objective-C.
I had make a little application with an Image Well (NSImageView) and some Buttons.
To receive the actions from the Buttons and Labels. I created a Class named "Controller". I have connected this class using the "Object" object to the InterfaceBuilder file.
For the Image Well, I created a class to inherits from NSImageView (DImageView) and set this class as class for the ImageWell (using the interfaceBuilder)
In this class I had overwritten the mouseDown Method:
//Class DImageView
- (void) mouseDown:(NSEvent *)theEvent
{
NSLog(#"Test");
}
Now I want to call an method which is defined in the class Controller from this method.
But if I create a new instance of the controller object with [[Controller alloc] init]. I'm creating an new instance and can't access the IBOutlets in the Controller class right?
How can I solve this problem?
Thanks for the help ..
Link it via xib file:
And consider overriding acceptsFirstResponder too, otherwise you will not get any mouse event.

how to fire mapView:didSelectAnnotationView

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];

How to get a reference to the view controller of a superview?

Is there a way to get a reference to the view controller of my superview?
There were several instances that I needed this on the past couple of months, but didn't know how to do it. I mean, if I have a custom button on a custom cell, and I wish to get a reference of the table view controller that controls the cell I`m currently in, is there a code snippet for that? Or is it something that I should just solve it by using better design patterns?
Thanks!
Your button should preferably not know about its superviews view controller.
However, if your button really needs to message objects that it shouldn't know the details about, you can use delegation to send the messages you want to the buttons delegate.
Create a MyButtonDelegate protocol and define the methods that everyone that conforms to that protocol need to implement (the callback). You can have optional methods as well.
Then add a property on the button #property (weak) id<MyButtonDelegate> so that any class of any kind can be set as the delegate as long as it conforms to your protocol.
Now the view controller can implement the MyButtonDelegate protocol and set itself as the delegate. The parts of the code that require knowledge about the view controller should be implemented in the delegate method (or methods).
The view can now send the protocol messages to its delegate (without knowing who or what it is) and the delegate can to the appropriate thing for that button. This way the same button could be reused because it doesn't depend on where it is used.
When I asked this question I was thinking of, in a situation where I have custom cells with buttons on them, how can the TableViewController know which cell's button was tapped.
More recently, reading the book "iOS Recipes", I got the solution:
-(IBAction)cellButtonTapped:(id)sender
{
NSLog(#"%s", __FUNCTION__);
UIButton *button = sender;
//Convert the tapped point to the tableView coordinate system
CGPoint correctedPoint = [button convertPoint:button.bounds.origin toView:self.tableView];
//Get the cell at that point
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:correctedPoint];
NSLog(#"Button tapped in row %d", indexPath.row);
}
Another solution, a bit more fragile (though simpler) would be:
- (IBAction)cellButtonTapped:(id)sender
{
// Go get the enclosing cell manually
UITableViewCell *parentCell = [[sender superview] superview];
NSIndexPath *pathForButton = [self.tableView indexPathForCell:parentCell];
}
And the most reusable one would be to add this method to a category of UITableView
- (NSIndexPath *)prp_indexPathForRowContainingView:(UIView *)view
{
CGPoint correctedPoint = [view convertPoint:view.bounds.origin toView:self];
return [self indexPathForRowAtPoint:correctedPoint];
}
And then, on your UITableViewController class, just use this:
- (IBAction)cellButtonTapped:(id)sender
{
NSIndexPath *pathForButton = [self.tableView indexPathForRowContainingView:sender];
}
If you know which class is the superview of your view controller, you can just iterate through the subviews array and typecheck for your superclass.
eg.
UIView *view;
for(tempView in self.subviews) {
if([tempView isKindOfClass:[SuperViewController class] ])
{
// you got the reference, do waht you want
}
}