MKAnnotationView Customize callout - handle touch on a button - objective-c

I'm using the J4n0 Callout code (github), to implement a custom annotation in MapKit.
In my annotation (MyCalloutView) I'm using a button and a label.
When I'm clicking on my button, the methode handleTouch is called, but the sender correspond an UITapGestureRecognizer with sender.view always equal to my annotation view, and not the button.
MyCalloutView.h
#interface MyCalloutView : CalloutView
#property (nonatomic, retain) IBOutlet UILabel* title;
#property (weak, nonatomic) IBOutlet UIButton *clickButton;
- (IBAction) handleTouch:(id)sender;
- (id) initWithAnnotation:(CalloutAnnotation*)annotation;
- (IBAction)onClickButton:(id)sender;
#end
MyCalloutView.m
#implementation MyCalloutView
-(IBAction) handleTouch:(UITapGestureRecognizer *)sender {
//LogDebug(#"touch from : %#", sender);
UIButton *senderButton = (UIButton *)sender.view;
LogDebug(#"Sender class : %# - Sender Tag : %d - Sender View class : %#", [sender class], sender.view.tag, sender.view.class);
LogDebug(#"Tap postion : (%f, %f)", [sender locationInView:sender.view].x, [sender locationInView:sender.view].y);
if(senderButton == self.clickButton){
LogDebug(#"le clique vient de click button !!");
}
}
[...]
CalloutView.h
#class CalloutAnnotation;
#interface CalloutView : BaseCalloutView
- (IBAction) handleTouch:(id)sender;
- (id)initWithAnnotation:(CalloutAnnotation*)annotation;
#end
CalloutView.m
#implementation CalloutView
-(IBAction) handleTouch:(id)sender {
LogDebug(#"touch %#", sender);
}
- (id)initWithAnnotation:(CalloutAnnotation*)annotation
{
NSString *identifier = NSStringFromClass([self class]);
self = [super initWithAnnotation:annotation reuseIdentifier:identifier];
if (self!=nil){
[[NSBundle mainBundle] loadNibNamed:identifier owner:self options:nil];
}
// prevent the tap and double tap from reaching views underneath
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTouch:)];
[self addGestureRecognizer:tapGestureRecognizer];
UITapGestureRecognizer *doubletapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTouch:)];
doubletapGestureRecognizer.numberOfTapsRequired = 2;
[self addGestureRecognizer:doubletapGestureRecognizer];
return self;
}
#end

This issue was a well known one. The solution is described in the GitHub : issue 1
The solution works for me.

Related

Custom view for NSPopupButton NSMenu

