UIPanGestureRecognizer and Simultaneous Recognizers

Once a line is selected during a long press, you want the user to be able to move that line around the screen by dragging it with a finger. So you need a gesture recognizer for a finger moving around the screen. This gesture is called panning, and its gesture recognizer subclass is UIPanGestureRecognizer.

Normally, a gesture recognizer does not share the touches it intercepts. Once it has recognized its gesture, it eats that touch, and no other recognizer gets a chance to handle it. In your case, this is bad: the entire pan gesture you want to recognize happens within a long press gesture. You need the long press recognizer and the pan recognizer to be able to recognize their gestures simultaneously. Let’s see how to do that.

First, in the class extension in BNRDrawView.m, declare that BNRDrawView conforms to the UIGestureRecognizerDelegate protocol. Then, declare a UIPanGestureRecognizer as a property so that you have access to it in all of your methods.

@​i​n​t​e​r​f​a​c​e​ ​B​N​R​D​r​a​w​V​i​e​w​ ​(​)​ ​<​U​I​G​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​D​e​l​e​g​a​t​e​>​

@​p​r​o​p​e​r​t​y​ ​(​n​o​n​a​t​o​m​i​c​,​ ​s​t​r​o​n​g​)​ ​U​I​P​a​n​G​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​ ​*​m​o​v​e​R​e​c​o​g​n​i​z​e​r​;​
@​p​r​o​p​e​r​t​y​ ​(​n​o​n​a​t​o​m​i​c​,​ ​s​t​r​o​n​g​)​ ​N​S​M​u​t​a​b​l​e​D​i​c​t​i​o​n​a​r​y​ ​*​l​i​n​e​s​I​n​P​r​o​g​r​e​s​s​;​
@​p​r​o​p​e​r​t​y​ ​(​n​o​n​a​t​o​m​i​c​,​ ​s​t​r​o​n​g​)​ ​N​S​M​u​t​a​b​l​e​A​r​r​a​y​ ​*​f​i​n​i​s​h​e​d​L​i​n​e​s​;​

@​p​r​o​p​e​r​t​y​ ​(​n​o​n​a​t​o​m​i​c​,​ ​w​e​a​k​)​ ​B​N​R​L​i​n​e​ ​*​s​e​l​e​c​t​e​d​L​i​n​e​;​

@​e​n​d​

In BNRDrawView.m, add code to initWithFrame: to instantiate a UIPanGestureRecognizer, set two of its properties, and attach it to the BNRDrawView.

[​s​e​l​f​ ​a​d​d​G​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​:​p​r​e​s​s​R​e​c​o​g​n​i​z​e​r​]​;​

s​e​l​f​.​m​o​v​e​R​e​c​o​g​n​i​z​e​r​ ​=​ ​[​[​U​I​P​a​n​G​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​ ​a​l​l​o​c​]​ ​i​n​i​t​W​i​t​h​T​a​r​g​e​t​:​s​e​l​f​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​a​c​t​i​o​n​:​@​s​e​l​e​c​t​o​r​(​m​o​v​e​L​i​n​e​:​)​]​;​
s​e​l​f​.​m​o​v​e​R​e​c​o​g​n​i​z​e​r​.​d​e​l​e​g​a​t​e​ ​=​ ​s​e​l​f​;​
s​e​l​f​.​m​o​v​e​R​e​c​o​g​n​i​z​e​r​.​c​a​n​c​e​l​s​T​o​u​c​h​e​s​I​n​V​i​e​w​ ​=​ ​N​O​;​
[​s​e​l​f​ ​a​d​d​G​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​:​s​e​l​f​.​m​o​v​e​R​e​c​o​g​n​i​z​e​r​]​;​

There are a number of methods in the UIGestureRecognizerDelegate protocol, but you are only interested in one – gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:. A gesture recognizer will send this message to its delegate when it recognizes its gesture but realizes that another gesture recognizer has recognized its gesture, too. If this method returns YES, the recognizer will share its touches with other gesture recognizers.

In BNRDrawView.m, return YES when the _moveRecognizer sends the message to its delegate.

