Callout button crashes app - MapKit - ios

UICalloutView willRemoveSubview:]: message sent to deallocated instance;
This only happens when I tap the callout button, but not on the first and not on the 2nd tap, but from the 3rd tap. So I tap the custom AnnotationView, callout pops, thats good. I tap it again, callout pops, all good. I tap another one, boom crash with that message. It only happes if is set the right accesoryview to be a button.
One key aspect to keep in mind..only happens in iOS 6... (go figure).
I am really stuck on this one – some help would be appreciated.
if ([annotation isKindOfClass:[RE_Annotation class]])
{
RE_Annotation *myAnnotation = (RE_Annotation *)annotation;
static NSString *annotationIdentifier = #"annotationIdentifier";
RE_AnnotationView *newAnnotationView = (RE_AnnotationView *)[mapViews dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if(newAnnotationView)
{
newAnnotationView.annotation = myAnnotation;
}
else
{
newAnnotationView = [[RE_AnnotationView alloc] initWithAnnotation:myAnnotation reuseIdentifier:annotationIdentifier];
}
return newAnnotationView;
}
return nil;
Also, this is my initwithannotation method:
- (id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if(self)
{
RE_Annotation *myAnnotation = annotation;
self = [super initWithAnnotation:myAnnotation reuseIdentifier:reuseIdentifier];
self.frame = CGRectMake(0, 0, kWidth, kHeight);
self.backgroundColor = [UIColor clearColor];
annotationView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"map_pin_pink.png"]];
annotationView.frame = CGRectMake(0, 0, kWidth - 2 *kBorder, kHeight - 2 * kBorder);
[self addSubview:annotationView];
[annotationView setContentMode:UIViewContentModeScaleAspectFill];
self.canShowCallout = YES;
self.rightCalloutAccessoryView = [[UIButton buttonWithType:UIButtonTypeInfoLight] retain]; ///if i take it out it doesnt crash the app. if i leave it it says that message
}
return self ;
}