I'm having some problems setting a custom NSView for my NSPopupButton menu items. Here is what I've got so far:
#interface ViewController ()
#property (weak) IBOutlet NSPopUpButton *popupButton;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
for(int i = 0; i < 25; i++) {
NSMenuItem *menuItem = [[NSMenuItem alloc ] initWithTitle:[NSString stringWithFormat:#"%d", i] action:#selector(itemClicked:) keyEquivalent:#""];
MenuView *menuView = [[MenuView alloc] initWithFrame:CGRectMake(0, 0, 184, 50)];
menuView.displayText.stringValue = #"This is a test";
[menuItem setView:menuView];
[self.popupButton.menu addItem:menuItem];
}
}
- (void)itemClicked:(id)sender {
}
#end
//My custom view
#implementation MenuView
- (id)initWithFrame:(NSRect)frameRect {
NSString* nibName = NSStringFromClass([self class]);
self = [super initWithFrame:frameRect];
if (self) {
if ([[NSBundle mainBundle] loadNibNamed:nibName
owner:self
topLevelObjects:nil]) {
[self configureView];
}
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self configureView];
}
- (void)configureView {
[self setWantsLayer:YES];
self.layer.backgroundColor = [NSColor blueColor].CGColor;
}
#end
//Here is what my xib MenuView looks like
And here is the problem:
This seems like it should be a fairly straight forward task but I'm not sure what is happening to my view and why my label on the view seems to disappear and is not showing any text for each of the views. I was poking around in the documentation and stumbled across this for the NSPopupButton Menu :
// Overrides behavior of NSView. This is the menu for the popup, not a context menu. PopUpButtons do not have context menus.
#property (nullable, strong) NSMenu *menu;
I'm not sure if there is something that I'm doing wrong that is causing this problem or if what I'm trying to do in this context is not achievable off of an NSPopupButton NSMenu. If anyone has any experience with this and could offer advice I'd really appreciate it.

How to go to the next cell (Detail View) in a UITableView?

So, i have a UITableView split in 3 sections. I want to be able, once i opened up the second row in the first section (i.e.), to swipe left to go to the next cell, and to swipe right to go the previous cell.
I wrote the code for the swipe:
SecondDetailView.m
- (void)viewDidLoad
{
UISwipeGestureRecognizer *swipeRecognizerLeft = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeDetectedLeft:)];
swipeRecognizerLeft.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:swipeRecognizerLeft];
[swipeRecognizerLeft release];
UISwipeGestureRecognizer *swipeRecognizerRight = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeDetectedRight:)];
swipeRecognizerRight.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:swipeRecognizerRight];
[swipeRecognizerRight release];
}
- (void)swipeDetectedRight:(UIGestureRecognizer *)sender {
NSLog(#"Right Swipe");
}
- (void)swipeDetectedLeft:(UIGestureRecognizer *)sender {
NSLog(#"Left Swipe");
}
How can i do that? Is it right to put the code into the Detail View?
I have a very simple solution for your issue.
You need to declare an NSMutableArray *arr; in your .h file and assign your array to this array when you are moving to detail page.
And also you need to declare an NSString *currentPos; variable.
- (void)swipeDetectedRight:(UIGestureRecognizer *)sender {
currentPos--;
NSMutableDictionary *dic=[arr objectAtIndex:currentPos];
}
- (void)swipeDetectedLeft:(UIGestureRecognizer *)sender {
currentPos++;
NSMutableDictionary *dic=[arr objectAtIndex:currentPos];
}
In this way you can get your next and prev index values of array.
Hope this help for you.
Shivam
In my example I use NSString as my data that will be displayed in detail view controller. Feel free to change that to whatever suits your needs. Okay so here we go:
First declare a protocol in DetailViewController like so:
#class DetailViewController;
#protocol DetailViewControllerDelegate <NSObject>
- (void)swipeToNextCell:(DetailViewController *)sender;
- (void)swipeToPreviousCell:(DetailViewController *)sender;
#end
#interface DetailViewController : UIViewController
#property(weak, nonatomic) id<DetailViewControllerDelegate> delegate;
#property(copy, nonatomic) NSString *data;
#property(weak, nonatomic) IBOutlet UILabel *label;
#end
The next thing is to add UISwipeGestureRecognizers in DetailViewController to check for gestures:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UISwipeGestureRecognizer *leftGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeDetectedLeft:)];
leftGesture.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:leftGesture];
UISwipeGestureRecognizer *rightGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeDetectedRight:)];
rightGesture.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:rightGesture];
}
Implement viewWillAppear to display your data when you push your DetailViewController:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.label.text = self.data;
}
Don't forget to implement methods that will be called by GestureRecognizers:
- (void)swipeDetectedRight:(UISwipeGestureRecognizer *)sender
{
NSLog(#"Right Swipe");
[self.delegate swipeToNextCell:self];
self.label.text = self.data;
}
- (void)swipeDetectedLeft:(UISwipeGestureRecognizer *)sender
{
NSLog(#"Left Swipe");
[self.delegate swipeToPreviousCell:self];
self.label.text = self.data;
}
And thats all you need in your Detail View. Now go to the TableViewController. Your TableViewController should implement the DetailViewControllerDelegate protocol:
#interface CustomTableViewController : UITableViewController <DetailViewControllerDelegate>
#property(strong, nonatomic) DetailViewController *detailViewController;
#property(assign, nonatomic) NSInteger currentRow;
#end
Here is my getter for detailViewController #property:
- (DetailViewController *)detailViewController
{
if (_detailViewController == nil)
{
_detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
_detailViewController.delegate = self;
}
return _detailViewController;
}
Here is how I manage row selection:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *viewController = self.detailViewController;
viewController.data = [NSString stringWithFormat:#"Cell: %d", indexPath.row];
viewController.title = #"Detail";
self.currentRow = indexPath.row;
[self.navigationController pushViewController:viewController animated:YES];
}
The last thing you have to do is to implement protocol's methods:
- (void)swipeToNextCell:(DetailViewController *)sender
{
// Get data for next row
sender.data = [NSString stringWithFormat:#"Cell: %d", ++self.currentRow];
}
- (void)swipeToPreviousCell:(DetailViewController *)sender
{
// Get data for next row
sender.data = [NSString stringWithFormat:#"Cell: %d", --self.currentRow];
}
I tested it on simulator and worked fine. It is very simple as my data model is quite simple - it is just NSString. There is no checking whether there is any row in section so you have to figure that out yourself. But the whole delegation pattern should be the same.
Good luck!
Declare a protocol in the detail view controller and set the parent (which should be the table view controller) as the delegate. Then, in the swipe methods call the delegate and implement the necessary code for changing the selected row.

iOS: get image label

I have this code:
- (IBAction) checkIt:(id)sender{
NSString *button = [[(UIImageView *)sender label]text];
I want to get the label of the UIImageView. What am I doing wrong? This works with buttons...Not sure I understand how to access attributes correctly.
Subclass UIImageView. Add a string member and property for accessing it. Instead of creating an object of UIImageView, create the object of your class.
In .h
#interface CustomImageView : UIImageView
{
NSString *imageName;
}
#property (nonatomic, retain) NSString *imageName;
#end
in .m
#implementation CustomImageView
#synthesize imageName;
-(void) dealloc
{
[imageName release];
[super dealloc];
}
#end
in the file where you want to use this class
-(void) addCustomImageView
{
CustomImageView *imgView = [[CustomImageView alloc]initWithImage:[UIImage imageNamed:#"abc.png"]];
imgView.frame = CGRectMake(0, 0, 100, 100);
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(imgViewtapped:)];
[imgView addGestureRecognizer:tap];
[tap release];
[self.view addSubview:imgView];
[imgView release];
}
-(NSString *) imgViewtapped:(CustomImageView *)sender
{
return [sender imageName];
}
UIImageViews do not have labels.
https://developer.apple.com/library/ios/#documentation/uikit/reference/UIImageView_Class/Reference/Reference.html

Trying to understand how to use xib files

I have a custom view that I'm using in a xib file. I load the view and add it to a window. It adds the view just fine as I can see the default text of the labels in the view, but when I try to change the label with a method call, it doesn't change the text.
The custom view isn't anything to fancy, just draws a rounded, transparent background.
NotificationView.h
#import <Cocoa/Cocoa.h>
#interface NotificationView : NSView
#property (weak) IBOutlet NSTextField *primaryLabel;
#property (weak) IBOutlet NSTextField *secondaryLabel;
#property (weak) IBOutlet NSTextField *identifierLabel;
#end
NotificationView.m
#implementation NotificationView
#synthesize primaryLabel;
#synthesize secondaryLabel;
#synthesize identifierLabel;
- (id) initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self)
{
return self;
}
return nil;
}
- (void)drawRect:(NSRect)dirtyRect
{
NSColor *bgColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.6];
NSRect rect = NSMakeRect([self bounds].origin.x + 3, [self bounds].origin.y + 3, [self bounds].size.width - 6, [self bounds].size.height - 6);
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:5.0 yRadius:5.0];
[path addClip];
NSShadow *shadow = [[NSShadow alloc] init];
[shadow setShadowColor:[NSColor redColor]];
[shadow setShadowBlurRadius:2.0f];
[shadow setShadowOffset:NSMakeSize(0.f, -1.f)];
[shadow set];
[bgColor set];
NSRectFill(rect);
[super drawRect:dirtyRect];
}
#end
In the xib I have a custom view set to the type NotificationView. I've added 3 labels to the view and connected them to the above IBOutlets. (I ctrl-click & drag from the label to the .h file to make the connection.)
I'm loading the view and adding it to a window with the following method. It looks through an array of windows, if an existing match is found it used that window, if not it creates a new window.
- (void) popupNotificationWithTag:(NSString *)tag fade:(double)msFade lineOne:(NSString *)lineOneText lineTwo:(NSString *)lineTwoText
{
NotificationWindow *notificationWindow;
NotificationWindow *tmpWindow;
NSEnumerator *enumerator;
// Walk the notification windows in the array
enumerator = [self.notificationWindows objectEnumerator];
if(enumerator)
{
while((tmpWindow = [enumerator nextObject]))
{
if([tmpWindow.tag isEqualToString:tag])
{
notificationWindow = tmpWindow;
}
}
}
// Make a new notification window
if (!notificationWindow)
{
int width = [[NSScreen mainScreen] frame].size.width;
int height = [[NSScreen mainScreen] frame].size.height;
notificationWindow = [[NotificationWindow alloc] initWithRect:NSMakeRect(width - 420, height - 130, 400, 100)];
NSNib *nib = [[NSNib alloc] initWithNibNamed:#"Notification" bundle: nil];
NSArray *objects;
[nib instantiateNibWithOwner:self topLevelObjects:&objects];
for (id obj in objects) {
if ([[obj class] isSubclassOfClass:[NSView class]])
[notificationWindow setContentView:obj];
}
[notificationWindow setTag:tag];
[self.notificationWindows addObject:notificationWindow];
}
// Display window
[notificationWindow makeKeyAndOrderFront:nil];
[notificationWindow display];
notificationWindow.fadeOut = msFade;
[notificationWindow setPrimaryText:lineOneText];
[notificationWindow setSecondaryText:lineTwoText];
[notificationWindow setIdentifierText:tag];
}
The window class is NotificationWindow.h
#import <Foundation/Foundation.h>
#interface NotificationWindow : NSWindow
#property (nonatomic, strong) NSString *tag;
#property (nonatomic) double fadeOut;
- (id)initWithRect:(NSRect)contentRect;
- (void) setPrimaryText:(NSString *)text;
- (void) setSecondaryText:(NSString *)text;
- (void) setIdentifierText:(NSString *)text;
#end
NotificationWindow.m
#import "NotificationWindow.h"
#import "NotificationView.h"
//===========================================================================================================================
// Private call properties and methods
//===========================================================================================================================
#interface NotificationWindow()
#property (nonatomic,strong) NSTimer *timerFade;
- (void) timerFadeFired;
#end
//===========================================================================================================================
//===========================================================================================================================
#implementation NotificationWindow
//===========================================================================================================================
// Property Getters and Setters
//===========================================================================================================================
#synthesize tag = _tag;
#synthesize fadeOut = _fadeOut;
#synthesize timerFade = _timerFade;
//===========================================================================================================================
// Public methods
//===========================================================================================================================
- (id)initWithRect:(NSRect)contentRect
{
if (self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO]) {
[self setLevel: NSScreenSaverWindowLevel];
[self setBackgroundColor: [NSColor clearColor]];
[self setAlphaValue: 1.0];
[self setOpaque: NO];
[self setHasShadow: NO];
[self setIgnoresMouseEvents: YES];
[self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
[self orderFront: NSApp];
self.fadeOut = -1;
// Start our timer to deal with fadeing the window
self.timerFade = [NSTimer scheduledTimerWithTimeInterval:0.001
target:self
selector:#selector(timerFadeFired)
userInfo:nil
repeats:YES];
return self;
}
return nil;
}
- (BOOL) canBecomeKeyWindow
{
return YES;
}
- (void) display
{
[super display];
[self setAlphaValue:1.0];
}
- (void) setPrimaryText:(NSString *)text
{
NotificationView *view = self.contentView;
view.primaryLabel.stringValue = text;
}
- (void) setSecondaryText:(NSString *)text
{
NotificationView *view = self.contentView;
view.secondaryLabel.stringValue = text;
}
- (void) setIdentifierText:(NSString *)text
{
NotificationView *view = self.contentView;
view.identifierLabel.stringValue = text;
}
//===========================================================================================================================
// Private methods
//===========================================================================================================================
- (void) timerFadeFired
{
[self orderFront:NSApp];
if (self.fadeOut > 0)
{
self.fadeOut--;
}
else if (self.fadeOut == 0)
{
if (self.alphaValue > 0)
self.alphaValue -= 0.002;
else
self.fadeOut = -1;
}
}
#end
So I assume I'm doing something wrong connecting the labels to the IBOutlets, but I can't figure out what. I suppose I could create the view in code, but I was trying to be good and use the interface builder.
I'm in XCode 4.2.1.

After setting UITextView's text property, screen does not update accordingly

I have view controllers A(FileListViewController) and B(TextFileViewController). A is a UITableViewController. What I am doing now is that after selecting a row in controller A, I load a text file and display that text in a UITextView in controller B.
The following is the header and implementation part(some code is abridged) of my the two controllers.
FileListViewcontroller Interface:
#interface FileListViewController : UITableViewController {
NSMutableArray * fileList;
DBRestClient* restClient;
TextFileViewController *tfvc;
}
#property (nonatomic, retain) NSMutableArray * fileList;
#property (nonatomic, retain) TextFileViewController *tfvc;
#end
FileListViewController Implementation:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DBMetadata *metaData = [fileList objectAtIndex:indexPath.row];
if(!metaData.isDirectory){
if([Utils isTextFile:metaData.path]){
if(!tfvc){
tfvc = [[TextFileViewController alloc] init];
}
[self restClient].delegate = self;
[[self restClient] loadFile:metaData.path intoPath:filePath];
[self.navigationController pushViewController:tfvc animated:YES];
}
}
- (void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath {
NSError *err = nil;
NSString *fileContent = [NSString stringWithContentsOfFile:destPath encoding:NSUTF8StringEncoding error:&err];
if(fileContent) {
[tfvc updateText:fileContent];
} else {
NSLog(#"Error reading %#: %#", destPath, err);
}
}
And here is the interface for TextFileViewController:
#interface TextFileViewController : UIViewController {
UITextView * textFileView;
}
#property (nonatomic, retain) IBOutlet UITextView * textFileView;
-(void) updateText:(NSString *) newString;
#end
TextFileViewController implementation:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(done)] autorelease];
textFileView = [[UITextView alloc] init];
}
- (void) updateText:(NSString *)newString {
NSLog(#"new string has value? %#", newString);
[textFileView setText:[NSString stringWithString:newString]];
NSLog(#"print upddated text of textview: %#", textFileView.text);
[[self textFileView] setNeedsDisplay];
}
(void)restClient: loadedFile: will be call after the loadFile:intoPath: is completed in the disSelectRowAtIndexPath method.
In TextFileViewController's updateText method, from NSLog I see that the text property is updated correctly. But the screen does not update accordingly. I've tried setNeedsDisplay but in vain. Did I miss something?
Thanks in advance.
In -[TextFileViewController viewDidLoad] you're creating a UITextView, but its frame is never set, and it's not added to the view hierarchy.
Try changing this:
textFileView = [[UITextView alloc] init];
to this:
textFileView = [[UITextView alloc] initWithFrame:[[self view] bounds]];
[[self view] addSubview:textFileView];
The problem is that textFileView is created in the viewDidLoad method of TextFileViewController. This method has not yet been called by the time you call updateText (this happens before the TextFileViewController is pushed).
You can fix this by forcing the view to load before you call [[self restClient] loadFile:metaData.path intoPath:filePath];.