UIButton Not Loading Properly For MKAnnotationView - objective-c

I am creating an MKAnnotationView with a detail disclosure button.
In mapView: viewForAnnotation: I just create an placeholder button.
// the right accessory view needs to be a disclosure button ready to bring up the photo
aView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
In mapView: didSelectAnnotationView: I actually create a button to be used (with the relevant tag)
// create a button for the callout
UIButton *disclosure = [self.delegate mapController:self buttonForAnnotation:aView.annotation];
NSLog(#"DisclosureButton: %#", disclosure);
// set the button's target for when it is tapped upon
[disclosure addTarget:self.delegate action:#selector(presentAnnotationPhoto:) forControlEvents:UIControlEventTouchUpInside];
// make the button the right callout accessory view
aView.rightCalloutAccessoryView = disclosure;
In the log, the button appears to be fully instantiated as well as set with the correct tag.
This is the button creator:
/**
* returns an button for a specific annotation
*
* #param sender the map controller which is sending this method to us (its' delegate)
* #param annotation the annotation we need to create a button for
*/
- (UIButton *)mapController:(MapController *) sender
buttonForAnnotation:(id <MKAnnotation>) annotation
{
// get the annotation as a flickr photo annotation
FlickrPhotoAnnotation *fpa = (FlickrPhotoAnnotation *)annotation;
// create a disclosure button used for showing photo in callout
UIButton *disclosureButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// associate the correct photo with the button
disclosureButton.tag = [self.photoList indexOfObject:fpa.photo];
return disclosureButton;
}
The problem comes when I select the annotation. For a few seconds when the annotation is selected and the detail disclosure button is tapped, nothing happens. However, after tapping away and back onto the annotation a few times and testing the button, it eventually works as expected.
What is going on with the strange delay? Sometimes when the button is going to work, it just appears as if the alpha is set to 0.0 until you tap on it and it appears.
Seriously one of the more odd problems I've encountered.

Before the didSelectAnnotationView delegate method is called, the map view has already prepared the callout view based on the annotation view's properties (before your changes).
So the callout you see the on the first tap is without the changes the app makes in didSelectAnnotationView. On the following taps, the callout could be based on the values set from the previous tap (this actually depends on how annotation view re-use is handled in viewForAnnotation).
It looks like the only things the code is doing in didSelectAnnotationView and buttonForAnnotation is setting the button action and tag.
I assume you're using the "tag" approach because the presentAnnotationPhoto: method needs to reference the selected annotation's properties.
You don't need to use a tag to get the selected annotation in your action method. Instead, there are a couple of better options:
Your custom action method can get the selected annotation from the map view's selectedAnnotations property. See this question for an example of how to do this.
Use the map view's own delegate method calloutAccessoryControlTapped instead of a custom action method. The delegate method passes a reference to the annotation view which contains a property pointing to its annotation (ie. view.annotation) so there's no guessing, searching, or question as to what annotation was selected. I recommend this option.
In the first option, do the addTarget in viewForAnnotation and don't bother setting the tag. You also don't need the buttonForAnnotation method. Then in the button action method, get the selected annotation from mapView.selectedAnnotations.
Currently, your action method is on self.delegate so you might have some trouble accessing the map view from that other controller. What you can do is create a local button action method in the map controller which gets the selected annotation and then calls the presentAnnotationPhoto: action method on self.delegate (except now that method can be written to accept an annotation parameter instead of being a button tap handler).
The second option is similar except you don't need to do any addTarget and in the calloutAccessoryControlTapped method, call presentAnnotationPhoto: on self.delegate.
For both options, I suggest modifying the presentAnnotationPhoto: method to accept the annotation object itself (FlickrPhotoAnnotation *) instead of the current UIButton * and in the map controller, do an addTarget on a method local to the map controller (or use calloutAccessoryControlTapped) and from that method, manually call presentAnnotationPhoto: and pass it the annotation.

Related

How to bind click action of NSButton in view based NSTableView

I have an NSTableView that is set to be 'view based', and within each NSTableCellView there is an NSButton and an NSTextField.
The text field is being populated correctly from an array controller. The buttons are appearing correctly but I'm having trouble working out how to hook up the click action.
I thought this would be possible by control-dragging from the NSButton in IB to a simple method like this one in my controller (in this case an NSDocument subclass):
- (IBAction)testAction:(NSButton *)sender {
NSLog(#"Test action");
}
It connects fine but never gets fired. Any ideas why this is or how to fix it?
I don't understand why this works, but I had the same problem and was able to get it working by assigning the table delegate and datasource to the file owner within IB, which is also the class of my click handlers. Only then did it seem to actually bind the click handlers for the buttons in my cell view. Previously I was setting the delegate and datasource in code after the view was loaded.
You have to subclass NSTableCellView class. put your onClick Action method in subclass files.
Let me know if i am not clear..

Show NSWindow on right click in NSTableView

I'd like to display an NSWindow when right clicking an item in an NSTableView, similarly to how the available outlets are shown in Interface Builder when you right click an object:
Unfortunately you can only use an NSMenu subclass as the menu property.
I also didn't find a delegate method of NSTableView that notifies about right clicks.
I was able to subclass NSTableView and implement rightMouseDown: and rightMouseUp: to be notified about those events, but if I set the menu property of the row cells to nil, they are not highlighted when right clicked, even though I call the super implementation):
- (void)rightMouseDown:(NSEvent *)theEvent {
[super rightMouseDown:theEvent];
NSPoint eventLocation = [theEvent locationInWindow];
eventLocation = [self convertPoint:eventLocation fromView:nil];
NSInteger rowIndex = [self rowAtPoint:eventLocation];
NSLog(#"Right clicked at row index %d", rowIndex);
}
I would like to have the highlight effect in the image below but display a window instead of the context menu:
First for the right click: explicitly select the row on right click (e.g. via this message). Then create your own NSWindow descendant, set an own NSView class as contentView and in the view you can draw the black background, rounded borders and what not. Show this window in your right click handler.
You can use an NSPopover, which works quite nicely. A popover creates a window for you, even if it is somewhat hidden. You'll get it from your controls if you send them the window message, and can register to listen for events, for instance.
The whole popover can be created in IB, and just have to implement the showRelativeToRect:ofView:preferredEdge: method in code.
To catch the right click event, you can use rightMouseDown:, which is originally defined in NSResponder, but is overridden in NSView to simply catch the event and show menu and it doesn't pass the event upwards in the responder chain (or the inheritance chain, for that matter). Hence, you simply implement that method to call showRelativeToRect:ofView:preferredEdge:.
You will typically need to have the contents in an NSViewController and its own accompanying nib file.
The NSPopover's contentViewController property can be set in IB, too.
All in all, not much code needed.
This tutorial is useful.

UIPageControl value changed not firing

I have an issue with the UIPageControl. I simplified the issue for clarity below:
I put a button and a UIPageControl in my app with Interface builder and put the following in the code:
- (IBAction)tappedButton:(id)sender{
self.pageControl.currentPage = 3;
NSLog(#"Tapped Button");
}
- (IBAction)changePage:(id)sender{
NSLog(#"PAGE Changed!!!!");
}
I attached the Value Changed action to the pageController via Interface Builder.
When I tap on the button, I see the output "Tapped Button" and the third dot is highlighted... but the changePage method is never called.
Any ideas?
The changePage: method will only be called when the users request a change of page using the UIPageControl. It won't be called when the user taps the button. If you want to call the changePage: method when the button is pressed called it explicitly:
[self changePage: nil];
UIPageControl doesn't actually change pages for you. You have to write that code, inresponse to the valueChanged event. So, you want to connect that event of your UIPageControl object to your output (tappedButton: I guess), and then call your changePage: method to actually change the UI. Does that help?
There are some example projects referenced from the Apple docs that use UIScrollView and UIPageControl together. Check 'em out.

How to make a custom tableView cell accessory

I have not yet found any really good examples on how to do this. There is an image that I want to use as the accessory button and when I put it in and click on it doesn't work. So it looks correct but doesn't work...
Here is my code:
[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
cell.accessoryView = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:#"TableView_Green_Disclosure.png"]];
So how do I get my UIImageView to call accessoryButtonTappedForRowWithIndexPath whenever it is tapped?
A thorough reading of accessoryView and accessoryType would reveal that they are mutually exclusive ways to customize a cell.
Setting the accessoryType will cause the table view delegate method to be called when it is tapped.
Setting the accessoryView will ignore the setting of accessoryType and give you something to display. If you want to receive a callback from the custom view you've put in place, it should be a control that is wired up to do so. (Or any view with a gesture recognizer.)
If you use a button, and set its action to accessoryTapped:, you will receive the button as the "sender" argument. You can walk up the view hierarchy until you find a table view cell, and then ask your table view what the indexPath of that cell is. This will then get you an index into your model objects and you be able to act on it appropriately.
Alternate to the button, you can enable interaction on the UIImageView above, and add a gesture recognizer to it.
To make the button actually do something, you'll need to implement - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath from UITableViewDelegate.
When an accessory button is tapped in a row, this method will be called and you'll have the chance to act appropriately using the passed in index path to determine which row's accessory was tapped.
Check the blog post hdr->cmdline for creating custom accessory view for UITableView.
The author used UIButton objects with images for custom accessory view.
To make use of the accessoryView - you would need to set the cell's accessoryType to UITableViewCellAccessoryNone deposit a UIButton (with associated image) into the cell and then wire it up to receive user touches. You might use something like the code below as the IBAction response to the cell's UIButton being touched:
- (IBAction) accessoryButtonPressed:(id) sender
{
NSUInteger pathInts[] = { 0,0 };
pathInts[1] = self.currentselectedrow; // ivar set when tableview row last selected
NSIndexPath* indexPath = [NSIndexPath indexPathWithIndexes:pathInts length:2];
[self tableView:mytableview accessoryButtonTappedForRowWithIndexPath:indexPath];
}
The UIButton would be wired to execute this glue code by way of a line inside your tableview's "cellForRowAtIndexPath:" function
[thecell setButtonTarget:self action:#selector(accessoryButtonPressed:)];
One thing I noticed is that the UIButton seems to want a 'swipe right' versus a simple 'tap' touch in order to trigger the event - but it could be my beta iOS that's the problem. Note that I had added a UIButton* object named 'cell_accessoryButton' to the Custom Cell source.
In the cell's source you'd support the 'setButtonTarget' call with code like this:
- (void) setButtonTarget:(MyViewController*)inTarget action:(SEL) inAction
{
[self.cell_accessoryButton addTarget: inTarget
action: (SEL) inAction
forControlEvents: UIControlEventTouchUpInside];
}
It's so much easier to just use the accessoryType reference and let iOS do the heavy lifting - but, if you want a custom graphic, etc - this is another path that works.

Why is it that my UITableViewController setEditing:animated: method is not called?

I must be doing stupid, but I can't see what: my UITableViewController subclass is never called when the edit button of my navigation is pressed.
What could be causing that?
My view hierarchy is loaded from a Nib file and put inside a popover. The [+] button is connected to the insertNewObject action of my UITableViewController subclass. It works fine.
The [Edit] button however has no action to connect to. The doc says it will automatically call the setEditing:animated: method of the view controller, which I override.
The nib file is set up pretty much as usual AFAICT. And in fact, I'm not sure what additional detail I can give that would suggest my mistake.
What is the control flow from the click on the [Edit] button to the call of the setEditing:animated method?
I feel like we must be missing the same thing.
Whatever the case, I made it work by doing the following.
IBOutlet UIBarButtonItem *editButton;
-(IBAction)editButtonPressed:(id)sender {
[self setEditing:YES animated:YES];
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animate
{
if(self.tableView.isEditing)
{
self.editButton.style = UIBarButtonItemStylePlain;
self.editButton.title = #"Edit";
}
else
{
//self.editButton.style = UIBarButtonSystemItemDone;
self.editButton.style = UIBarButtonSystemItemEdit;
self.editButton.title = #"Done";
}
// Toggle table view state
[super setEditing:!self.tableView.isEditing animated:animate];
}
I hooked the editButton up to the button I added to the nav bar and it's action to the editButtonPressed IBAction. After doing that my setEditing: is called (obviously) and the super call toggles the table view's editing state.
I'd like to use the system defined button styles, but the appropriate one is commented out because while it did change style I couldn't figure out how to change the text from "Edit" to "Done" so I had to do it all manually (that only worked if I left the button as Custom and set the style generically). This has the downside of not being localized (for free), etc.