Silver Solution


#1

Hi Everyone,

After a bit of wrangling I managed to get the silver solution to work.

I simply unset the selected line by setting the selected line to nil. Then, I made the UIMenuController a property and set the menu invisible in the moveLine method.

[code]- (void) moveLine: (UIPanGestureRecognizer *) gr
{

//These are the two line added to make the bug disappear
[self setSelectedLine:nil];
[self.menu setMenuVisible:NO];

....

}[/code]


#2

I see how this resolves the bug in a way – so you can continue drawing without moving the selected line. But it also affects your ability to move a line with a longPress Gesture. Now with a long press the line is deselected and not moved when you try to move it…
I did it by inserting a condition in all of the touch/event methods…

 if (self.selectedLine) {
        return;
    }

so that the new drawing wouldn’t begin (or change or end) if there was a selected line.
Also to prevent moving the line without a long press I disabled moveLine if the menu was visible. And I don’t think its necessary to create a menu property as sharedMenuController is always available…

-(void)moveLine:(UIPanGestureRecognizer*)gr
{
    
    //If we have not selected a line do nothing
    if (!self.selectedLine) {
        return;
    }
    UIMenuController *menu=[UIMenuController sharedMenuController];
    if (menu.isMenuVisible==YES) {
        return;
    }

...

#3

That’s a good solution. I did mine in a slightly different way so that while the line is selected, you can still draw new lines while the selected line remains selected. In BNRDrawView.m, I created a BOOL property called isTapped;

@property (nonatomic) BOOL isTapped;

I initialised isTapped as NO. In the -tap: method, I set it to YES in the if (self.selectedLine){…} condition and NO in the else condition. In the -deleteLine: method, I set it to NO. Then I added this code to -moveline:

- (void)moveLine:(UIPanGestureRecognizer *)gr
{
    ...
    if (self.isTapped) {
        return;
    }
    ...
}

#4

Yet another solution.

I first moved the deceleration of UIMenuController to the local interface and out of the tap method:

...
@property (nonatomic, strong)UIMenuController *menu;
...

Next, all I had to do was add the following method since we setup the pan gesture to already be a delegate.

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if (self.menu.menuVisible)
    {
        [self.menu setMenuVisible:NO];
        self.selectedLine = nil;
    }
        
    return YES;
}

The above gets called by the framework rather than me having to manage it. It simply checks to see if the menu is visible and if so it takes it away (since obviously the user wanted to do something else) and then clears the selected lines. This allows for a new line to be drawn and it also allows for the long press gesture and the corresponding movement of the selected line even if done initially while the menu was displayed.


#5

Another solution for the mix.

I added the following code to moveLine::