In the initWithAnnotation method, there is this line:
self.rightCalloutAccessoryView =
[[UIButton buttonWithType:UIButtonTypeInfoLight] retain];
///if i take it out it doesnt crash the app. if i leave it it says that message
By "it", you must be referring to the retain and this implies the app is not using ARC.
Based on that, you should make the following corrections:
In viewForAnnotation, you need to autorelease the view when you alloc+init it otherwise there's a leak:
newAnnotationView = [[[RE_AnnotationView alloc]
initWithAnnotation:myAnnotation
reuseIdentifier:annotationIdentifier] autorelease];
In initWithAnnotation, remove the retain when creating the callout button since buttonWithType returns an auto-released object (and you don't want to over-retain it):
self.rightCalloutAccessoryView =
[UIButton buttonWithType:UIButtonTypeInfoLight];
Another possibly unrelated issue is that in initWithAnnotation, the code is calling super initWithAnnotation twice. This seems unnecessary and may be harmful. Remove the second call.
The above changes at least fix the issues with the code shown. However, there may be other, similar memory-management related issues in the rest of the app that may still cause crashes. Check and solve all issues reported by Analyzer.
Regarding the fact that "it only happens in iOS 6": iOS 6 may be less forgiving of memory-management errors or an internal change in the SDK may be exposing or manifesting these errors earlier. Regardless, the above fixes are necessary.
An unrelated point is there should be no need to manually create a UIImageView and addSubview it to the annotation view. You can just set the annotation view's image property.

Related

Custom MapKit Annotation Lagging

I am display a simple MKMapView to display a collection of discovered places near the users location. Upon getting results, I am adding custom annotation views, let's say of class MyAnnotationView to the map.
These custom view's are displayed nicely, and I have ironed out all of the intricate handlers for a very nice UI. Like most would assume (or hope..), upon touching one of my markers, a separate (and custom) MKAnnotationView pops up to display more detail. This custom view has much more detail regarding the location found, with several buttons the user is able to interact with. The interaction is not the issue here (thankfully having overcome that obstacle).
My issue is, for whatever reason, there seems to be a bit of "lag" between the TouchDown and the TouchUpInside event calling, about roughly ~0.5 seconds delay to be more precise.
I have checked firing my anticipated method for only the TouchDown event, and it fires almost immediately (with the micro-delay any UIButton naturally creates).
My guess is that the MKMapView is the culprit. Given it is intercepting / monitoring many different UIGestureRecognizer's, I'm assuming the framework is just a bit "behind" in delivering my TouchUpInside event..
Unfortunately, assumptions don't really help anyone, especially if they don't come with a solution. So if anyone has any idea's / workarounds as to why the event handling is experiencing this delay I would love to hear. Thanks!
CODE REFERENCES
Here is some of my code to help:
Custom annotation view (w/ buttons) .h
#import <UIKit/UIKit.h>
#import "MyAnnotationView.h"
#import MapKit;
#interface MyAnnotationView : MKAnnotationView
.m
#import "MyAnnotationView.h"
#implementation MyAnnotationView {
CGFloat width, height;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
width = frame.size.width, height = frame.size.height;
self.contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
[self addSubview:self.contentView];
}
return self;
}
Adding the views
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
// Here I simply create an annotation, assign it to a new `MyAnnotationView`
// and add the view.
MyAnnotationView *view = [[MyAnnotationView alloc] init];
...
// Note: the detailButton is just a UIButton
// This lags...
[view.detailButton addTarget:self action:#selector(didTouchCell) forControlEvents:UIControlEventTouchUpInside];
// No lag... hmm
// [view.detailButton addTarget:self action:#selector(didTouchCell) forControlEvents:UIControlEventTouchDown];
}
The reason for the delay is that the map view is waiting to see if you're going to double-tap to zoom in. Even if you double-tap an annotation, the map will still zoom in. You can remove the delay if you're not bothered about double-tap-to-zoom by removing the double-tap gesture from the view hierarchy.
- (void)removeDoubleTapGestures:(UIView *)view {
NSArray *gestureRecognizers = [view gestureRecognizers];
for (UIGestureRecognizer *recognizer in gestureRecognizers) {
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]] &&
[(UITapGestureRecognizer *)recognizer numberOfTapsRequired] == 2) {
[view removeGestureRecognizer:recognizer];
}
}
for (UIView *subview in view.subviews) {
[self removeDoubleTapGestures:subview];
}
}
In your viewDidLoad call:
[self removeDoubleTapGestures:myMapView];
Remember though that you're modifying MKMapView's view hierarchy, so if Apple change things in the future it could stop working.

UIImagePickerController takePicture Dismisses Controller

