Search code examples
objective-ccocoa-touchuimenucontroller

UIMenuItem continuing to show up even after the pointer is set as nil


In the following code when i select a line, a delete menu shows up. Now after this when i start drawing a new line (leaving the delete menu as-is), the delete menu on top of the first one is supposed to disappear. Thats because while i'm starting to draw the second line, the uimenucontroller singleton object destroys as the pointer pointing it is set to nil. This is what is supposed to happen. But apparently its not.. When i log the pointer in drawRect method, it does show up as nil though.. So why is the menu still showing up? Here is the code---

 -(void)tap:(UIGestureRecognizer *)sender{
NSLog(@"Recognized tap-%@",sender);
CGPoint p= [sender locationInView:self];
selectedLine= [self lineAtPoint:p];//Not going to return anything with just a tap which draws a (non drawable) point and not a line.

[linesInProcess removeAllObjects]; //Avoid adding a new line while performing the tap gesture

if (self.selectedLine) {
    NSLog(@"setting up delete menu");
    [self becomeFirstResponder];

    menu= [UIMenuController sharedMenuController];

    //        create a "Delete" menu item
   deleteItem= [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteLine:)];
    [menu setMenuItems:@[deleteItem]];

    //        Tell the menu where it should come from and show it.
    [menu setTargetRect:CGRectMake(p.x, p.y, 2, 2) inView:self];
    [menu setMenuVisible:YES animated:YES];
}
else{
    //        Hide the menu if no line is selected
    [menu setMenuVisible:NO animated:YES];
}
[self setNeedsDisplay];
NSLog(@"test");
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"%@",NSStringFromSelector(_cmd));

//    Silver challenge
selectedLine=nil;
menu=nil

int i=0;
for (UITouch *touch in touches) {
    NSLog(@"%dth iteration using touch- %@",++i,touch);

    //        Use the touch object [packed in an NSValue] as the key
    NSValue *key= [NSValue valueWithNonretainedObject:touch];
    NSLog(@"key-%@ touch-%@",key,touch);
    //        Create a line for the value
    CGPoint loc= [touch locationInView:self];
    Line *newLine= [[Line alloc] initWithLocation:loc];//Line to be drawn..Plus, setting begin and end pts. happens here.
    NSLog(@"new line- %@",newLine);
    //        put pair in dictionary
    [linesInProcess setObject:newLine forKey:key];

}

}

 - (void)drawRect:(CGRect)rect
{
  NSLog(@"%@",NSStringFromSelector(_cmd));

// Drawing code
CGContextRef context= UIGraphicsGetCurrentContext();
CGContextSetLineCap(context, kCGLineCapRound);//style of the line-endpoints
NSLog(@"delete menu-%@",menu);
//    Draw complete lines in black
[[UIColor blackColor] set];
for (Line *line in self.rootObj.completeLines) {
    NSLog(@"Iterating over array");
    NSLog(@"complete line- %@",line);
    NSLog(@"begin pt.- (%f, %f) and end pt.- (%f, %f)",[line begin].x,[line begin].y,[line end].x,[line end].y);
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    CGContextAddLineToPoint(context, [line end].x, [line end].y);
    CGContextStrokePath(context);
}

//    Draw lines in process in red..
[[UIColor redColor] set];
for (NSValue *v in linesInProcess) {
    NSLog(@"Iterating over dictionary");
    Line *line= [linesInProcess objectForKey:v];
    NSLog(@"line being drawn- %@",line);
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    CGContextAddLineToPoint(context, [line end].x,[line end].y);
    CGContextStrokePath(context);
}

NSLog(@"selected line: %@",self.selectedLine);
//    Change the color of selected line. Redraw that line as green colored.
if (selectedLine) {
    NSLog(@"reseting color");
    [[UIColor greenColor] set];
    CGContextMoveToPoint(context, [selectedLine begin].x, [selectedLine begin].y);
    CGContextAddLineToPoint(context, [selectedLine end].x, [selectedLine end].y);
    CGContextStrokePath(context);
}
NSLog(@"drawing ended");
}

I'm solving this problem from the Big Nerd Ranch Guide. In the touchesBegan: and tap: method, 'menu' is used.It is an ivar. Pls note that touchesBegan: is not calling setNeedsDisplay. This is just a code snippet. In the actual program setNeedsDisplay has been called from other methods like touchesMoved: and touchesEnded:. I just want to know as to why the menu on the view stays, when clearly i'm destroying it in touchesBegan: method by setting its pointer to nil.

The control flow is something like this during and after the 1st line is selected to show up the delete menu:

  1. tap message is passed and the menu object is created and all the related settings are done.
  2. action:- The first line is selected and the delete menu shows up on top of it.
  3. action:- Then (leaving the delete menu on for the first line), second line is being drawn.
  4. touchesBegan: message is passed to the view and menu pointer is set to nil.
  5. tap method may or may not be called, if it does then the if block is not run hence 'menu' is never reinstantiated.
  6. Ultimately drawRect: message is passed and all the lines and settings are realized.
  7. the first line's delete menu stays as is. (This is where the issue is)

Solution

  • Since your controller is a Singleton, it probably has a strong reference as static for an instance of itself, and that instance is what is being returned to your pointer when you call sharedInstance. (What I assume the sharedMenuController method is)

    + (id)sharedMenucontroller {
        static UIMenuController *sharedMenuController = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedMenuController = [[self alloc] init];
        });
        return sharedMyManager;
    }
    

    This means that arc wont clean it just by setting your pointer to that singleton's static variable as nil. If you just want to hide it, call setVisible:NO on it, is the simplest way.