if ([gr state] == UIGestureRecognizerStateBegan) {
    self.selectedLine = [self lineAtPoint:[gr locationInView:self]];
    [[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO];
}

This seems to work and I can’t see any obvious problems.


#6

My solution is similar, but I added “self.moveRecognizer.cancelsTouchesInView” to avoid a new line created along the path a selected line is being moved.

-(void)moveLine:(UIPanGestureRecognizer *)gr
{
    self.moveRecognizer.cancelsTouchesInView = NO;
    
    if (!self.selectedLine) return;

    [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
    if (gr.state == UIGestureRecognizerStateBegan) {
        if (self.selectedLine != [self lineAtPoint:[gr locationInView:self]]) {
            self.selectedLine = nil;
            return;
        }
    }

    if (gr.state == UIGestureRecognizerStateChanged) {
        self.moveRecognizer.cancelsTouchesInView = YES;
        ... ...

#7

Here is another one:

In “touchesBegan: …” I ask for a current selected line, if yes, I point it to nil and then I use the singleton of the UIMenuController to disappear the menu and deselect the line so the user can freely draw another line.

if (self.selectedLine) { self.selectedLine = nil; [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES]; }

And to prevent the panning while drawing a new one, I implemented the following method from the UIGestureRecognizerDelegate, so the tap gesture fails while the user is panning:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if (gestureRecognizer == self.moveRecognizer && otherGestureRecognizer == self.tapRecognizer) { return YES; } return NO; }


#8

Yet another similar but not quite the same solution.

My first solution involved (like another solution here) setting the selected line to nil. I didn’t bother checking to see if there was one selected at all since either way it would end up as nil :

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

...
...
}

However the problem that happened is that if there was a line selected with the delete menu popped up and I started drawing a new line, for a fraction of a second the selected line would pan then would stop panning, so it didn’t fully fix the bug. I thought about it and realized that this was happening because of the setting delaysTouchesBegan by the double tap recognizer. A previous solution here mentions implementing the protocol function gestureRecognizer:shouldRequireFailureOfGestureRecognizer: but I found an easier way to achieve the same (rather than implement a protocol function) was to send the pan gesture recognizer the message requireGestureRecognizerToFail:

I should note that this also works if pass the double tap recognizer instead of the single tap recognizer to the requireGestureRecognizerToFail. This actually makes more sense since it’s the doubletap recognizer that delays the touchesBegan event. Not really sure why it works with either single or double tap recognizer, probably some sort of chaining of events going on behind the scenes.


#9

Thanks pepelucho, my solution is similar and i was facing the same issue with the pan gesture triggering for a second.

[moveRecognizer requireGestureRecognizerToFail:tapRecognizer];

This code fixes it.

Cheers!


#10

[quote=“C6silver”]Yet another solution.

I first moved the deceleration of UIMenuController to the local interface and out of the tap method:

...
@property (nonatomic, strong)UIMenuController *menu;
...

Next, all I had to do was add the following method since we setup the pan gesture to already be a delegate.

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if (self.menu.menuVisible)
    {
        [self.menu setMenuVisible:NO];
        self.selectedLine = nil;
    }
        
    return YES;
}

The above gets called by the framework rather than me having to manage it. It simply checks to see if the menu is visible and if so it takes it away (since obviously the user wanted to do something else) and then clears the selected lines. This allows for a new line to be drawn and it also allows for the long press gesture and the corresponding movement of the selected line even if done initially while the menu was displayed.[/quote]

C6silver:
This is an AWESOME solution! I only did a small thing differently – instead of creating a property to hold the UIMenuController – I just called it like this: [[UIMenuController sharedMenuController] isMenuVisible].

I originally went the route that most people have posted by going into touchesBegan – however, my line kept moving a tiny bit. Thank you for sharing your solution!!! It was definitely helpful! :smiley:


#11

here’s my solution:

I added some code at very beginning of ‘moveLine:’

[code]- (void)moveLine:(UIPanGestureRecognizer *)gr
{
// If we have not selected a line, we do not do anything here
if (!self.selectedLine) return;

CGPoint point = [gr locationInView:self];
if (self.selectedLine == [self lineAtPoint:point]) // move the line only if the finger is on it
{
    [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES]; // first thing: hide menu controller!

// When the pan recognizer changes its position...
    if (gr.state == UIGestureRecognizerStateChanged) {
        // How far has tha pan moved?
        CGPoint translation = [gr translationInView:self];
    
        // Add the translation to the current beginning and end point of the line
        CGPoint begin = self.selectedLine.begin;
        CGPoint end = self.selectedLine.end;
        begin.x += translation.x;
        begin.y += translation.y;
        end.x += translation.x;
        end.y += translation.y;
    
        // Set the new beginning and end point of the line
        self.selectedLine.begin = begin;
        self.selectedLine.end = end;
    
        // Redraw the screen
        [self setNeedsDisplay];
    
        [gr setTranslation:CGPointZero inView:self];
    }
}

}
[/code]

and an ‘if statement’ in ‘touchesMoved:’ as well:

[code]- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// Let’s put in a log statement to see the order of events
NSLog(@"%@", NSStringFromSelector(_cmd));

// silver challenge if statement
if (self.selectedLine) {
    [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
    self.selectedLine = nil;
}

for (UITouch *t in touches) {
    NSValue *key = [NSValue valueWithNonretainedObject:t];
    BNRLine *line = self.linesInProgress[key];
    line.end = [t locationInView:self];
}

[self setNeedsDisplay];

}
[/code]

I removed the bug of the silver challenge and also a second boring bug that let you drag the selected line (the one with menu controller on it) even if your finger drags somewhere else.

I didn’t create menu controller property because it’s a singleton and then available with ‘sharedMenuController’ method within the entire code app.


#12

[quote=“C6silver”]Yet another solution.

I first moved the deceleration of UIMenuController to the local interface and out of the tap method:

...
@property (nonatomic, strong)UIMenuController *menu;
...

Next, all I had to do was add the following method since we setup the pan gesture to already be a delegate.

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if (self.menu.menuVisible)
    {
        [self.menu setMenuVisible:NO];
        self.selectedLine = nil;
    }
        
    return YES;
}

The above gets called by the framework rather than me having to manage it. It simply checks to see if the menu is visible and if so it takes it away (since obviously the user wanted to do something else) and then clears the selected lines. This allows for a new line to be drawn and it also allows for the long press gesture and the corresponding movement of the selected line even if done initially while the menu was displayed.[/quote]

Great solution! But how did you know to override -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch?
Which gesture recognizer is being used as the argument when this method gets called? Also, how come we do not have to edit touchesBegan: or strokeLine:?


#13

Probably went a different route from most of the other solution. Initially went for the touchBegan: method that others was using, but it was still moving the line slightly, so I thought, why not be more specific when to trigger moveLine: ?

So we can track the UILongPressGestureRecogniser state

@interface BNRDrawView () <UIGestureRecognizerDelegate>
@property (nonatomic, strong) UILongPressGestureRecognizer *pressRecogniser;
@end

@implementation BNRDrawView

- (instancetype)initWithFrame:(CGRect)frame {
...
        self.pressRecogniser = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
        [self addGestureRecognizer:self.pressRecogniser];
...
}

We want to deselect the selectedLine and hide the menu when we start drawing

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    
    if (self.selectedLine) {
        [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
        self.selectedLine = nil;
    }
...
}

Basically defining pan gesture to only work if user is long press on a line

- (void)panning:(UIPanGestureRecognizer *)gestureRecogniser {
    if (!self.selectedLine) {
        return;
    }
    
    if (gestureRecogniser.state == UIGestureRecognizerStateChanged &&
        self.pressRecogniser.state == UIGestureRecognizerStateChanged) {
        ...
    }
}