-​ ​(​B​O​O​L​)​g​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​:​(​U​I​G​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​ ​*​)​g​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​
 ​ ​ ​ ​s​h​o​u​l​d​R​e​c​o​g​n​i​z​e​S​i​m​u​l​t​a​n​e​o​u​s​l​y​W​i​t​h​G​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​:​(​U​I​G​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​ ​*​)​o​t​h​e​r​
{​
 ​ ​ ​ ​i​f​ ​(​g​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​ ​=​=​ ​s​e​l​f​.​m​o​v​e​R​e​c​o​g​n​i​z​e​r​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​r​e​t​u​r​n​ ​Y​E​S​;​
 ​ ​ ​ ​}​
 ​ ​ ​ ​r​e​t​u​r​n​ ​N​O​;​
}​

Now when the user begins a long press, the UIPanGestureRecognizer will be allowed to keep track of this finger, too. When the finger begins to move, the pan recognizer will transition to the began state. If these two recognizers could not work simultaneously, the long press recognizer would start, and the pan recognizer would never transition to the began state or send its action message to its target.

In addition to the states you have already seen, a pan gesture recognizer supports the changed state. When a finger starts to move, the pan recognizer enters the began state and sends a message to its target. While the finger moves around the screen, the recognizer transitions to the changed state and sends its action message to its target repeatedly. Finally, when the finger leaves the screen, the recognizer’s state is set to ended, and the final message is delivered to the target.

Now you need to implement the moveLine: method that the pan recognizer sends its target. In this implementation, you will send the message translationInView: to the pan recognizer. This UIPanGestureRecognizer method returns how far the pan has moved as a CGPoint in the coordinate system of the view passed as the argument. When the pan gesture begins, this property is set to the zero point (where both x and y equal zero). As the pan moves, this value is updated – if the pan goes very far to the right, it has a high x value; if the pan returns to where it began, its translation goes back to the zero point.

In BNRDrawView.m, implement moveLine:. Notice that because you will send the gesture recognizer a method from the UIPanGestureRecognizer class, the parameter of this method must be a pointer to an instance of UIPanGestureRecognizer rather than UIGestureRecognizer.

