How can I simulate a two-finger left-to-right swipe within a UITextView in UIAutomation?
I've tried this:
var textView = target.frontMostApp().mainWindow().textViews()[0];
textView.dragInsideWithOptions({
touchCount:2,
startOffset:{x:0.2, y:5.80},
endOffset:{x:0.8, y:5.80},
duration:1.5
});
But apparently it is not having any effect. Sometimes it looks like it is doing only one finger gesture even I am passing 2 as the touch count.
One concern is: How UIAutomation knows where place the second touch? I am only specifying the start and end of one touch, not two. Is this correct? Maybe this is the origin of my problem?
Also, I have not very clear the coordinates system of objects. Unless I pass very small values for x, y I usually get warnings: out of bounds. Passing coordinates I would usually pass using Objective-C seem to be way large :(
A very late answer...
I met your problem today, in 2015, and I have the chance to have Apple doc for this function. Wasn't the case in 2012 perhaps...
Apple docs says :
You can use offsets to achieve finer precision in specifying the hitpoint within the rect for the specified element. The offset comprises a pair of x and y values, each ranging from 0.0 to 1.0. These values represent, respectively, relative horizontal and vertical positions within the rect, with {x:0.0, y:0.0} as the top left and {x:1.0, y:1.0} as the bottom right. Thus, {x:0.3, y:0.6} specifies a position just below and to the left of center, and {x:1.0, y:0.5} specifies a position centered vertically at the far right.
This example performs a slow drag within the target element from left edge to right edge, just below the top:
target.dragInsideWithOptions({startOffset:{x:0.0, y:0.1}, endOffset:{x:1.0, y:0.1}, duration:1.5});
So x and y values are relative within the rect you choose.
In your example :
startOffset:{x:0.2, y:5.80}
You begin the swipe from left in the textview at 20% of its length, and y is 5.80 times beyond the border...
It would better work with values as :
startOffset:{x:0.0, y:0.1}, endOffset:{x:0.5, y:0.1}
For my part, I made the two-finger left-to-right swipe work with this code :
target.frontMostApp().mainWindow().dragInsideWithOptions({touchCount:2, startOffset:{x:0.0, y:0.1}, endOffset:{x:0.5, y:0.1}, duration:1});
Related
I have the following view where I'm using a pan gesture in the upwards or downwards direction to rotate it positively or negatively:
I'm wondering, is there a mathematical equation to precisely covert the amount panned to the amount it should be rotated so the timing is correct to keep the users finger on the view while it is rotating? For instance, if the pan translation comes back as 1, what would the proper amount be to rotate it?.
There are a few details you need to provide to give a meaningful answer:
Are you rotating the view about its centre (the default) or is there an anchor point?
Since the view is rotating, while the touch is moving strictly vertically in the superview, what's the expected behaviour as the view rotates further away from the vertical line defining the pan?
Is there a reason you're using a pan gesture instead of a rotation gesture, or even just direct touch tracking? It seems like it creates more problems than it solves.
I'm going to assume the view is rotating about its centre for the sake of simplicity, and I'll use a pan starting on the right side of the view as an example, with the rotation not exceeding ±90°. Here are two options:
Movement up and down translates linearly to the angle of rotation, i.e., a pan of a given distance rotates the view the same amount, no matter where the pan starts. In that case, you need to decide what the top and bottom limits of the pan are. They might be the bounds of the superview. Regardless, you want to convert the distance travelled in the Y direction to a value between -1 and 1, where -1 represents the bottom limit and 1 represents the top limit. Something like 2 * (dy / superview.bounds.size.height - 0.5). Multiply that by π/2 (M_PI_2 in math.h) to scale from the range [-1, 1] to the range [-π/2, π/2] and you've got the angle to add/subtract from the view's rotation at the beginning of the gesture.
The view tracks the touch so that its right edge is always "pointed at" the touch. In this case, pan isn't terribly useful because you only need the location of the touch in the superview, not the distance travelled. Calculate dx and dy as the difference in x and y coordinates from the view's centre to the touch location. Then calculate atan2(dy, dx) and you've got the absolute angle of rotation for the view.
I hope this puts you on the right track.
The answer is the angle would be panAmount.y / rectangleWidth.
Here is proof: https://math.stackexchange.com/questions/322694/angle-of-rotated-line-segment
I am making a vintage phone and got a working starting code where user moves his fingers over a UIImageView numbers and it rotates dial. It then moves it back to original position. See screenshot.
The three problems that I can't seem to figure out are;
How can I restrict user to rotate only in clockwise direction? Currently user can move it in any direction (clockwise and counter clockwise)
How can I detect which number that user selected? Meaning user touched 1 or 3 or 5? I need this info so that I can stop the rotation when that number reaches the bar on the right.
In my current code when I stop the rotation and let go of the circle, it moves back to it's place by moving back counter clockwise. It works well if I select 1,2,3,4 but for any number 5 and up the dial moves clockwise back to its original position. How can I force counter clockwise motion on touchesEnded?
Let’s assume that you’re talking about this gesture:
Source.
Build a single-touch rotation gesture recognizer. After building the gesture recognizer correctly, you can just look at the rotation and see what to do with the rotary pad.
There are several things you’ll consider when building a single-touch rotation gesture recognizer. If you look at UIRotationGestureRecognizer, it uses connection between two touches, backed by two fingers, to derive the current angle, then compares the angle to the previous angle, derived from an earlier touch change event, to see the delta.
Measuring the current angle
It takes two points to form a line and you need a line to know the angle. If you’re working with only one touch, you need an anchor point. There are many ways to send an anchor point to your gesture recognizer, and since you’re likely going to build a custom class, use delegation.
Accumulating rotation counts
If you simply note the angle and send off messages during touch changes, it’ll sometimes work. However, if you’d like to implement hysteresis (e.g. this rotary dial will only rotate once clockwise, then it tightens up), you’ll need to accumulate rotation counts for both clockwise and counter-clockwise directions.
Fortunately, you can assume that a) the touch events will not get dropped too often, and b) simply comparing the current angle against the past angle, seeing if they cross quadrant boundaries, will suffice.
For example:
If the touch moved from the top-left quadrant into the top-right quadrant, add one to the rotation count.
If the touch moved from the top-right quadrant into the top-left quadrant, subtract one from the rotation count.
(Yup, this actually works.)
Emitting the correct, accumulated rotation
If you want to emit rotation information exactly like how UIRotationGestureRecognizer did, there will be four things you’re tracking.
Starting Angle: The angle between a connection from the anchor point to the starting touch, and a connection from the anchor point to a fixed reference point.
Current Angle: The angle between a connection from the anchor point to the current touch, and a connection from the anchor point to a fixed reference point.
Rotation Count: The number of clockwise revolutions derived from continuously comparing the current value of Current Angle against its last value (as talked about in the last section). If the touch is moving counter-clockwise, then this count will go into negative.
You’ll provide Rotation Count * 2_PI + (Current Angle - Starting Angle) as the rotation.
OK, I would take a different approach. First, you want to create a RotaryDial class to encapsulate all of the behavior. Then you can just plug it into any view as you see fit.
To keep things simple I would consider making each number button a movable UIImageView, call it RotaryDialDigit or something like that. You would instantiate and place ten of those.
The dial "frame" would just tag along for the ride as the user moves one of the RotaryDialDigit buttons. It's just an image (unless you want the user to be able to touch it and do something with it.
From there, knowing which button is being held down and limiting its rotation to a given direction as well as stopping at at the bar is fairly easy stuff.
By using a protocol you can then have the RotaryDial instance tell the container when a number has been dialed. To the container RotaryDial would feel like a keypad sending a message every time a button is pressed. You really don't want the container bothering with anything other than completed number selections.
To detect which number is touched, when you create each number you should set the tag value of its UIView. Then when the user touches the number you can detect which UIView object it was by checking that tag value.
For the rotation problem, I'd suggest looking at how you are calculating the angle. At a guess I'd say for numbers greater than 4 (which you discern from the tag) you need to do something like subtract the angle you are currently calculating from 360 degrees (well 2Pi). (But I have a head cold right now so the actual math is escaping me :-) )
Without seeing your code, I assume the numbers are a static image and you are animating the finger holes as they rotate past each number. If so:
Detecting which number: defina a CGRect around each button. When the user taps the screen, check which rectangle contains the tap location.
Controlling rotation direction: as the user drags their finger, comtinuously calculate the angle from the dial stop to the current tap location. If the angle moves in the wrong direction, dont update the position of the finger hole. Note that trig functions return vales from +Pi to -Pi radians. For the digits greater than 5, rather than handle negative angles you will probably want to add 2Pi radians ( or 360 degrees) to the angle.
Rotating wrong way: the digits below 5 are generatting angles in the range of 0 to -Pi. Without seeing code, I suspect adding 2Pi to the angle will correct your rotation direction.
Here is a better dial:
Have fun!
I need to animate controls by moving them along the x axis from x to x-1000.
My container view is 200 pixels across and each control is 100 pixels wide.
There is a maximum of 4 controls (the controls are heavy and I need to re-use them)
So therefore, as I animate the controls from x to x-1000, I need to re-use them.
So as control 1 goes off to the left, it becomes invisible and needs to be re-positioned to the right hand side of the container view.
As I will be using an ease in function, the control needs to inherit the same speed and deceleration is it had before; so it literally just animates from right to left, once off screen, instantly re-positioned to the right of the container view, and carries on animating from right to left at the same deceleration rate.
Is there anyway to invoke a function for each frame of a CAPropertyAnimation? or something along those lines?
As far as I know, you can't get velocity information from core animation.
You will have to roll your own animation for this. Setup a timer, and give each view an xVelocity. Every time the timer fires, have it adjust the xVelocity of all objects (you could apply a sine curve to it to give it an ease/out effect). Then change the center point of all the objects.
I have a static map image with a bunch of circles and squares on it that depict cities. I have loaded the image into an imageView that is sub-classed under a scrollView so that I can capture user touches and zoom/scroll across the map. My challenge is that I want to pop-up a label whenever a user touches one of these circles/squares for a city to tell them which city it is and possibly load a detail view for the city. I figured I could pre-load all the relative CGPoints for the cities based on the imageView map into a dictionary so I can reference them during a "touchesBegan" event, but I'm quickly getting in over my head and possibly going about this the wrong way.
So far everything is working and I can capture the CGPoint x and y coordinates of touches. The biggest issue I have is determining the proximity of the user touches to a discrete point I may have in the dictionary. In other words if the dictionary has "Boston = NSPoint: {235, 118};" how can I tell when a user is close to that point without making them repeat the touch until it is exact? Is there an easy way to determine if a user touch is "close" to a pre-existing point? Am I going about this the right way?
Any advice or slaps in the back of the head are welcome.
Thanks, Mike
You could use UIButtons to represent the cities. Then you'll get the standard touch, highlight, etc, behaviors with less effort. Adding the buttons as subviews on your map should cause them to scale and scroll along with the map.
if i understand it correctly, you want to know if the point at which the user tapped is "close" enough to a point that is marked as a city.
you would have to quantify close i.e. set a threshold value after which the tap is farther, before which the tap is closer.
once you do that, calculate the cartesian coordinate distance sqrt ( (x1-x2)^2 + (y1-y2)^2)
for each element ( read dictionary with x,y values for cities) in the array and store the results in another array. then take the minimum of the result. the index of that result is the city that is closest to the tap if it is lesser than the said threshold.
you can either use an R-Tree, or you can calculate the proximity of the touch to each visible point in the current view. To calculate the proximity you would normally use the Pythagorean theorem but in this case you can skip the square-root because you're only comparing the relative sizes. Also you can declare a distance cut off if you like say 50 pixels squared to 2500. So you'd put the result into an object containing distance and reference point and put the objects in an NSMutableArray, not adding the results under your cutoff, and select the minimum result.
So if you have a touched point pT, then for each point pN, you'd calculate:
d=(pT.x-pN.x)*(pT.x-pN.x) + (pT.y-pN.y)*(pT.y-pN.y); //d is the squared distance
The point pN with the minimum d is the point that was closest to pT. And like I said if you want only touches within 10 pixels to count, you can test that d <= 10*10;
The method of testing for touches within a 20x20 square area works too, except if two points are within 20 pixels of each other, then you need to know which is the closest touched point.
I am trying to figure out how can you drag an image while constraining its movement along a certain path.
I tried several tricks including animation along a path, but couldn't get the animation to play and pause and play backwards - so that seems out of the question.
Any ideas ? anyone ?
What you're basically trying to do is match finger movement to a 'translation' transition.
As the user touches down and starts to move their finger you want to use the current touch point value to create a translation transform which you apply to your UIImageView. Here's how you would do it:
On touch down, save the imageview's starting x,y position.
On move, calculate the delta from old point to new one. This is where you can clamp the values. So you can ignore, say, the y change and only use the x deltas. This means that the image will only move left to right. If you ignore the x and use y, then it only moves up and down.
Once you have the 'new' calculated/clamped x,y values, use it to create a new transform using CGAffineTransformMakeTranslation(x, y). Assign this transform to the UIImageView. The image moves to that place.
Once the finger lifts, figure out the delta from the original starting x,y, point and the lift-off point, then adjust the ImageView's bounds and reset the transform to CGAffineTransformIdentity. This doesn't move the object, but it sets it so subsequent accesses to the ImageView use the actual position and don't have to keep adjusting for transforms.
Moving along on a grid is easy too. Just round out the x,y values in step 2 so they're a multiple of the grid size (i.e. round out to every 10 pixel) before you pass it on to make the translation transform.
If you want to make it extra smooth, surround the code where you assign the transition with UIView animation blocks. Mess around with the easing and timing settings. The image should drag behind a bit but smoothly 'rubber-band' from one touch point to the next.
See this Sample Code : Move Me