I have a custom UIImagePickerController that works nicely, only I am facing one issue that I feel should be fairly simple - I just have yet to figure out the solution.
Upon touching my custom added "photo" button, I have it targeted to the build in takePicture method of the UIIPC. Here is my code
#interface CustomCameraController ()
#end
#implementation CustomCameraController {
CGFloat width, height;
}
- (instancetype)init {
if (self = [super init]) {
width = self.view.frame.size.width, height = self.view.frame.size.height;
self.allowsEditing = YES;
self.sourceType = UIImagePickerControllerSourceTypeCamera;
self.showsCameraControls = NO;
self.toolbarHidden = YES;
[self buildCameraOverlay];
}
return self;
}
- (void)buildCameraOverlay {
UIView *customOverlay = [UIView alloc] ...
// ... Custom overlay setup done here
_takePhoto = [[CustomButton alloc] initWithFrame:CGRectMake(0, 0, heightBottomBar*.5, heightBottomBar*.5)];
_takePhoto.center = CGPointMake(bottomBar.frame.size.width/2, bottomBar.frame.size.height/2);
[_takePhoto setImage:[UIImage imageNamed:#"camera button icon"] forState:UIControlStateNormal];
[_takePhoto addTarget:self action:#selector(takePicture) forControlEvents:UIControlEventTouchUpInside];
[bottomBar addSubview:_takePhoto];
// ...
self.cameraOverlayView = customOverlay;
}
This is done in my custom controller CustomCameraController init call.
The problem is, upon taking the picture via takePicture, the camera shutter goes off, everything works just fine, but the controller dismisses itself. I'm trying to figure out how to stop it from closing immediately after taking the picture, so I can A)present the taken picture, and B) give the user the option to choose the image or cancel and retake another one (returning to the camera)
If anyone knows why this happens or something that I am missing / doing incorrectly please let me know. I'm sure it's a simple answer - just can't seem to figure it out. Thanks!
The most common reason for such a weird behaviour is usually lack of delegate methods (for UIImagePickerController in this case) or their wrong implementation.

ios remote keyboard window

In iOS 9, I see a new window appearing in my app that I didn't see before. An image is below. From walking the view tree, I suspect it may be coming from the UIRemoteKeyboardWindow -- but I don't know that. What is it, and what do I have to do to keep it from appearing?
EDIT: As a commenter pointed out, this is tied to the inputView, i.e. the keyboard. I don't want a keyboard and so disabled it by calling
self.inputView = [[UIView alloc]initWithFrame: CGRectZero];
That did kill the keyboard, but there is still the accessory. I've tried similar tricks to kill the accessory; none of them have worked, yet. Calling self.inputAccessoryView is returning nil, which doesn't help.
This got rid of it:
-(void) killAccessory {
UIView* input = self.inputView;
UIView* parent = input.superview;
parent.hidden = YES;
}
-(BOOL) becomeFirstResponder {
BOOL r = [super becomeFirstResponder];
[self killAccessory];
return r;
}
You can use the following code :
textField.inputAssistantItem.leadingBarButtonGroups = [[NSArray alloc] init];
textField.inputAssistantItem.trailingBarButtonGroups = [[NSArray alloc] init];
the textField use above is the textField which you tap to edit.

Adding an annotation to mkmap, but want to remove part of the annotation but it won't remove

I create an annotation, which contains several elements textbubble and pin. I turn the bubble on when I show annotation, but later I want to shut the bubble off and leave the annotation.
Here are my two methods. The add subview works, but remove subview does not.
-(void)hideETACountdown {
self.etaView.hidden = YES;
[self.etaView removeFromSuperview];
}
-(void)showETACountdown {
self.etaView = [[UIView alloc] initWithFrame:CGRectMake(-34, -97, 89, 59)];
UIImageView *bg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"WaitBubble_backgroundandshadow.png"]];
[self.etaView addSubview:bg];
UILabel *minLabel = [[UILabel alloc] initWithFrame:CGRectMake(7, 24, 42, 21)];
minLabel.text = #"min";
minLabel.textAlignment = UITextAlignmentCenter;
minLabel.font = [UIFont systemFontOfSize:10];
self.etaLabel = [[UILabel alloc] initWithFrame:CGRectMake(13, 4, 30, 27)];
self.etaLabel.font = [UIFont boldSystemFontOfSize:22];
self.etaLabel.textAlignment = UITextAlignmentCenter;
self.etaLabel.text = #"";
[self.etaView addSubview:minLabel];
[self.etaView addSubview:self.etaLabel];
[self addSubview:self.etaView];
self.etaView.hidden = NO;
}
- (id) initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]) {
self.canShowCallout = YES;
self.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
self.innerImage = [[UIImageView alloc] initWithImage:nil];
self.innerImage.frame = CGRectMake(-15, -38, 32, 39);
[self addSubview:self.innerImage];
if(self.showETA) {
[NSNotificationCenter addUniqueObserver:self
selector:#selector(handleEtaTimeUpdate:)
name:kEtaUpdate
object:nil];
[self showETACountdown];
}
}
return self;
}
// UPDATE /////
There seems to be some confusion. This code above is not in the viewController that holds my mkmap, but rather the code inside my custom annotation. Further, I don't want to hide or show the entire annotation based on selecting or deselecting. The self.etaView is custom view which is just part of the annotation. My annotation consists of a custom map pin and an eta bubble. Once the ETA is counted down to 0, I want to remove the bubble (aka self.etaView), but the annotation (map pin) needs to stay on the map the entire time. I just want to hide the ETA bubble.
I am using the proper addAnnotation methods, in the proper way, in my viewController that holds my mkmap. Again, this code above is inside my custom annotation and I want my custom annotation to be responsible for removing its own elements, NOT removing itself from the map.
Come on, why using this weird logics with addSubView and removeFromSuperView. MKMapView is built to support "datasource" for pins. I dunno what kind of view you are trying to acheive but this CGRectMake(-34, -97, 89, 59) looks awful. So please, use method:
-(MKAnnotationView *)mapView:(MKMapView *)aMapView viewForAnnotation:(id<MKAnnotation>)annotation
This way you will have no difficulties managing the annotation using method
- (void)deselectAnnotation:(id < MKAnnotation >)annotation animated:(BOOL)animated
For example:
[mapView deselectAnnotation:[mapView.selectedAnnotations objectAtIndex:0] animated:YES];
The method to remove the bubble was getting called, but it just wasn't getting removed? So what I have done is create notification listener on my annotation and post a notification when I want it removed and it removes it. Not sure why it doesn't just work by calling an instance method?
Anyway, notifications solved it. Need to move on so I can launch the app.

