Is there any way to show a callout when a person touch a MKPolylineView?
i tried to add a UITapGestureRecognizer and in selector display a callout in some coordinate. but didnt work. any suggestions for making this?
the following is what i tried in method
- (MKOverlayView *)mapView:(MKMapView *)mapa viewForOverlay:(id <MKOverlay>)overlay
self.polylineView = [[MKPolylineView alloc] initWithPolyline: self.polyline];
self.polylineView.strokeColor = [UIColor blackColor];
self.polylineView.lineWidth = 5.0;
self.polylineView.alpha = 0.7;
UITapGestureRecognizer *touchOnView = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(addBubble:)];
[touchOnView setNumberOfTapsRequired:1];
[touchOnView setNumberOfTouchesRequired:1];
[self.polylineView addGestureRecognizer:touchOnView];
Very interesting question - I'd never even thought of putting a gesture recognizer on a map overlay. With a bit of experimentation, I've verified it is possible to detect a tap on the MKPolylineView. Just as you found, the tap gestures don't work on map overlays. So instead I put the tap gesture on the MKMapView rather than on the MKPolylineView. Then to handle the tap:
- (void)handleTapGesture:(UIGestureRecognizer*)gestureRecognizer
{
if (measureLine != nil)
{
UIView* hitView = [self.polylineView hitTest:[gestureRecognizer locationInView:self.polylineView] withEvent:nil];
}
}
hitView will be nil if your tap was outside of the MKPolylineView, or it will be self.polylineView if the tap was inside.
However, this might not behave quite as you'd like. For horizontal and vertical lines, it works perfectly because the size of the underlying view is roughly the same size as the line. But for a 45 degree line, the underlying view has to be much larger than than the line, because it is an axis aligned bounding box (AABB). If you think about a 45 degree line, to enclose it using only horizontal and vertical lines you will end up with a large area - much larger than you'd want to detect taps in.
e.g.
--------
| / |
| / |
| / |
| / |
|/ |
--------
But using a tap gesture or the hit test will always result in recognizing taps inside these AABBs. So regardless of where you try to attach your gesture - e.g. to the MKPolylineView as you tried, or to the MKMapView, you'll get spurious results. The problem gets worse for longer lines - if you imagine a line going from the top right of your map view to the bottom left, the AABB that you'd need to enclose it would cover the entire area of the map view, meaning that a tap in the top left or bottom right would be interpreted as hitting the MKPolylineView.
To solve the problem, I'd suggest the following approach:
Use a tap gesture recognizer on the map view
In your method that handles the tap:
convert the screen tap position to a map coordinate
loop through each of your polylines (unless you have only 1)
for each point in the polyline take the line segment that connects that point to the next point, and calculate the distance of your map coordinate away from this line. Use trigonometry to calculate this.
if the distance is very close to this segment, then stop checking the rest of the segments and handle whatever callouts etc you wanted to show
if the distance is not close, then move onto the next point and the line segment that connects it to the next but one point
This approach is guaranteed to work regardless of the length of your polylines, or what angle they are at. There are no AABB concerns. The downside is that all of those distance calculations might be computationally expensive - so if your polylines are made up of a great number of points, or if you have a large number of polylines, then you might not be able to do all of those calculations without blocking the UI from being responsive, meaning you'd need to move it into a background thread. If you only have a handful of polylines, and/or they are made up of few points, then you'll be fine.
Related
I have found a few other questions and answers similar to this, but none of them quite work perfect for me.
My vision is to have a horizontal scrollable view at the bottom of the screen where I can scroll through what will look like cards in a hand. Ideally, I could eventually make the card in the middle scaled up a bit and give it a highlighted look to show the user which card is selected. Even better would be if I could figure out how to keep the scroll view resizing to fir the number of sprites (cards) in the view.
Anyways, I am still very new to XCode and Swift, so it is hard for me to take what I find and change it. But, I am hoping to learn fast.
What I understand so far is that a UIScrollView could overlay the scene and with a moveable spritenode I could scroll through the view. The view would then translate the coordinates somehow to the SpriteKit Scene to move the sprites that will look like they are in the view. I think that's how it works. Any help would be great. I am pretty stuck. <3
You have to make your own logic that takes place in touchesMoved() using a global/member variable.
Unfortunately, a lot of gamedev and SK is math and logic.. You have to come up with your own problems and solutions.. There is no manual because the possibilities in programming and Swift are endless :)
Moving the cards:
Basically, you compare each touch location to the last one, and this becomes a "delta value" that you can use to perform actions.
Example, if I touch in the center of the screen, my touch location is 0,0 (or whatever your anchorpoint is set to). If I move my finger right, then I'm now at say 25, 0... This creates a "delta value" of +25x.
With that delta value, you can perform various actions such as moveBy for all the cards... so if I have a deltaX of +25, then I need to move all of the card nodes to the right (by a certain amount that you will determine according to your preferences). If I have a deltaX of -25, I move the cards to the left by a certain amount.
Where you do the actual moving is up to you--you could put a function in update() or touchesMoved() that constantly moves the cards a certain direction at a certain rate of that deltaX value..
Ok that was a mouthful... Maybe this will help:
for touch in touches {
myGlobalDeltaX = myDeltaXFunc(currentTouch: touch)
myMoveFunc(cards: allTheCards, byDeltaX: myGlobalDeltaX)
- You can search on how to make a Delta function, but it really is just the same thing from Algebra.
- myMoveFunc can be something as simple as iterating through all of your card nodes then running .moveBy on them at the same time.
Middle detection:
To detect which card is in the center, you would put in touchesEnded() or update() a call to check the name / identity of the node in the center of the screen... so something like
// `self` here refers to your GameScene class' instance, which is just an `SKScene` object
let centerX = self.frame.midX
let centerY = self.frame.midY
let center = CGPoint(x: centerX, y: centerY)
let centerNode = self.nodes(at: center)
You would obviously want to change centerX and centerY to wherever it is you want the middle card to be :) Right now, this is just in the dead-center of the screen.
Once you have a centerNode, you would then just need to do whatever function you have created to "select" it.
let selectedCard = centerNode
mySelectionFunc(middleCard: selectedCard)
This may look like a lot, but I drew out the steps to make understanding it a bit easier.. You can do all of this in one line if desired.
mySelectionFunc(middleCard: self.nodes(at: CGPoint(x: self.frame.x, y: self.frame.y)))
Hope this helps some!
Within a UISrollView, I have several programmatically-added subviews representing nodes in a tree:
Each node's frame includes the node itself plus the line connecting it to its parent. (I did this to facilitate animation of the line with the node.) In the picture below, the frame is drawn for one of the nodes:
When the user taps on one of the nodes, two child nodes are "born". I'd like to animate this by having the child nodes descend down from behind the parent node. My basic animation code is:
- (void)descendFromParent
{
// Do nothing if this is root node
if (!self.parent)
return;
// Move node to parent location
self.frame = CGRectMake(self.frame.origin.x + self.parent.nodeFrame.origin.x - self.nodeFrame.origin.x,
self.frame.origin.y + self.parent.nodeFrame.origin.y - self.nodeFrame.origin.y,
self.frame.size.width, self.frame.size.height);
// Animate the move back to original location
[UIView animateWithDuration:1.0
delay:0.0
options:0
animations:^{
self.frame = self.trueFrame;
}
completion:nil];
}
(nodeFrame is a frame containing just the circular part of the view.)
The problem of course is that as the child node is descending, it (especially the line) is visible on top of and above the parent node:
I've tried a lot of different ways to make this work -- using CGContextClip, etc. -- but none of them work, mainly because drawRect: isn't called during the animation.
How can I make this work?
I think your problem is not how to arrange the views in the correct view hierarchy, but the following:
After you have arranged the child disks behind the parent disk, you want them to slide down, and while they are sliding, the edge that connects their centers should have first zero length (when all 3 disks are at the same place), and should then be extended until it reaches its final length in the end.
If this is the case, one possible solution would be:
Lets call the initial x,y center coordinate of one disk (0,0), and the final coordinate (X,Y). Using the usual animation code, it is easy to move the child center to (X,Y) in time t.
Now assume you have an additional image view that shows the edge along the diagonal. In the end position, the center of the "edge view" is at (X/2,Y/2), and its size is (X,Y). If this view is placed behind all others, it will connect the two disks at their final position.
The animation consists now of 1) changing the center property from the initial position (0,0) to (X/2,Y/2), and 2) changing the scale of the view (using its transform property) from zero to the final size, also in time t.
This should do it.
You may want to look at the UIView methods
-insertSubview:belowSubview:
-insertSubview:atIndex:
-sendSubviewToBack:
In your case, you can either send the two subviews to the background with -sendSubviewToBack: after you have added them. Or you can just add them with -insertSubview:atIndex: and provide 0 as the index.
You can specific an index for a subview, try inserting the nodes you want to animate at index 0 and have the blue nodes at a higher index.
I need help making a drawing demo.
When the user draws a line using their finger,
the line has directional arrows on both ends.
When their finger releases, it draws the line with
"?" (question-mark) in center of the line.
Then, when user taps on the "?", it will show a new view and the user can enter a
value, and the value is in that line.
And we can add multiple lines on capture-image and also we can delete
selected line.
I don't understand how can I start developing these features so
please give me an idea or any link, or suggestion to start develop this
feature.
You should use a UITapGestureRecognizer and a UIBezierPath. Have it so that person taps at one point and then taps at a second point, then make a UIBezierPath between the two points. To get the question mark in the middle you could make it so that the line goes from the first point to (half the distance between point 1 and point 2 - 20pt). And then do the same with the other half of the line (so that you now have a space in the middle of the line.
You can take advantage of the Core Graphics Framework to draw shapes in iOS. This allows you to draw lines, circles, arrows, rectangles etc. Here is some example code to draw a line between 2 points (taken from: How do I draw a line on the iPhone?):
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat red[4] = {1.0f, 0.0f, 0.0f, 1.0f};
CGContextSetStrokeColor(c, red);
CGContextBeginPath(c);
CGContextMoveToPoint(c, 5.0f, 5.0f);
CGContextAddLineToPoint(c, 50.0f, 50.0f);
CGContextStrokePath(c);
First define a subclass of UIView to create a space to draw in. Then use a UITapGestureRecognizer to detect taps. As #WyattMufson suggested in his answer, I would get the user to tap once to get the start point of the line and then tap again to get the end point. This is done to ensure that only straight lines can be drawn.
Once you have the start point and end point you can calculate the mid point and connect the two points with a '?' character in between. Then save these line coordinates (start, mid and end points) in some sort of data structure to keep track of all the lines that have been drawn.
When the user taps on a specific line, you can use the saved line coordinates information to detect whether the tap occurred on a line or not (you will have to perform some calculations to do this). If so, display a popover that accepts user input. Once the user inputs a value, close the popover and replace the '?' with the new value.
For line deletion, you could use the UILongPressGestureRecognizer. The user would tap and hold on a drawn line, which would bring up a popover to confirm whether the user wants to proceed with deletion or not. If they do, access the saved line coordinates to detect whether the hold occurred on a line or not. If so, erase that line.
Here are some references to get you started:
How make a simple drawing app with UIKit
How do I draw a line on the iPhone?
Check is a point (x,y) is between two points drawn on a straight line
How to get UITouch location from UIGestureRecognizer
How to traverse the peg through all the squares in a Monopoly type board Game ?
I have written a function for the movepegbutton which on click moves the peg to the destination position which it gets from the random generated number.
This is what I have written so far for the Move peg Button.
(IBAction)movePegButton:(id)sender
{
self.pegDestinationPositionIndex = self.pegCurrentPositionIndex + self.randomNumber;
if (self.pegDestinationPositionIndex > [self.boardCordinatesArray count] - 1)
{
self.pegDestinationPositionIndex = self.pegDestinationPositionIndex - [self.boardCordinatesArray count];
}
[self animatePeg];
self.pegCurrentPositionIndex = self.pegDestinationPositionIndex;
}
This is what I have written to animate the peg.
(void)animatePeg
{
int destinationXCord = [[[self.boardCordinatesArray objectAtIndex:self.pegDestinationPositionIndex] objectForKey:#"x"]intValue];
int destinationYCord = [[[self.boardCordinatesArray objectAtIndex:self.pegDestinationPositionIndex] objectForKey:#"y"]intValue];
[UIView animateWithDuration:1.0 animations:^{
self.peg.center = CGPointMake(destinationXCord, destinationYCord);
}];
}
So far the peg moves correctly to the destination square but it is not traversing through all the squares in its way, for e.g. if it is 8X8 square, for first run dice rolls 6, the peg moves correctly to 6th square, for second run dice rolls 5, the peg moves correctly to the destination square but it directly jumps over to that position diagonally, it doesn't traverse the squares which it has in its way.
I am stuck here, how should i do it ??
So your peg is supposed to walk in a square, ei. follow the edge of the board, but instead cuts corners, am I right?
If I am so, then the answer is very straight forward. To make sure that the peg follows the squares lain on the board, you must make it move to every square in the path from source square to destination square, otherwise it will simply cut the corner and go straight for the destination square.
If the player rolls a 4, and is standing on square 9 and must reach square 13. Then instead of making the peg move directly to square 13, I would force the peg to go first to 10, then 11, then 12 and then at last square 13.
Of course if the squares are aligned in a square manor, that is; four edges, then some optimization could be done, and only split up the path if a corner is in between the source square and the destination square.
And of course this optimization could also be applied even though the squares are not aligned in a square like manor, just as long as it follows some path, and you know where the 'corners' are.
Edit:
To try and test my paint skills and to try and visualize the problem at hand, I have created something within paint.
What you are doing in the presented code is Case 1. You are doing a single animation when moving the peg. What you really want do is Case 2, here your function -(void)animatePeg; should recognize that the destination square is more than 1 square away, and therefore not do a single move animation, but a series of move animations, to be more precise, the number of animations is always indexOfDestinationSquare - indexOfSourceSquare. So in the illustrated case, the peg should do a total of eight move animations instead of only one.
Case 3 shows how this process could be optimized by recognizing the direction of the move, and only split when going around a corner.
I hope this clarifies what approach I would take. To do this approach you need to extend your -(void)animatePeg; function to be able to split up the single move into a series of moves, and then animate the moves one-by-one.
In my app I have a small subview that I'd like to tether in the bottom right corner and allow to be moved only vertically for a fixed distance.
I have some code to allow it to be dragged, but no further constraints:
- (void)viewDragged:(UIPanGestureRecognizer *)gesture
{
CGPoint translation = [gesture translationInView:keyView];
// move view
keyView.center = CGPointMake(keyView.center.x + translation.x,
keyView.center.y + translation.y);
// reset translation
[gesture setTranslation:CGPointZero inView:keyView];
}
I've looked around on StackOverflow and tried a couple of ways, but the only one that worked didn't take into account diagonal movement, and I couldn't find anything in regards to tethering it to one spot.
So does anyone know how this can be done? Many thanks
EDIT: Also, does anyone know of a good, informative reference doc (other than the Apple one) for topics of this nature?
can you just add in a line to the view dragged code that says if keyview.centre.y is less than some threshold (where you want it to stop), then don't change it below that. eg.
keyView.centre = CGPointMake(keyView.center.x,
keyView.center.y - translation.y < threshold ? threshold : keyView.center.y);
also remove the change to the x parameter to stop it moving diagonally.