-​ ​(​v​o​i​d​)​m​o​v​e​L​i​n​e​:​(​U​I​P​a​n​G​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​ ​*​)​g​r​
{​
 ​ ​ ​ ​/​/​ ​I​f​ ​w​e​ ​h​a​v​e​ ​n​o​t​ ​s​e​l​e​c​t​e​d​ ​a​ ​l​i​n​e​,​ ​w​e​ ​d​o​ ​n​o​t​ ​d​o​ ​a​n​y​t​h​i​n​g​ ​h​e​r​e​
 ​ ​ ​ ​i​f​ ​(​!​s​e​l​f​.​s​e​l​e​c​t​e​d​L​i​n​e​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​r​e​t​u​r​n​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​/​/​ ​W​h​e​n​ ​t​h​e​ ​p​a​n​ ​r​e​c​o​g​n​i​z​e​r​ ​c​h​a​n​g​e​s​ ​i​t​s​ ​p​o​s​i​t​i​o​n​.​.​.​
 ​ ​ ​ ​i​f​ ​(​g​r​.​s​t​a​t​e​ ​=​=​ ​U​I​G​e​s​t​u​r​e​R​e​c​o​g​n​i​z​e​r​S​t​a​t​e​C​h​a​n​g​e​d​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​H​o​w​ ​f​a​r​ ​h​a​s​ ​t​h​e​ ​p​a​n​ ​m​o​v​e​d​?​
 ​ ​ ​ ​ ​ ​ ​ ​C​G​P​o​i​n​t​ ​t​r​a​n​s​l​a​t​i​o​n​ ​=​ ​[​g​r​ ​t​r​a​n​s​l​a​t​i​o​n​I​n​V​i​e​w​:​s​e​l​f​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​A​d​d​ ​t​h​e​ ​t​r​a​n​s​l​a​t​i​o​n​ ​t​o​ ​t​h​e​ ​c​u​r​r​e​n​t​ ​b​e​g​i​n​n​i​n​g​ ​a​n​d​ ​e​n​d​ ​p​o​i​n​t​s​ ​o​f​ ​t​h​e​ ​l​i​n​e​
 ​ ​ ​ ​ ​ ​ ​ ​C​G​P​o​i​n​t​ ​b​e​g​i​n​ ​=​ ​s​e​l​f​.​s​e​l​e​c​t​e​d​L​i​n​e​.​b​e​g​i​n​;​
 ​ ​ ​ ​ ​ ​ ​ ​C​G​P​o​i​n​t​ ​e​n​d​ ​=​ ​s​e​l​f​.​s​e​l​e​c​t​e​d​L​i​n​e​.​e​n​d​;​
 ​ ​ ​ ​ ​ ​ ​ ​b​e​g​i​n​.​x​ ​+​=​ ​t​r​a​n​s​l​a​t​i​o​n​.​x​;​
 ​ ​ ​ ​ ​ ​ ​ ​b​e​g​i​n​.​y​ ​+​=​ ​t​r​a​n​s​l​a​t​i​o​n​.​y​;​
 ​ ​ ​ ​ ​ ​ ​ ​e​n​d​.​x​ ​+​=​ ​t​r​a​n​s​l​a​t​i​o​n​.​x​;​
 ​ ​ ​ ​ ​ ​ ​ ​e​n​d​.​y​ ​+​=​ ​t​r​a​n​s​l​a​t​i​o​n​.​y​;​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​S​e​t​ ​t​h​e​ ​n​e​w​ ​b​e​g​i​n​n​i​n​g​ ​a​n​d​ ​e​n​d​ ​p​o​i​n​t​s​ ​o​f​ ​t​h​e​ ​l​i​n​e​
 ​ ​ ​ ​ ​ ​ ​ ​s​e​l​f​.​s​e​l​e​c​t​e​d​L​i​n​e​.​b​e​g​i​n​ ​=​ ​b​e​g​i​n​;​
 ​ ​ ​ ​ ​ ​ ​ ​s​e​l​f​.​s​e​l​e​c​t​e​d​L​i​n​e​.​e​n​d​ ​=​ ​e​n​d​;​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​R​e​d​r​a​w​ ​t​h​e​ ​s​c​r​e​e​n​
 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​N​e​e​d​s​D​i​s​p​l​a​y​]​;​
 ​ ​ ​ ​}​
}​

Build and run the application. Touch and hold on a line and begin dragging – and you will immediately notice that the line and your finger are way out of sync. This makes sense because you are adding the current translation over and over again to the line’s original end points. You really need the gesture recognizer to report the change in translation since the last time this method was called instead. Fortunately, you can do this. You can set the translation of a pan gesture recognizer back to the zero point every time it reports a change. Then, the next time it reports a change, it will have the translation since the last event.

Near the bottom of moveLine: in BNRDrawView.m, add the following line of code.

 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​N​e​e​d​s​D​i​s​p​l​a​y​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​[​g​r​ ​s​e​t​T​r​a​n​s​l​a​t​i​o​n​:​C​G​P​o​i​n​t​Z​e​r​o​ ​i​n​V​i​e​w​:​s​e​l​f​]​;​
 ​ ​ ​ ​}​
}​

Build and run the application and move a line around. Works great!

Before moving on, let’s take a look at a property you set in the pan gesture recognizer – cancelsTouchesInView. Every UIGestureRecognizer has this property and, by default, this property is YES. This means that the gesture recognizer will eat any touch it recognizes so that the view will not have a chance to handle it via the traditional UIResponder methods, like touchesBegan:withEvent:.

Usually, this is what you want, but not always. In this case, the gesture that the pan recognizer recognizes is the same kind of touch that the view handles to draw lines using the UIResponder methods. If the gesture recognizer eats these touches, then users will not be able to draw lines.

When you set cancelsTouchesInView to NO, touches that the gesture recognizer recognizes also get delivered to the view via the UIResponder methods. This allows both the recognizer and the view’s UIResponder methods to handle the same touches. If you are curious, comment out the line that sets cancelsTouchesInView to NO and build and run again to see the effects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset