didFailToReceiveAdWithError never fires - iphone

This is my first iAd for iPhone.
In development mode, if I switch my iPhone to airport mode, my app being debugged never ever gets this event.
But, if I start app with airport off, I get the 'bannerViewDidLoadAd' event okay. And if airport turned on -- never get didFailToReceiveAdWithError.
#interface ViewController : UIViewController <ADBannerViewDelegate> {
ADBannerView* adView;
}
#property(nonatomic, retain) IBOutlet ADBannerView *adView;
...
- (void)viewDidLoad
{
... (adView is from Interface Builder )
adView.requiredContentSizeIdentifiers = [NSSet setWithObject:ADBannerContentSizeIdentifier320x50];
adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier320x50;
[self.view addSubview:adView];
adView.delegate=self;
[super viewDidLoad];
}
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
NSLog(#"bannerViewDidLoadAd");
if ( adView.hidden )
{
NSLog(#"going visible");
[UIView beginAnimations:#"animateAdBannerOn" context:NULL];
adView.hidden = NO;
// banner is invisible now and moved out of the screen on 50 px
//banner.frame = CGRectOffset(banner.frame, 0, 50);
[UIView commitAnimations];
}
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
NSLog(#"didFailToReceiveAdWithError");
if( !adView.hidden ) // ad banner displayed, but lost ad network
{
NSLog(#"going hidden");
[UIView beginAnimations:#"animateAdBannerOff" context:NULL];
adView.hidden = YES;
// banner is visible and we move it out of the screen, due to connection issue
//banner.frame = CGRectOffset(banner.frame, 0, -50);
[UIView commitAnimations];
}
}

The only time
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
is called is when an ad is already displayed and receives an error. When you are in airplane mode the initial ad is never displayed therefore this method is not called.
*Edit for clarity

If you check the Apple Developer Documentation you notice you have 2 options:
To assist you in validating your implementation, the iAd Network
occasionally returns errors to test your error handling code.
You can also test your error handling support manually by turning your device’s wireless capability off.
http://developer.apple.com/library/ios/#DOCUMENTATION/UserExperience/Conceptual/iAd_Guide/TestingiAdApplications/TestingiAdApplications.html#//apple_ref/doc/uid/TP40009881-CH6-SW1
Take into account that you can't turn off wireless for the iOS simulator. You need to disable the network connection of your development system:
IPhone Connectivity Testing: How do I force it to lose connection?

Related

UIAlertView doesn't slide up Automatically when keyboard appears on becomeFirstResponder

I have subclassed UIAlertView and inside which I am showing a textField which takes the input. When user clicks on textField the keyboard shows up and UIAlertView moves up to adjust for keyboard. But when I do [textField becomeFirstResponder] in didPresentAlertView delegate method of UIAlertView, the alertView doesn't moves up to adjust for keyboard. Instead the UIAlertView gets hidden behind the keyboard.
PS - I know that Apple says that UIAlertView should not be subclassed and to be used as it is, but I am subclassing UIAlertView because I want to redesign the Apple's default UI elements in it.
You should really not do something against Apple's recommendation.
Reasons
You can face unexpected issues like the one you are facing.
Your code can break for future iOS releases as you are violating recommendations.
Redesigning Apple's standard controls is in violation of HIG guidelines. As a result, there is a chance that your app can get rejected. Create your own instead, by using UIView subclass.
As an alternative, Apple has made provision in the UIAlertView for this requirement. You don't need to add a textfield to the alert view, instead, use the UIAlertView property alertViewStyle. It accepts values defined in the enum UIAlertViewStyle
typedef NS_ENUM(NSInteger, UIAlertViewStyle) {
UIAlertViewStyleDefault = 0,
UIAlertViewStyleSecureTextInput, // Secure text input
UIAlertViewStylePlainTextInput, // Plain text input
UIAlertViewStyleLoginAndPasswordInput // Two text fields, one for username and other for password
};
Example, lets assume a use case that you want to accept password from the user. The code to achieve this is as below.
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Please enter password"
message:nil
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Continue", nil];
[alert setAlertViewStyle:UIAlertViewStyleSecureTextInput];
[alert show];
To validate the input, lets say password entered must be minimum 6 characters, implement this delegate method,
- (BOOL)alertViewShouldEnableFirstOtherButton:(UIAlertView *)alertView
{
NSString *inputText = [[alertView textFieldAtIndex:0] text];
if( [inputText length] >= 6 )
{
return YES;
}
else
{
return NO;
}
}
To get the user input
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if([title isEqualToString:#"Login"])
{
UITextField *password = [alertView textFieldAtIndex:0];
NSLog(#"Password: %#", password.text);
}
}
To re-iterate,
UIAlertView has a private view hierarchy and it is recommended to use it as-is without modification. If you use it against recommendation you will get unexpected results.
From Apple docs
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
This is standard technique used even in iOS default apps (Ex: Entering Wi-Fi password, etc.), hence using this will ensure you don't face issues like the one you mention.
Hope that helps!
I do like this to upthe screen to show the textfiled. :-) Hope this help you.
- (void) textFieldDidBeginEditing:(UITextField *)textField {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.5];
[UIView setAnimationBeginsFromCurrentState:YES];
self.view.frame = CGRectMake(( self.view.frame.origin.x), (self.view.frame.origin.y-50 ), self.view.frame.size.width, self.view.frame.size.height);
[UIView commitAnimations];
}
- (void) textFieldDidEndEditing:(UITextField *)textField {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.5];
[UIView setAnimationBeginsFromCurrentState:YES];
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y+50 , self.view.frame.size.width, self.view.frame.size.height);
[UIView commitAnimations];
}

UIAlertView Rendering Error

I have been working on an app for a couple of months now, but have finally run into an issue that I can't solve myself, and can't find anything on the internet to help.
I am using several normal UIAlertViews, in my app. Some have 2 buttons, some have 3 buttons, and a couple have 2 buttons and a text field. However all have the same issue. When you call [someAlertView show]; the alert view appears as normal, but then suddenly its graphics context seems to get corrupted as you can see from the screenshot.
This happens on both iPhone and iPad simulators (both 5.0 and 5.1), and happens on an iPad and iPhone4S device as well.
The image showing through is whatever happens to be behind the alertView.
The Alert still works, I can click the buttons, type in the text field, then when it dismisses the delegate methods are called correctly and everything goes back to normal. When the alertView appears again, the same thing happens.
The view behind the alert is a custom UIScrollView subclass with a content size of approximately 4000 pixels by 1000 with a UIImage as the background. The png file is mostly transparent, so is only about 80kB in memory size, and the phone is having no issues rendering it - the scroll view is still fully responsive and not slow.
It also has a CADisplayLink timer attached to it as part of the subclass. I have tried disabling this just before the alertView is shown, but it makes no difference so I am doubtful that is the issue.
This app is a partial rewrite of one I made for a university project, and that one could display UIAlertViews over the top of a scrollView of the same size and subclass without issue. The difference between this app and that one is that in my old app, I had subclassed UIAlertView to add extra things such as a pickerView, however I decided that I didn't like the way it looked so moved everything out of the alert and am just sticking with a standard UIAlertView.
This is how the alertView in the screenshot is called:
- (IBAction)loadSimulation:(id)sender {
importAlert = [[UIAlertView alloc] initWithTitle:#"Load Simulation" message:#"Enter Filename:" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Load", nil];
[importAlert setAlertViewStyle:UIAlertViewStylePlainTextInput];
[importAlert showPausingSimulation:self.simulationView]; //Calling [importAlert show]; makes no difference.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
[self hideOrganiser]; //Not an issue as the problem occurs on iPad as well.
}
}
With this being the categorised AlertView to add the ability to stop the scrollViews CADisplay link.
#interface UIAlertView(pauseDisplayLink)
- (void)showPausingSimulation:(UILogicSimulatorView*)simulationView;
#end
#implementation UIAlertView(pauseDisplayLink)
- (void)showPausingSimulation:(UILogicSimulatorView *)simulationView {
[simulationView stopRunning];
[simulationView removeDisplayLink]; //displayLink needs to be removed from the run loop, otherwise it will keep going in the background and get corrupted.
[self show];
}
I get no memory warnings when this happens, so I am doubtful it is due to lack of resources.
Has anyone come across an issue like this before? If you need further information I can try to provide it, but I am limited in what code I can post. Any help would be appreciated, I've been trying to solve this for two weeks and can't figure it out.
Edit:
It appears that it is not the AlertView at all (or rather it is not just the alertView), as the problem goes away when I remove the scroll view behind it, so there must be some issue between the two. This is the code for my UIScrollView subclass:
.h file:
#import
#import
#class ECSimulatorController;
#interface UILogicSimulatorView : UIScrollView {
CADisplayLink *displayLink;
NSInteger _updateRate;
ECSimulatorController* _hostName;
}
#property (nonatomic) NSInteger updateRate;
#property (nonatomic, strong) ECSimulatorController* hostName;
- (void) removeDisplayLink;
- (void) reAddDisplayLink;
- (void) displayUpdated:(CADisplayLink*)timer;
- (void) startRunning;
- (void) stopRunning;
- (void) refreshRate:(NSInteger)rate;
- (void) setHost:(id)host;
- (void)setMinimumNumberOfTouches:(NSInteger)touches;
- (void)setMaximumNumberOfTouches:(NSInteger)touches;
#end
.m file:
#import "UILogicSimulatorView.h"
#import "ECSimulatorController.h"
#import <QuartzCore/QuartzCore.h>
#implementation UILogicSimulatorView
#synthesize updateRate = _updateRate;
#synthesize hostName = _hostName;
- (void)reAddDisplayLink {
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; //allows the display link to be re-added to the run loop after having been removed.
}
- (void)removeDisplayLink {
[displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; //allows the display link to be removed from the Run loop without deleting it. Removing it is essential to prevent corruption between the games and the simulator as both use CADisplay link, and only one can be in the run loop at a given moment.
}
- (void)startRunning {
[self refreshRate:self.updateRate];
[displayLink setPaused:NO];
}
- (void)refreshRate:(NSInteger)rate {
if (rate > 59) {
rate = 59; //prevent the rate from being set too an undefined value.
}
NSInteger frameInterval = 60 - rate; //rate is the number of frames to skip. There are 60FPS, so this converts to frame interval.
[displayLink setFrameInterval:frameInterval];
}
- (void)stopRunning {
[displayLink setPaused:YES];
}
- (void)displayUpdated:(CADisplayLink*)timer {
//call the function that the snakeController host needs to update
[self.hostName updateStates];
}
- (void)setHost:(ECSimulatorController*)host;
{
self.hostName = host; //Host allows the CADisplay link to call a selector in the object which created this one.
}
- (id)initWithFrame:(CGRect)frame
{
//Locates the UIScrollView's gesture recogniser
if(self = [super initWithFrame:frame])
{
[self setMinimumNumberOfTouches:2];
displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(displayUpdated:)]; //CADisplayLink will update the logic gate states.
self.updateRate = 1;
[displayLink setPaused:YES];
}
return self;
}
- (void)setMinimumNumberOfTouches:(NSInteger)touches{
for (UIGestureRecognizer *gestureRecognizer in [self gestureRecognizers])
{
if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
//Changes the minimum number of touches to 'touches'. This allows the UIPanGestureRecogniser in the object which created this one to work with one finger.
[(UIPanGestureRecognizer*)gestureRecognizer setMinimumNumberOfTouches:touches];
}
}
}
- (void)setMaximumNumberOfTouches:(NSInteger)touches{
for (UIGestureRecognizer *gestureRecognizer in [self gestureRecognizers])
{
if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
//Changes the maximum number of touches to 'touches'. This allows the UIPanGestureRecogniser in the object which created this one to work with one finger.
[(UIPanGestureRecognizer*)gestureRecognizer setMaximumNumberOfTouches:touches];
}
}
}
#end
Well, I have managed to come up a solution to this. Really it is probably just masking the issue rather than finding the route cause, but at this point I will take it.
First some code:
#interface UIView (ViewCapture)
- (UIImage*)captureView;
- (UIImage*)captureViewInRect:(CGRect)rect;
#end
#implementation UIView (ViewCapture)
- (UIImage*)captureView {
return [self captureViewInRect:self.frame];
}
- (UIImage*)captureViewInRect:(CGRect)rect
{
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return screenShot;
}
#end
- (void)showPausingSimulation:(UILogicSimulatorView *)simulationView {
[simulationView stopRunning];
UIView* superView = simulationView.superview;
CGPoint oldOffset = simulationView.contentOffset;
for (UIView* subview in simulationView.subviews) {
//offset subviews so they appear when content offset is (0,0)
CGRect frame = subview.frame;
frame.origin.x -= oldOffset.x;
frame.origin.y -= oldOffset.y;
subview.frame = frame;
}
simulationView.contentOffset = CGPointZero; //set the offset to (0,0)
UIImage* image = [simulationView captureView]; //Capture the frame of the scrollview
simulationView.contentOffset = oldOffset; //restore the old offset
for (UIView* subview in simulationView.subviews) {
//Restore the original positions of the subviews
CGRect frame = subview.frame;
frame.origin.x += oldOffset.x;
frame.origin.y += oldOffset.y;
subview.frame = frame;
}
[simulationView setHidden:YES];
UIImageView* imageView = [[UIImageView alloc] initWithFrame:simulationView.frame];
[imageView setImage:image];
[imageView setTag:999];
[superView addSubview:imageView];
[imageView setHidden:NO];
superView = nil;
imageView = nil;
image = nil;
[self show];
}
- (void)dismissUnpausingSimulation:(UILogicSimulatorView *)simulationView {
UIView* superView = simulationView.superview;
UIImageView* imageView = (UIImageView*)[superView viewWithTag:999];
[imageView removeFromSuperview];
imageView = nil;
superView = nil;
[simulationView setHidden:NO];
[simulationView startRunning];
}
Then modifying the dismiss delegate method in my class to have this line:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
[alertView dismissUnpausingSimulation:self.simulationView];
...
When the alert view is called, but before it is shown, I need to hide the simulator to prevent it corrupting the alert. However just hiding it is ugly as then all is visible behind is a empty view.
To fix this, I first make a UIImage from the simulator views graphics context. I then create a UIImageView with the same frame as the simulator and set the UIImage as its image.
I then hide the simulator view (curing the alert issue), and add my new UIImageView to the simulators superview. I also set the tag of the image view so I can find it later.
When the alert dismisses, the image view is then recovered based on its tag, and removed from its superview. The simulator is then unhidden.
The result is that the rendering issue is gone.
I know its too late for an answer to this question. Lately I had experianced this very same issue.
My Case:
Added couple of custom UIViews with background images and some controlls to the scroll view with shadow effect. I had also set the shadowOffset.
The Solution:
After some step by step analysis, I found out that setting the setShadowOpacity caused The rendering problem for me. When i commented that line of code, it cured the UIAlertView back to normal appearance.
More:
To make sure, I created a new project mimicing the original ui with shadowOpacity. But it didnt caused the rendering problem as i expected. So I am not sure about the root cause. For me it was setShadowOpacity.

