0

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)
rahulbsb
  • 115
  • 12

1 Answers1

0

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.

Afonso Tsukamoto
  • 1,184
  • 1
  • 12
  • 21
  • What do you mean with 'what type'? Is a UIMenucontroller pointer ... ? Static inside the implementation of UIMenuController. I don't get it. – Afonso Tsukamoto Jul 06 '14 at 18:12
  • I had settled with passing setVisible: message to the menu object. But then it just did not seem logical to me: the fact that when u are letting go of supposedly one and only strong pointer (menu), how could the object not be destroyed? – rahulbsb Jul 06 '14 at 18:14
  • Yeah i'm sorry by asking 'what type'. What i meant was i couldn't find that UIMenucontroller pointer that was still pointing to the object. Yeah the code makes sense. sharedMenuController (being a static) would permanently keep pointing to the object until the app is killed. Thanks. – rahulbsb Jul 06 '14 at 18:19
  • That is the tradeoff of having a singleton. UIMenuController keeps an instance of it self after sharedMenuController is called once. There might be methods which make arc uses to free those instances, but you cannot count on that, you must protect your code. Still, setting your variable to nil when pointing to a singleton, is still a good practice, if those arc methods do exist, keeping a strong reference to that single instance will keep it from being cleaned. – Afonso Tsukamoto Jul 06 '14 at 18:21
  • I guess setting menu var to nil brings down the retain count (menu was a strong pointer too). So in doing so we are still keeping the memory footprint low even though UIMenuController hasn't been destroyed.. I could be terribly wrong here. Pls assist. I'm relatively new to this. – rahulbsb Jul 06 '14 at 18:26
  • Well, yes, you are bringing the retain count down, but that is not bringing the memory footprint that low, since is like decrementing an counter(the way I think about it, not a fact). This only gets relevant memory wise if arc, in fact, detects that a class is the only one keeping a reference to a instance of itself and frees that instance. I have no idea if this happens, I can see some cases where this should not happen, making me think that arc doesn't do that. So making your pointer to nil, might not be that important, memory wise. – Afonso Tsukamoto Jul 06 '14 at 18:38
  • You can always replace your pointer to the singleton by a call to singleton itself, like [[UIMenuController sharedMenuController]setMenuItems:@[deleteItem]];, and then, you are saving 1 iVar :) – Afonso Tsukamoto Jul 06 '14 at 18:40