UIView and UIViewController

I know this is really basic stuff but i need to understand whether my understanding of this is correct.
So what i want to do is this. I want an view with a label on which when double tapped flips and loads another view. On the second view i want a UIPickerView and above i have a button saying back. Both views will be of same size as an UIPickerView which is 320px x 216px.
What i am thinking of to do is create two UIViewclasses named labelView and pickerView. I would then create a viewController which on loadView loads labelView then when user double taps the labelView i get an event in labelView class which is sent to my viewController that then can unload loadView and load the pickerView.
Does this sound as the best way to do this ? Is there a simpler way ? I am also unsure how i route the event from the labelView class to the viewControllerclass.
I dont exactly know the most efficient way to do it(as i am also now to this language),but it is for sure that i have solved ur problem. I made a simple program for that.Three classes involved here in my eg are BaseViewController (which will show two views),LabelView and PickerView (according to ur requirement).
In LabelView.h
#protocol LabelViewDelegate
-(void)didTapTwiceLabelView;
#end
#interface LabelView : UIView {
id <LabelViewDelegate> delegate;
}
#property(nonatomic,retain)id <LabelViewDelegate> delegate;
-(void)didTouch;
#end
In LabelView.m
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
UILabel* labl = [[UILabel alloc] initWithFrame:CGRectMake(10, 5, frame.size.width-20,20)];
labl.text = #"Some Text";
[self addSubview:labl];
[labl release]; labl = nil;
self.backgroundColor = [UIColor grayColor];
UITapGestureRecognizer* ges = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTouch)] autorelease];
ges.numberOfTapsRequired = 2;
[self addGestureRecognizer:ges];
}
return self;
}
-(void)didTouch
{
[delegate didTapTwiceLabelView];
}
//=============================================================
In Pickerview.h
#protocol PickerViewDelegate
-(void)didTapBackButton;
#end
#interface PickerView : UIView <UIPickerViewDelegate,UIPickerViewDataSource>{
id <PickerViewDelegate> delegate;
}
#property(nonatomic,retain)id <PickerViewDelegate> delegate;
#end
In Pickerview.m
#implementation PickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
UIPickerView* picker = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 30, 320, 216)];
picker.delegate = self;
picker.dataSource = self;
[self addSubview:picker];
[picker release]; picker = nil;
self.frame = CGRectMake(frame.origin.x, frame.origin.y, 320, 250);
UIButton* btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10, 1, 50, 27)];
[btn setTitle:#"Back" forState:UIControlStateNormal];
[btn addTarget:self action:#selector(backButton) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn];
}
return self;
}
-(void)backButton
{
[delegate didTapBackButton];
}
//====================================================================
in BaseViewController.h
#import "LabelView.h"
#import "PickerView.h"
#interface VarticalLabel : UIViewController<UITextFieldDelegate,PickerViewDelegate,LabelViewDelegate> {
PickerView* myPickerView;
LabelView* myLabelView;
}
#end
In BaseViewController.m
-(void)viewDidLoad
{
[super viewDidLoad];
myPickerView= [[PickerView alloc] initWithFrame:CGRectMake(0, 50, 320, 250)];
[self.view addSubview:myPickerView];
myPickerView.delegate = self;
myLabelView= [[LabelView alloc] initWithFrame:CGRectMake(0, 50, 320, 250)];
[self.view addSubview:myLabelView];
myLabelView.delegate = self;
myPickerView.hidden = YES;
}
#pragma mark PickerViewDelgate
-(void)didTapBackButton
{
myPickerView.hidden = YES;
myLabelView.hidden = NO;
}
#pragma mark LabelViewDelegate
-(void)didTapTwiceLabelView
{
myPickerView.hidden = NO;
myLabelView.hidden = YES;
}
To get events from a button to the view controller, just hook up the button's event, e.g. touch up inside, to a method in the view controller, using interface builder. (Double tapping is probably more complicated though.)
When you say 'flips', do you mean it actually shows an animation of flipping over a view to show a 'reverse' side? Like in the weather app when you hit the 'i' button? I'm assuming this is what you mean.
Perhaps check TheElements sample example on the iPhone Reference Library, it has an example of flip animation.
Btw, it's not strictly necessary to unload the loadView that is being 'hidden' when you flip -- it saves you having to construct it again when you flip back -- but it may be pertinent if you have memory use concerns, and/or the system warns you about memory being low.
Also, what do you mean by "create a UIView"? Do you mean subclass UIView, or just instantiate a UIVIew and add children view objects to it? The latter is the usual strategy. Don't subclass UIView just because you want to add some things to a UIView.
If you've got one screen of information that gives way to another screen of information, you'd normally make them separate view controllers. So in your case you'd have one view controller with the label and upon receiving the input you want, you'd switch to the view controller composed of the UIPickerView and the button.
Supposing you use Interface Builder, you would probably have a top level XIB (which the normal project templates will have provided) that defines the app delegate and contains a reference to the initial view controller in a separate XIB (also supplied). In the separate XIB you'd probably want to add another view controller by reference (so, put it in, give it the class name but indicate that its description is contained in another file) and in that view controller put in the picker view and the button.
The point of loadView, as separate from the normal class init, is to facilitate naming and linking to an instance in one XIB while having the layout defined in another. View controllers are alloced and inited when something that has a reference to them is alloced and inited. But the view is only loaded when it is going to be presented, and may be unloaded and reloaded while the app is running (though not while it is showing). Generally speaking, views will be loaded when needed and unnecessary views will be unloaded upon a low memory warning. That's all automatic, even if you don't put anything in the XIBs and just create a view programmatically within loadView or as a result of viewDidLoad.
I've made that all sound more complicated than your solution, but it's actually simpler because of the amount you can do in Interface Builder, once you're past the curve of learning it. It may actually be worth jumping straight to the Xcode 4 beta, as it shakes things up quite a lot in this area and sites have reported that a gold master was seeded at one point, so is likely to become the official thing very soon.
With respect to catching the double tap, the easiest thing is a UITapGestureRecognizer (see here). You'd do something like:
// create a tap gesture recogniser, tell it to send events to this instance
// of this class, and to send them via the 'handleGesture:' message, which
// we'll implement below...
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleGesture:)];
// we want double taps
tapGestureRecognizer.numberOfTapsRequired = 2;
// attach the gesture recogniser to the view we want to catch taps on
[labelView addGestureRecognizer:tapGestureRecognizer];
// we have an owning reference to the recogniser but have now given it to
// the label. We don't intend to talk to it again without being prompted,
// so should relinquish ownership
[tapGestureRecognizer release];
/* ... elsewhere ... */
// the method we've nominated to receive gesture events
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
// could check 'gestureRecognizer' against tapGestureRecognizer above if
// we set the same message for multiple recognisers
// just make sure we're getting this because the gesture occurred
if(gestureRecognizer.state == UIGestureRecognizerStateRecognized)
{
// do something to present the other view
}
}
Gesture recognisers are available as of iOS 3.2 (which was for iPad only; so iOS 4.0 on iPhone and iPod Touch).

Resources