MKAnnotation shows it's Custom Marker graphic in simulator but not on device - iphone

I had this working very early, but then it stopped and I have no idea why. Here is the code:
- (void)updateMarkers:(NSMutableArray *)myAudioLocationVOArray
{
[self cleanupMarkers];
NSLog(#"UPDATE ALL MARKERS");
int tArrayCount = [myAudioLocationVOArray count];
for (int i=0; i< tArrayCount; i = i + 1)
{
AudioLocationVO* tAudioLocVO = [myAudioLocationVOArray objectAtIndex:i];
AudioAnnotation *tNewAnn = [[AudioAnnotation alloc] init];
tNewAnn.coordinate = CLLocationCoordinate2DMake(tAudioLocVO.latitude, tAudioLocVO.longitude);
// add current track if available
tNewAnn.audioLocationVORef = tAudioLocVO;
[self.mapView addAnnotation:tNewAnn];
[tNewAnn release];
}
}
- (void)cleanupMarkers
{
NSLog(#"REMOVE ALL MARKERS");
NSArray *tExistingPoints = self.mapView.annotations;
if ([tExistingPoints count] > 0)
{
[self.mapView removeAnnotations:tExistingPoints];
}
}
- (MKAnnotationView *)mapView:(MKMapView *)myMapView viewForAnnotation:(id <MKAnnotation>)myAnnotation
{
if ([myAnnotation isKindOfClass:[AudioAnnotation class]])
{
AudioAnnotation *tAnnotation = (AudioAnnotation *)myAnnotation;
MKAnnotationView *tNewMarkerView = [[[MKAnnotationView alloc] initWithAnnotation:tAnnotation reuseIdentifier:nil] autorelease];
if(tAnnotation.audioLocationVORef.state == ANNOTATION_STATE_DROPPING)
{
NSLog(#"ADD DROP MARKER");
[tNewMarkerView setImage:[UIImage imageNamed:#"greenmarker.png"]];
tNewMarkerView.draggable = YES;
}
else
{
NSLog(#"ADD NEW MARKER");
[tNewMarkerView setImage:[UIImage imageNamed:#"newMarker.png"]];
tNewMarkerView.draggable = NO;
}
tNewMarkerView.frame = CGRectMake(tNewMarkerView.frame.origin.x,tNewMarkerView.frame.origin.y,20,26);
tNewMarkerView.canShowCallout = YES;
tNewMarkerView.enabled = YES;
// callout button
UIButton *tButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
tNewMarkerView.rightCalloutAccessoryView = tButton;
// cover art and title/subtitle
UIButton *tCover = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if(tAnnotation.audioLocationVORef.trackVO==nil)
{
tAnnotation.title = #"Drop a Track";
tAnnotation.subtitle = #"Choose a track to drop";
[tCover setImage:[UIImage imageNamed:#"preCover.png"] forState:UIControlStateNormal];
}
else
{
tAnnotation.title = tAnnotation.audioLocationVORef.trackVO.songTitle;
tAnnotation.subtitle = tAnnotation.audioLocationVORef.trackVO.artist;
NSLog(#"ADD DATA MARKER %#", tAnnotation.title);
if(tAnnotation.audioLocationVORef.state==ANNOTATION_STATE_DROPPING){
tAnnotation.subtitle = #"Touch submit to Drop";
}
[tCover setImage:[tAnnotation.audioLocationVORef.trackVO getCoverArt] forState:UIControlStateNormal];
}
// make cover enabled to see song detail?
tCover.enabled = NO;
tNewMarkerView.leftCalloutAccessoryView = tCover;
[tCover release];
return tNewMarkerView;
}
return nil;
}
I tried to delete and add again the graphics as assets. I have been playing around a bit with the frame property. So far no luck.
And why the difference between simulator and device. I am using SDK 4.2... on iPhone 4

Make sure the image filenames match exactly with the resource names including upper/lower-case.
For example, if the resource is "GreenMarker.png", then "greenmarker.png" will only work on the simulator and not on the device.
Apple QA1697 (Why doesn't my device load a file that loads fine in the Simulator?) says:
Case-sensitivity: iPhone OS uses a
case-sensitive file system, unlike the
Simulator which uses a
case-insensitive file system by
default. Make sure the
case-sensitivity of resources accessed
within code matches the filename
case-sensitivity.

True, they should, but the Mac is case-preserving but also case-insensitive. The simulator runs on the Mac, so that's what you get.

Related

Can't be changed pin color when I change segement in mkMapView

I have some problem regarding Annotations in mapView. Lets have one glance on my requirement.
I want to give choice to user to choose location for meeting.
There are two options.
1) I should give list of near by data
Or
2) He can drag and drop pin anywhere he wants !
For that I have created one segment.
First index for near by data
and
Second index for dropping a pin.
For First option ("near by") I need to fetch near by data from location of Seller, location of Buyer and midpoint between seller and buyer. So I call google api and get data by passing latitude and longitude three times. There is no issue when I get data first time. My array fill up with all data (included 3 responses) and pin color also changes as per requirement.
Buyer (Red Color)
Seller ( Purple)
Mid Point (Green)
Now when I click on drop pin all data are removed from array and one pin is dropped on map.
Till now it works fine !
But when you again click on "near by", Problem starts ! No doubt it gives me data as I want but pin colors don't maintained.
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([segmentND selectedSegmentIndex]==0) {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString* BridgeAnnotationIdentifier = #"bridgeAnnotationIdentifier";
MKPinAnnotationView* pinView = (MKPinAnnotationView *)
[myMapView dequeueReusableAnnotationViewWithIdentifier:BridgeAnnotationIdentifier];
if (!pinView)
{
MKPinAnnotationView* customPinView = [[[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:BridgeAnnotationIdentifier] autorelease];
switch (self.pinColor) {
case 0:
{
customPinView.pinColor = MKPinAnnotationColorPurple;
}
break;
case 1:
{
customPinView.pinColor = MKPinAnnotationColorRed;
}
break;
case 2:
{
customPinView.pinColor = MKPinAnnotationColorGreen;
}
break;
default:
break;
}
customPinView.canShowCallout = YES;
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
else {
// Code of dragging dropping pin. It works Fine.s
}
}
I am attaching image for more idea.
Please give me solution or any another way to implement it. Remember Pin color is compulsory to differentiate Seller Buyer and Midpoint !
The trouble with your current approach is that self.pinColor doesn't change according to which annotation the map is needing a view for. It can and will call viewForAnnotation when ever it feels like it. Maybe the map has been scrolled around and one pin has just come back into view. Maybe the app was put into the background and is just being brought back into view by the user. What ever the reason you need to analyze the annotation is is passing in to determine which pin colour to use in the view. What object are you using for your annotation? If it was HSAnno and it had a property called pinColor you'd do something like this instead of your switch statement.
HSAnno* currentAnno = (HSAnno *)annotation;
pinView.pinColor = currentAnno.pinColor;
That way no matter what annotation needed to be redrawn viewForAnnotation would always return the right coloured pin.
You set your pincolor in that code part when no reusable pin exists.
if (!pinView)
....
customPinView.pinColor = MKPinAnnotationColorPurple;
....
}
When viewForAnnotation is called and reusable pins are found they are used. There is where the wrong color pin is taken.
Set your pincolor in the
else
{
pinView.annotation = annotation;
}
part and it should work fine.
Think its got mistaken here
else
{
pinView.annotation = annotation;
}
return pinView;
Correct it to
else
{
pinView.annotation = annotation;
return pinView;
}
Here's I little modified your code & MKMapView delegate. Previously you've changing pin color inside if that make it to call only for once when first time MKMapView loads.
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString* BridgeAnnotationIdentifier = #"bridgeAnnotationIdentifier";
MKPinAnnotationView* pinView = (MKPinAnnotationView *)
[mapView dequeueReusableAnnotationViewWithIdentifier:BridgeAnnotationIdentifier];
if (!pinView)
{
pinView = [[[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:BridgeAnnotationIdentifier] autorelease];
}
else
{
pinView.annotation = annotation;
}
switch (self.pinColor) {
case 0:
{
pinView.pinColor = MKPinAnnotationColorPurple;
}
break;
case 1:
{
pinView.pinColor = MKPinAnnotationColorRed;
}
break;
case 2:
{
pinView.pinColor = MKPinAnnotationColorGreen;
}
break;
default:
break;
pinView.canShowCallout = YES;
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
pinView.rightCalloutAccessoryView = rightButton;
return pinView;
}
return pinView;
}
P.S. I've removed your first UISegment condition to check at my side, please add it as is, when you implement.

How to update an object's image in NSMutableArray?

I am trying to set the image of the face down card to the image that the value of the card is. The method in KCCard, image:, returns the image of the card.
- (UIImage *)image:(BOOL)yesOrNo
{
if (!yesOrNo) {
return [UIImage imageNamed:#"back-blue-150-3.png"];
} else {
return [UIImage imageNamed:[NSString stringWithFormat:#"%#-%#-150", [self suitAsString], [self valueAsString]]];
}
}
The code I am using in the deal method is as follows.
int lastDealerX = 437;
//int lastDealerY = 49;
int lastDealerTag = 0;
for (KCCard *aCard in dealerHand) {
if (lastDealerTag == 0) {
KCCardView *cardView = [[KCCardView alloc] initWithFrame:CGRectMake(lastDealerX, 49, 150, 215)];
cardView.backgroundColor = [UIColor blackColor];
cardView.image = [aCard image:NO];
cardView.tag = lastDealerTag;
[self.view addSubview:cardView];
lastDealerTag = lastDealerTag + 1;
lastDealerX = lastDealerX + 42;
} else {
KCCardView *cardView = [[KCCardView alloc] initWithFrame:CGRectMake(lastDealerX, 49, 150, 215)];
cardView.backgroundColor = [UIColor blackColor];
cardView.image = [aCard image:YES];
cardView.tag = lastDealerTag;
[self.view addSubview:cardView];
lastDealerTag = lastDealerTag + 1;
lastDealerX = lastDealerX + 42;
}
}
The KCCardView with tag 0 shows the card face down and the other card is face up. The problem is that when I want the face down card to show, it won't. Here is the show code.
- (IBAction)showCard:(id)sender {
for (UIView *view in self.view.subviews) {
for (KCCard *aCard in dealerHand) {
KCCardView *cardView = (KCCardView *)view;
if (cardView.tag == 0) {
cardView.image = [[dealerHand objectAtIndex:0] image:YES];
}
}
}
}
KCCard is an NSObject, KCCardView is a UIImageView, and dealerHand is an NSMutableArray.
Here is a video showing the build and run: http://aleckazarian.com/misc/Blackjack.mov
Here is the XCode project: http://aleckazarian.com/misc/Blackjack.zip
If you look at the connection in the nib you'll notice that it is connected to
showCard
this is a completely different method to
showCard:
In your class you implement - (IBAction)showCard:(id)sender; therefore you need to break the connection in Interface builder and reconnect it.
Update
The second time I ran your program I got
-[UIRoundedRectButton setImage:]: unrecognized selector sent to instance 0x68612e0
This looks like it's because you are iterating over the view's subviews and checking if 0 == tag. 0 is the default value for tag so essentially mostly every view will respond true unless you have explicitly set the tags to something else. The problem code it
for (UIView *view in self.view.subviews) {
for (KCCard *aCard in dealerHand) {
KCCardView *cardView = (KCCardView *)view;
if (cardView.tag == 0) { // <------- This is the bad check
cardView.image = [((KCCard *)[dealerHand objectAtIndex:0]) image:YES];
}
}
}
To fix this either do one of these (they are in order of my preference - I wouldn't go near 3 or 4 in this case):
Keep a reference to the cardView's in an array
Give the cardView's a non zero tag when they are created
Use respondsToSelector:
Test for the class `[cardView isKindOf:[UIButton class]];
The compiler does not know, what kind of object [dealerHand objectAtIndex:0] is, thus it cannot respond to image:. Try this:
cardView.image = [((KCCard *)[dealerHand objectAtIndex:0]) image:YES];

Show hotels (and near by places) by appropriate icon on Map in iPhone App

I am using Google places API for getting places near by the predefined location.
It works fine. I am supposed to show PushPins for each place. For now, I am using default red pushpins for each place.
Now, I want to show appropriate icon for each place, for eg;
for Hotels, Restaurants, etc....
On android, my colleague developers do the same thing, by using the Google API response.
In iPhone, I am not able to find any such help. Is there any way to do that on iPhone ???
This might not address your question. But, some work-around.
I have came across the similar problem.
I did categorized the data, and showed notations according to that, along with the condition.
Hope this code gives you some idea about how i did it.
-(MKAnnotationView*)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKPinAnnotationView *view = nil;
if (annotation != mapView.userLocation) {
view = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"myAnnotationIdentifier"];
if (!view) {
view = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:#"myAnnotationIdentifier"];
CustomClassAnnotation *desclosureButton = [[CustomClassAnnotation alloc] initWithFrame:CGRectMake(0, 0, 29, 31)];
[desclosureButton addTarget:self action:#selector(mapAction:) forControlEvents:(UIControlEventTouchUpInside)];
view.rightCalloutAccessoryView = desclosureButton;
view.canShowCallout = YES;
}
((CustomClassAnnotation *)view.rightCalloutAccessoryView).annotation = annotation;
if (((MapViewAnnotation *)annotation).type == 1) {
view.image = [UIImage imageNamed:#"image_type1.png"];
}
else if (((MapViewAnnotation *)annotation).type == 2) {
view.image = [UIImage imageNamed:#"image_type2.png"];
}
else if (((MapViewAnnotation *)annotation).type == 3) {
view.image = [UIImage imageNamed:#"image_type3.png"];
}
}
return view;
}
Best of luck...

UIWebView - Enabling Action Sheets on <img> tags

Is it just me or has the action sheet on <img> tags been disabled in UIWebView? In Safari, e.g, when you want to save an image locally, you touch and hold on the image to get an action sheet shown. But it's not working in my custom UIWebView. I mean, it is still working for <a> tags, i.e, when I touch and hold on html links, an action sheet shows up. But not for the <img> tags.
I've tried things like putting img { -webkit-touch-callout: inherit; } in css, which didn't work. On the other hand, when I double-tap and hold on the images, a copy-balloon shows up.
So the question is, has the default action sheet callout for <img> tags been disabled for UIWebView? Is so, is there a way to re-enable it? I've googled around and saw many Q&As on how to disable it in UIWebView, so is it just me who aren't seeing the popup?
Thanks in advance!
Yes apple has disabled this feature (among others) in UIWebViews and kept it for Safari only.
However you can recreate this yourself by extending this tutorial, http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/.
Once you've finished this tutorial you'll want to add a few extra's so you can actually save images (which the tutorial doesn't cover).
I added an extra notification called #"tapAndHoldShortNotification" after 0.3 seconds which calls a method with just the disable callout code in it (to prevent both the default and your own menu popping while the page is still loading, a little bug fix).
Also to detect images you'll need to extend the JSTools.js, here's mine with the extra functions.
function MyAppGetHTMLElementsAtPoint(x,y) {
var tags = ",";
var e = document.elementFromPoint(x,y);
while (e) {
if (e.tagName) {
tags += e.tagName + ',';
}
e = e.parentNode;
}
return tags;
}
function MyAppGetLinkSRCAtPoint(x,y) {
var tags = "";
var e = document.elementFromPoint(x,y);
while (e) {
if (e.src) {
tags += e.src;
break;
}
e = e.parentNode;
}
return tags;
}
function MyAppGetLinkHREFAtPoint(x,y) {
var tags = "";
var e = document.elementFromPoint(x,y);
while (e) {
if (e.href) {
tags += e.href;
break;
}
e = e.parentNode;
}
return tags;
}
Now you can detect the user clicking on images and actually find out the images url they are clicking on, but we need to change the -(void)openContextualMenuAtPoint: method to provide extra options.
Again here's mine (I tried to copy Safari's behaviour for this):
- (void)openContextualMenuAt:(CGPoint)pt{
// Load the JavaScript code from the Resources and inject it into the web page
NSString *path = [[NSBundle mainBundle] pathForResource:#"JSTools" ofType:#"js"];
NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
[webView stringByEvaluatingJavaScriptFromString:jsCode];
// get the Tags at the touch location
NSString *tags = [webView stringByEvaluatingJavaScriptFromString:
[NSString stringWithFormat:#"MyAppGetHTMLElementsAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];
NSString *tagsHREF = [webView stringByEvaluatingJavaScriptFromString:
[NSString stringWithFormat:#"MyAppGetLinkHREFAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];
NSString *tagsSRC = [webView stringByEvaluatingJavaScriptFromString:
[NSString stringWithFormat:#"MyAppGetLinkSRCAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
selectedLinkURL = #"";
selectedImageURL = #"";
// If an image was touched, add image-related buttons.
if ([tags rangeOfString:#",IMG,"].location != NSNotFound) {
selectedImageURL = tagsSRC;
if (sheet.title == nil) {
sheet.title = tagsSRC;
}
[sheet addButtonWithTitle:#"Save Image"];
[sheet addButtonWithTitle:#"Copy Image"];
}
// If a link is pressed add image buttons.
if ([tags rangeOfString:#",A,"].location != NSNotFound){
selectedLinkURL = tagsHREF;
sheet.title = tagsHREF;
[sheet addButtonWithTitle:#"Open"];
[sheet addButtonWithTitle:#"Copy"];
}
if (sheet.numberOfButtons > 0) {
[sheet addButtonWithTitle:#"Cancel"];
sheet.cancelButtonIndex = (sheet.numberOfButtons-1);
[sheet showInView:webView];
}
[selectedLinkURL retain];
[selectedImageURL retain];
[sheet release];
}
(NOTES: selectedLinkURL and selectedImageURL are declared in the .h file to let them be accessed throughout the class, for saving or opening the link latter.
So far we've just been going back over the tutorials code making changes but now we will move into what the tutorial doesn't cover (it stops before actually mentioning how to handle saving the images or opening the links).
To handle the users choice we now need to add the actionSheet:clickedButtonAtIndex: method.
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:#"Open"]){
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:selectedLinkURL]]];
}
else if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:#"Copy"]){
[[UIPasteboard generalPasteboard] setString:selectedLinkURL];
}
else if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:#"Copy Image"]){
[[UIPasteboard generalPasteboard] setString:selectedImageURL];
}
else if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:#"Save Image"]){
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(saveImageURL:) object:selectedImageURL];
[queue addOperation:operation];
[operation release];
}
}
This checks what the user wants to do and handles /most/ of them, only the "save image" operation needs another method to handle that. For the progress I used MBProgressHub.
Add an MBProgressHUB *progressHud; to the interface declaration in the .h and set it up in the init method (of whatever class you're handling the webview from).
progressHud = [[MBProgressHUD alloc] initWithView:self.view];
progressHud.customView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Tick.png"]] autorelease];
progressHud.opacity = 0.8;
[self.view addSubview:progressHud];
[progressHud hide:NO];
progressHud.userInteractionEnabled = NO;
And the -(void)saveImageURL:(NSString*)url; method will actually save it to the image library.
(A better way would be to do the download through an NSURLRequest and update the progress hud in MBProgressHUDModeDeterminate to deflect how long it'll actually take to download, but this is a more hacked together implementation then that)
-(void)saveImageURL:(NSString*)url{
[self performSelectorOnMainThread:#selector(showStartSaveAlert) withObject:nil waitUntilDone:YES];
UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]], nil, nil, nil);
[self performSelectorOnMainThread:#selector(showFinishedSaveAlert) withObject:nil waitUntilDone:YES];
}
-(void)showStartSaveAlert{
progressHud.mode = MBProgressHUDModeIndeterminate;
progressHud.labelText = #"Saving Image...";
[progressHud show:YES];
}
-(void)showFinishedSaveAlert{
// Set custom view mode
progressHud.mode = MBProgressHUDModeCustomView;
progressHud.labelText = #"Completed";
[progressHud performSelector:#selector(hide:) withObject:[NSNumber numberWithBool:YES] afterDelay:0.5];
}
And of cause add [progressHud release]; to the dealloc method.
Hopefully this shows you how to add some of the options to a webView that apple left out.
Of cause though you can add more things to this like a "Read Later" option for instapaper or a "Open In Safari" button.
(looking at the length of this post I'm seeing why the original tutorial left out the finial implementation details)
Edit: (updated with more info)
I was asked about the detail I glossed over at the top, the #"tapAndHoldShortNotification", so this is clarifying it.
This is my UIWindow subclass, it adds the second notification to cancel the default selection menu (this is because when I tried the tutorial it showed both menus).
- (void)tapAndHoldAction:(NSTimer*)timer {
contextualMenuTimer = nil;
UIView* clickedView = [self hitTest:CGPointMake(tapLocation.x, tapLocation.y) withEvent:nil];
while (clickedView != nil) {
if ([clickedView isKindOfClass:[UIWebView class]]) {
break;
}
clickedView = clickedView.superview;
}
if (clickedView) {
NSDictionary *coord = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:tapLocation.x],#"x",
[NSNumber numberWithFloat:tapLocation.y],#"y",nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"TapAndHoldNotification" object:coord];
}
}
- (void)tapAndHoldActionShort:(NSTimer*)timer {
UIView* clickedView = [self hitTest:CGPointMake(tapLocation.x, tapLocation.y) withEvent:nil];
while (clickedView != nil) {
if ([clickedView isKindOfClass:[UIWebView class]]) {
break;
}
clickedView = clickedView.superview;
}
if (clickedView) {
NSDictionary *coord = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:tapLocation.x],#"x",
[NSNumber numberWithFloat:tapLocation.y],#"y",nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"TapAndHoldShortNotification" object:coord];
}
}
- (void)sendEvent:(UIEvent *)event {
NSSet *touches = [event touchesForWindow:self];
[touches retain];
[super sendEvent:event]; // Call super to make sure the event is processed as usual
if ([touches count] == 1) { // We're only interested in one-finger events
UITouch *touch = [touches anyObject];
switch ([touch phase]) {
case UITouchPhaseBegan: // A finger touched the screen
tapLocation = [touch locationInView:self];
[contextualMenuTimer invalidate];
contextualMenuTimer = [NSTimer scheduledTimerWithTimeInterval:0.8 target:self selector:#selector(tapAndHoldAction:) userInfo:nil repeats:NO];
NSTimer *myTimer;
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(tapAndHoldActionShort:) userInfo:nil repeats:NO];
break;
case UITouchPhaseEnded:
case UITouchPhaseMoved:
case UITouchPhaseCancelled:
[contextualMenuTimer invalidate];
contextualMenuTimer = nil;
break;
}
} else { // Multiple fingers are touching the screen
[contextualMenuTimer invalidate];
contextualMenuTimer = nil;
}
[touches release];
}
The notification is then handled like this:
// in -viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(stopSelection:) name:#"TapAndHoldShortNotification" object:nil];
- (void)stopSelection:(NSNotification*)notification{
[webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.style.webkitTouchCallout='none';"];
}
It's only a little change but it fixes the annoying little bug where you get 2 menus appear (the standard one and yours).
Also you could easily add iPad support by sending the touches location as the notification fires and then showing the UIActionSheet from that point, though this was written before the iPad so doesn't include support for that.
After struggling for, like 2 or 3 days non-stop on this problem, it seems like the position is computed "relatively" to the UIWebView's "TOP-LEFT" corner (I am programing for iOS 7).
So, to make this work, when you get the position, on the controller where your WebView is (i'll put a snippet of my code below), don't add the "scroll-offset"
SNIPPET - ContextualMenuAction:
- (void)contextualMenuAction:(NSNotification*)notification {
// Load javascript
[self loadJavascript];
// Initialize the coordinates
CGPoint pt;
pt.x = [[[notification object] objectForKey:#"x"] floatValue];
pt.y = [[[notification object] objectForKey:#"y"] floatValue];
// Convert point from window to view coordinate system
pt = [self.WebView convertPoint:pt fromView:nil];
// Get PAGE and UIWEBVIEW dimensions
CGSize pageDimensions = [self.WebView documentSize];
CGSize webviewDimensions = self.WebView.frame.size;
/***** If the page is in MOBILE version *****/
if (webviewDimensions.width == pageDimensions.width) {
}
/***** If the page is in DESKTOP version *****/
else {
// convert point from view to HTML coordinate system
CGSize viewSize = [self.WebView frame].size;
// Contiens la portion de la page visible depuis la webview (en fonction du zoom)
CGSize windowSize = [self.WebView windowSize];
CGFloat factor = windowSize.width / viewSize.width;
CGFloat factorHeight = windowSize.height / viewSize.height;
NSLog(#"factor: %f", factor);
pt.x = pt.x * factor; // ** logically, we would add the offset **
pt.y = pt.y * factorHeight; // ** logically, we would add the offset **
}
NSLog(#"x: %f and y: %f", pt.x, pt.y);
NSLog(#"WINDOW: width: %f height: %f", [self.WebView windowSize].width, [self.WebView windowSize].height);
NSLog(#"DOCUMENT: width: %f height: %f", pageDimensions.width, pageDimensions.height);
[self openContextualMenuAt:pt];
}
SNIPPET - in openContextualMenuAt:
To load the correct JS function:
- (void)openContextualMenuAt:(CGPoint)pt {
// Load javascript
[self loadJavascript];
// get the Tags at the touch location
NSString *tags = [self.WebView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"getHTMLTagsAtPoint(%li,%li);",(long)pt.x,(long)pt.y]];
...
}
SNIPPET - in JSTools.js:
This is the function I use to get the element touched
function getHTMLTagsAtPoint(x,y) {
var tags = ",";
var element = document.elementFromPoint(x,y);
while (element) {
if (element.tagName) {
tags += element.tagName + ',';
}
element = element.parentNode;
}
return tags;
}
SNIPPET - loadJavascript
I use this one to inject my JS code in the webview
-(void)loadJavascript {
[self.WebView stringByEvaluatingJavaScriptFromString:
[NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"JSTools" ofType:#"js"] encoding:NSUTF8StringEncoding error:nil]];
}
This part (everything I did to overrride the default UIActionSheet) is HEAVILY (should I say completely) based on
this post
#Freerunning's answer is complete (i did almost everything he said in my other classes, like on the post my code is based on), the snippets i posted is just to show you more "completely" how my code is.
Hope this helps! ^^
First of all thanks to Freerunnering for the great solution!
But you can do this with an UILongPressGestureRecognizer instead of a custom LongPressRecognizer. This makes things a bit easier to implement:
In the Viewcontroller Containing the webView:
Add UIGestureRecognizerDelegate to your ViewController
let mainJavascript = "function MyAppGetHTMLElementsAtPoint(x,y) { var tags = \",\"; var e = document.elementFromPoint(x,y); while (e) { if (e.tagName) { tags += e.tagName + ','; } e = e.parentNode; } return tags; } function MyAppGetLinkSRCAtPoint(x,y) { var tags = \"\"; var e = document.elementFromPoint(x,y); while (e) { if (e.src) { tags += e.src; break; } e = e.parentNode; } return tags; } function MyAppGetLinkHREFAtPoint(x,y) { var tags = \"\"; var e = document.elementFromPoint(x,y); while (e) { if (e.href) { tags += e.href; break; } e = e.parentNode; } return tags; }"
func viewDidLoad() {
...
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(CustomViewController.longPressRecognizerAction(_:)))
self.webView.scrollView.addGestureRecognizer(longPressRecognizer)
longPressRecognizer.delegate = self
...
}
func longPressRecognizerAction(sender: UILongPressGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Began {
let tapPostion = sender.locationInView(self.webView)
let tags = self.webView.stringByEvaluatingJavaScriptFromString("MyAppGetHTMLElementsAtPoint(\(tapPostion.x),\(tapPostion.y));")
let href = self.webView.stringByEvaluatingJavaScriptFromString("MyAppGetLinkHREFAtPoint(\(tapPostion.x),\(tapPostion.y));")
let src = self.webView.stringByEvaluatingJavaScriptFromString("MyAppGetLinkSRCAtPoint(\(tapPostion.x),\(tapPostion.y));")
print("tags: \(tags)\nhref: \(href)\nsrc: \(src)")
// handle the results, for example with an UIDocumentInteractionController
}
}
// Without this function, the customLongPressRecognizer would be replaced by the original UIWebView LongPressRecognizer
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
And thats it!

iPhone Developer's Cookbook: ModalAlert Frozen

I've used a recipe from the iPhone Developer's Cookbook called ModalAlert in order to get some text from a user; however, when the alert is shown, the keyboard and buttons are frozen. Here is the code for the modal alert.
+(NSString *) textQueryWith: (NSString *)question prompt: (NSString *)prompt button1: (NSString *)button1 button2:(NSString *) button2
{
// Create alert
CFRunLoopRef currentLoop = CFRunLoopGetCurrent();
ModalAlertDelegate *madelegate = [[ModalAlertDelegate alloc] initWithRunLoop:currentLoop];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:question message:#"\n" delegate:madelegate cancelButtonTitle:button1 otherButtonTitles:button2, nil];
// Build text field
UITextField *tf = [[UITextField alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 260.0f, 30.0f)];
tf.borderStyle = UITextBorderStyleRoundedRect;
tf.tag = TEXT_FIELD_TAG;
tf.placeholder = prompt;
tf.clearButtonMode = UITextFieldViewModeWhileEditing;
tf.keyboardType = UIKeyboardTypeAlphabet;
tf.keyboardAppearance = UIKeyboardAppearanceAlert;
tf.autocapitalizationType = UITextAutocapitalizationTypeWords;
tf.autocorrectionType = UITextAutocorrectionTypeNo;
tf.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
// Show alert and wait for it to finish displaying
[alertView show];
while (CGRectEqualToRect(alertView.bounds, CGRectZero));
// Find the center for the text field and add it
CGRect bounds = alertView.bounds;
tf.center = CGPointMake(bounds.size.width / 2.0f, bounds.size.height / 2.0f - 10.0f);
[alertView addSubview:tf];
[tf release];
// Set the field to first responder and move it into place
[madelegate performSelector:#selector(moveAlert:) withObject:alertView afterDelay: 0.7f];
// Start the run loop
CFRunLoopRun();
// Retrieve the user choices
NSUInteger index = madelegate.index;
NSString *answer = [[madelegate.text copy] autorelease];
if (index == 0) answer = nil; // assumes cancel in position 0
[alertView release];
[madelegate release];
return answer;
}
Thanks!
You should probably check whether a UITextField's userInteractionEnabled property defaults to YES or NO.
// Put the modal alert inside a new thread. This happened to me before, and this is how i fixed it.
- (void)SomeMethod {
[NSThread detachNewThreadSelector:#selector(CheckCurrentPuzzle) toTarget:self withObject:nil]; }
-(void) CheckCurrentPuzzle {
NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];
// code that should be run in the new thread goes here
if ([gameBoard AreAllCellsFilled]) {
if ([gameBoard FilledWithoutWin]) {
//only show this message once per puzzle
if (![currentPuzzle showedRemovalMessage]) {
NSArray *buttons = [NSArray arrayWithObject:#"Yes"];
if ([ModalAlert ask:#"blah blah blah" withTitle:#"Incomplete Puzzle" withCancel:#"No" withButtons:buttons] == 1) {
NSLog(#"Remove The Incorrect Cells");
[gameBoard RemoveIncorrect];
} else {
[gameSounds.bloop2 play];
}
}
} else {
if ([gameBoard IsBoardComplete]) {
[self performSelectorOnMainThread:#selector(WINNER) withObject:nil waitUntilDone:false];
}
}
}
[pool2 release];
}
-(void) WINNER {
//ladies and gentleman we have a winner
}
I had a problem similar to this in my educational game QPlus. It bugged me because I had the "exact" same code in two related apps, and they did not have the bug. It turned out that the bug was because the selector method was not declared in the header file. I am working in Xcode 4.2.
Details below:
In .m:
tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(emailLabelPressed)];
tapRecognizer.numberOfTapsRequired = 1;
[aLabel addGestureRecognizer:tapRecognizer];
[aLabel setUserInteractionEnabled:YES];
And later in the .m:
(void)emailLabelPressed {
//details
}
That works just fine in the simulator, but on an actual device the email interface presented modally will not edit. You can send or save as draft but no editing.
Then add this to the .h file:
(void)emailLabelPressed;
And voila, it works on the device. Of course this was the difference with the related apps - they both had the method declared in the header file. I would classify this as an iOS bug, but being such a novice developer I wouldn't presume to know.
Based on this, you may want to verify that your selector method moveAlert: is declared in your header file.
Enjoy,
Damien