Strange memory leak in Window:addSubView

first of all sorry for my English :-) not so good.
I have a strange memory leak with the following code (code after the explanation).
I have a class, FLWaitingView. It is a simple view with a waiting indicator (plus a view with background), used to say to the user "wait for the data to be loaded".
It has two simple methods: show and dismiss.
In the show method, I find the main Application Window and add the subviews (the waiting view and a background view, with different animations). In the dismiss method, I remove it from superview.
In every show, I verify that the view isn't already visible using a static bool var (is_visible).
The strange thing is this: In the dismiss method, I use:
[self.view removeFromSuperview];
[self.waitingView removeFromSuperview];
to remove the two views from the Window, to avoid them to be retained. They are correctly removed, I can verify this with NSLog (for cicle on each window subview). But, in INSTRUMENTS, using the "mark heap" function, I see that in every single reload (new instance of FLWaitingView, then show, then dismiss) the old instance remains in memory and continues to increase memory usage. Obviously is not a problem of the calling code, because I correctly release the object:
//CALLING CODE
//customWaitingView is a property retained
self.customWaitingView = [[[FLWaitingView alloc]init]autorelease];
[self.customWaitingView show];
Moreover, and I think that this is the most important information, if I move the view dismission in another method, called by a selector, the leak disappear!!!
Now I show the "wrong" code and, after, the "correction". I would like to understand why it happens.
- (void)show
{
if (!is_visible){
id appDelegate = [[UIApplication sharedApplication] delegate];
UIWindow *window = [appDelegate window];
self.waitingLabel.text = #"Attendere";
self.view.alpha = 1.0;
self.waitingView.alpha = 1.0;
[window addSubview:self.view];
[window addSubview:self.waitingView];
[self.waitingIndicator startAnimating];
self.view.frame = window.frame;
self.waitingView.center = window.center;
// "Pop in" animation for alert
[self doPopInAnimationWithDelegate:self];
// "Fade in" animation for background
[self doFadeInAnimation];
is_visible = YES;
} else {
NSLog(#"FLWaitingView %# already visible, do nothing", self);
}
}
- (void)dismiss
{
[UIView beginAnimations:nil context:nil];
self.view.alpha = 0.0;
self.waitingView.alpha = 0.0;
[UIView commitAnimations];
[self.waitingIndicator stopAnimating];
//here is the problem
[self.view removeFromSuperview];
[self.waitingView removeFromSuperview];
is_visible = NO;
}
the code above is the "wrong" one, but if I add
[self performSelector:#selector(alertDidFadeOut) withObject:nil afterDelay:0.5];
in the dismiss method and a new method (obviously removing the redundant code from dismiss method):
- (void)alertDidFadeOut
{
//here the memory is correctly released
[self.view removeFromSuperview];
[self.waitingView removeFromSuperview];
is_visible = NO;
}
the memory is correctly released.
Why??????
Thank you in advance
Fabio
Your view isn't getting released as you would be expecting because at the moment you're releasing it there are still animations linked to it. You can only properly release it after the animations are finished.
Your second method works because the animation lasts less than 0.5 seconds - the releasing code is called after view is freed of all the animations.
Proper way to animate the view would be to either create an animation and assign its delegate or maybe a bit more elegant soulution is to use block-based animation like this:
- (void)dismiss
{
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[UIView animateWithDuration: 0.15
animations: ^{
self.view.alpha = 0.0;
self.waitingView.alpha = 0.0;
}
completion: ^(BOOL finished){
[self.waitingIndicator stopAnimating];
    [self.view removeFromSuperview];
    [self.waitingView removeFromSuperview];
    is_visible = NO;
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}];
}

Admob Integration on iPhone - Memory Leak Problem

I wonder if anyone can help with the following. I have integrated both iAds and AdMob into my app. However a user reported that the app crashes on the iPod Touch. Using Instruments in xCode I have managed to identify that something called "GOOGLE_SHUFFLE_RVS_User_waylonis_Code_afma1_googlmac_iPhone_GoogleAds_Signals_Protected_build_GoogleAdsSignals_build_Release_iphoneos_Google" is causing a memory leak of about 500 bytes everytime it is called.My ad refresh rate is set at 20 seconds so this happens every 20 seconds.
My code is as follows.
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
if (self.bannerIsVisible)
{
[UIView beginAnimations:#"animateAdBannerOff" context:NULL];
// banner is visible and we move it out of the screen, due to connection issue
banner.frame = CGRectOffset(banner.frame, 0, -90);
[UIView commitAnimations];
self.bannerIsVisible = NO;
}
[self loadAdMobAd];
}
-(void)loadAdMobAd {
if (!bannerView_) {
CGRect adSize = CGRectMake (0,40,0,0);
adSize.size = GAD_SIZE_320x50;
bannerView_ = [[GADBannerView alloc] initWithFrame:adSize];
bannerView_.rootViewController = self;
bannerView_.adUnitID = MY_BANNER_UNIT_ID;
bannerView_.rootViewController = self;
[self.view addSubview:bannerView_];
// Initiate a generic request to load it with an ad.
[bannerView_ loadRequest:[GADRequest request]];
}
}
The idea is that if an iAd is not available an AdMob ad is loaded instead.
Is there anything wrong with my code that could be causing the leak ?
Many thanks,
Martin
Apparently the GOOGLE_SHUFFLE_RVS memory leak is a known issue. According to the Google Group (http://groups.google.com/group/google-admob-ads-sdk/browse_thread/thread/2631fcb87d909bfa/edafd2a4ac175f47?lnk=gst&q=memory+leak#edafd2a4ac175f47), "it's a known glitch, and it'll be fixed in the next release" (from a comment posted on March 31). They also say it's fixed internally but not released yet.
I was very surprised that AdMob/Google didn't give higher priority to something as significant as an ad banner that leaks memory every time an ad loads. I guess everyone is just using the memory leaking version for now. :-o
Joe
You're alloc'ing bannerView_, adding it to the view, but not releasing it.
Try adding [bannerView_ release]; after the loadRequest line.

Subclassing MKAnnotationView and overriding setDragState

This is about an iPhone App using MKMapKit:
I created a custom MKAnnotationView for a draggable Annotation. I want to create a custom animation. I set a custom pin image and the annotation is draggable (which both is not shown here, it happens in the mapview) with the following code:
- (void) movePinUpFinished {
[super setDragState:MKAnnotationViewDragStateDragging];
[self setDragState:MKAnnotationViewDragStateDragging];
}
- (void) setDragState:(MKAnnotationViewDragState) myState {
if (myState == MKAnnotationViewDragStateStarting) {
NSLog(#"starting");
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-20);
self.center = endPoint;
[self movePinUpFinished];
}
if (myState == MKAnnotationViewDragStateEnding) {
NSLog(#"ending");
[super setDragState:MKAnnotationViewDragStateEnding];
[self setDragState:MKAnnotationViewDragStateNone];
[super setDragState:MKAnnotationViewDragStateNone];
}
if (myState == MKAnnotationViewDragStateDragging) {
NSLog(#"dragging");
}
if (myState == MKAnnotationViewDragStateCanceling) {
NSLog(#"cancel");
}
if (myState == MKAnnotationViewDragStateNone) {
NSLog(#"none");
}
}
Everything works fine, the annotation is moved up a bit, is draggable and when i release the annotation, the mapview receives the "dragstateending".
But now I want the animation to run over a time period and change the dragStateStarting to the following:
if (myState == MKAnnotationViewDragStateStarting) {
NSLog(#"starting");
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-20);
[UIView animateWithDuration:1.0
animations:^{ self.center = endPoint; }
completion:^(BOOL finished){ [self movePinUpFinished]; }];
}
The animations runs as wanted over the period of a second and the annotation is draggable. But when I release the annotation, the mapview is not receiving the ending through the delegat. What I also recognized was that when I am doing the animation with "UIView animateWithDuration..." is that immedently after beginning the dragging, as the animation starts, the ballon of the annotation opens. When i am setting the new center without the animation, the balloon keeps closed and is only opened after finishing the dragging by releasing the annotation.
What am I doing wrong? Is this the right way to override setDragState. Do I really have to call the super class? But without setting the dragstate in the superclass my mapview didnt realized the changes of the dragstate.
I wonder about the original implementation of MKPinAnnotationView, but because it is an internal Class I couldn't find a description of the setDragState method.
Thx for help. Cheers,
Ben
I had the pin drag working but was trying to figure out why the pin annimations that occur when you don't override setDragState - no longer work in my implementation. Your question contained my answer .. Thanks!
Part of the problem with your code is that once you override the setDragState function, per the xcode documentation, you are responsible for updating the dragState variable based on the new state coming in. I would also be a little concerned about your code calling itself (setDragState calling [self setDragState]).
Here is the code I ended up (with your help) that does all of the lifts, drags and drops as I expect them to occur. Hope this helps you too!
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated
{
if (newDragState == MKAnnotationViewDragStateStarting)
{
// lift the pin and set the state to dragging
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ self.dragState = MKAnnotationViewDragStateDragging; }];
}
else if (newDragState == MKAnnotationViewDragStateEnding)
{
// save the new location, drop the pin, and set state to none
/* my app specific code to save the new position
objectObservations[ACTIVE].latitude = pinAnnotation.coordinate.latitude;
objectObservations[ACTIVE].longitude = pinAnnotation.coordinate.longitude;
posChanged = TRUE;
*/
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ self.dragState = MKAnnotationViewDragStateNone; }];
}
else if (newDragState == MKAnnotationViewDragStateCanceling)
{
// drop the pin and set the state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ self.dragState = MKAnnotationViewDragStateNone; }];
}
}
While Brian's solution worked, it lacked taking into account the users finger blocking the annotation view which is being manipulated.
This means that the user could not precisely place the pin once he was dragging it. The standard MKPinAnnotationView does a great job at this, what happens is when the finger begins dragging, the pin is lifted above the finger, and the visual point of the pin is used for placement not the previous centre point which now resides under the finger.
In addition to this my implementation also adds another animation when dropping the pin after dragging, by lifting the pin and dropping it with a higher speed. This is very close the the native user experience and will be apreciated by your users.
Please check out my gist on GitHub for the code.
What's really cool is setting a delegate is optional, optionally a notification is sent when the annotation view is dropped back onto the map.
I didn't study Ben's code much but it didn't worked for me. So I tried Brian's and it works great. Thanks a lot! I've been trying to solve annotation's animation during drag'n'drop for a long time.
But I have one suggestion to Brian's solution. I think that it would be better to support MKMapKit's delegate and notify it about changing dragState and save new position within standard delegate's method: - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState. Here's my code:
DraggableAnnotationView.h:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface DraggableAnnotationView : MKAnnotationView
{
id <MKMapViewDelegate> delegate;
MKAnnotationViewDragState dragState;
}
#property (nonatomic, assign) id <MKMapViewDelegate> delegate;
#property (nonatomic, assign) MKAnnotationViewDragState dragState;
#property (nonatomic, assign) MKMapView *mapView;
#end
DraggableAnnotationView.m:
#import "DraggableAnnotationView.h"
#import "MapAnnotation.h"
#implementation DraggableAnnotationView
#synthesize delegate, dragState, mapView;
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated
{
[delegate mapView:mapView annotationView:self didChangeDragState:newDragState fromOldState:dragState];
if (newDragState == MKAnnotationViewDragStateStarting) {
// lift the pin and set the state to dragging
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ dragState = MKAnnotationViewDragStateDragging; }];
} else if (newDragState == MKAnnotationViewDragStateEnding) {
// drop the pin, and set state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ dragState = MKAnnotationViewDragStateNone; }];
} else if (newDragState == MKAnnotationViewDragStateCanceling) {
// drop the pin and set the state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ dragState = MKAnnotationViewDragStateNone; }];
}
}
- (void)dealloc
{
[super dealloc];
}
#end