iOS + MKMapView user touch based drawing - ios

I have searched a lot for this question, but none of them seem to do exactly what I want.
A lot of tutorials show me how to add lines and polygons in code, but not with freehand drawing.
The question is the following one:
I am building a real estate application. If the user is on the MKMapView it has the ability to draw a rectangle/circle/... around a certain area where he/she wants to buy/rent a house. Then I need to display the results that correspond within the area the user has selected.
Currently I have a UIView on top of my MKMapView where I do some custom drawing, is there a way to translate points to coordinates from that or ..? Or is this completely not the way this is done ? I have also heard about MKMapOverlayView, etc .. but am not exactly sure how to use this.
Can anybody point me in the right direction or does he have some sample code or a tutorial that can help me accomplish what I am in need for?
Thanks

I have an app that basically does this. I have a map view, with a toolbar at the top of the screen. When you press a button on that toolbar, you are now in a mode where you can swipe your finger across the map. The start and end of the swipe will represent the corners of a rectangle. The app will draw a translucent blue rectangle overlay to show the area you've selected. When you lift your finger, the rectangular selection is complete, and the app begins a search for locations in my database.
I do not handle circles, but I think you could do something similar, where you have two selection modes (rectangular, or circular). In the circular selection mode, the swipe start and end points could represent circle center, and edge (radius). Or, the two ends of a diameter line. I'll leave that part to you.
Implementation
First, I define a transparent overlay layer, that handles selection (OverlaySelectionView.h):
#import <QuartzCore/QuartzCore.h>
#import <MapKit/MapKit.h>
#protocol OverlaySelectionViewDelegate
// callback when user finishes selecting map region
- (void) areaSelected: (CGRect)screenArea;
#end
#interface OverlaySelectionView : UIView {
#private
UIView* dragArea;
CGRect dragAreaBounds;
id<OverlaySelectionViewDelegate> delegate;
}
#property (nonatomic, assign) id<OverlaySelectionViewDelegate> delegate;
#end
and OverlaySelectionView.m:
#import "OverlaySelectionView.h"
#interface OverlaySelectionView()
#property (nonatomic, retain) UIView* dragArea;
#end
#implementation OverlaySelectionView
#synthesize dragArea;
#synthesize delegate;
- (void) initialize {
dragAreaBounds = CGRectMake(0, 0, 0, 0);
self.userInteractionEnabled = YES;
self.multipleTouchEnabled = NO;
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
}
- (id) initWithCoder: (NSCoder*) coder {
self = [super initWithCoder: coder];
if (self != nil) {
[self initialize];
}
return self;
}
- (id) initWithFrame: (CGRect) frame {
self = [super initWithFrame: frame];
if (self != nil) {
[self initialize];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// do nothing
}
#pragma mark - Touch handling
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [[event allTouches] anyObject];
dragAreaBounds.origin = [touch locationInView:self];
}
- (void)handleTouch:(UIEvent *)event {
UITouch* touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self];
dragAreaBounds.size.height = location.y - dragAreaBounds.origin.y;
dragAreaBounds.size.width = location.x - dragAreaBounds.origin.x;
if (self.dragArea == nil) {
UIView* area = [[UIView alloc] initWithFrame: dragAreaBounds];
area.backgroundColor = [UIColor blueColor];
area.opaque = NO;
area.alpha = 0.3f;
area.userInteractionEnabled = NO;
self.dragArea = area;
[self addSubview: self.dragArea];
[dragArea release];
} else {
self.dragArea.frame = dragAreaBounds;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self handleTouch: event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self handleTouch: event];
if (self.delegate != nil) {
[delegate areaSelected: dragAreaBounds];
}
[self initialize];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self initialize];
[self.dragArea removeFromSuperview];
self.dragArea = nil;
}
#pragma mark -
- (void) dealloc {
[dragArea release];
[super dealloc];
}
#end
Then I have a class that implements the protocol defined above (MapViewController.h):
#import "OverlaySelectionView.h"
typedef struct {
CLLocationDegrees minLatitude;
CLLocationDegrees maxLatitude;
CLLocationDegrees minLongitude;
CLLocationDegrees maxLongitude;
} LocationBounds;
#interface MapViewController : UIViewController<MKMapViewDelegate, OverlaySelectionViewDelegate> {
LocationBounds searchBounds;
UIBarButtonItem* areaButton;
And in my MapViewController.m, the areaSelected method is where I perform the conversion of touch coordinates to geographic coordinates with convertPoint:toCoordinateFromView: :
#pragma mark - OverlaySelectionViewDelegate
- (void) areaSelected: (CGRect)screenArea
{
self.areaButton.style = UIBarButtonItemStyleBordered;
self.areaButton.title = #"Area";
CGPoint point = screenArea.origin;
// we must account for upper nav bar height!
point.y -= 44;
CLLocationCoordinate2D upperLeft = [mapView convertPoint: point toCoordinateFromView: mapView];
point.x += screenArea.size.width;
CLLocationCoordinate2D upperRight = [mapView convertPoint: point toCoordinateFromView: mapView];
point.x -= screenArea.size.width;
point.y += screenArea.size.height;
CLLocationCoordinate2D lowerLeft = [mapView convertPoint: point toCoordinateFromView: mapView];
point.x += screenArea.size.width;
CLLocationCoordinate2D lowerRight = [mapView convertPoint: point toCoordinateFromView: mapView];
searchBounds.minLatitude = MIN(lowerLeft.latitude, lowerRight.latitude);
searchBounds.minLongitude = MIN(upperLeft.longitude, lowerLeft.longitude);
searchBounds.maxLatitude = MAX(upperLeft.latitude, upperRight.latitude);
searchBounds.maxLongitude = MAX(upperRight.longitude, lowerRight.longitude);
// TODO: comment out to keep search rectangle on screen
[[self.view.subviews lastObject] removeFromSuperview];
[self performSelectorInBackground: #selector(lookupHistoryByArea) withObject: nil];
}
// this action is triggered when user selects the Area button to start selecting area
// TODO: connect this to areaButton yourself (I did it in Interface Builder)
- (IBAction) selectArea: (id) sender
{
PoliteAlertView* message = [[PoliteAlertView alloc] initWithTitle: #"Information"
message: #"Select an area to search by dragging your finger across the map"
delegate: self
keyName: #"swipe_msg_read"
cancelButtonTitle: #"Ok"
otherButtonTitles: nil];
[message show];
[message release];
OverlaySelectionView* overlay = [[OverlaySelectionView alloc] initWithFrame: self.view.frame];
overlay.delegate = self;
[self.view addSubview: overlay];
[overlay release];
self.areaButton.style = UIBarButtonItemStyleDone;
self.areaButton.title = #"Swipe";
}
You'll notice that my MapViewController has a property, areaButton. That's a button on my toolbar, which normally says Area. After the user presses it, they are in area selection mode at which point, the button label changes to say Swipe to remind them to swipe (maybe not the best UI, but that's what I have).
Also notice that when the user presses Area to enter area selection mode, I show them an alert that tells them that they need to swipe. Since this is probably only a reminder they need to see once, I have used my own PoliteAlertView, which is a custom UIAlertView that users can suppress (don't show the alert again).
My lookupHistoryByArea is just a method that searches my database for locations, by the saved searchBounds (in the background), and then plots new overlays on the map at the found locations. This will obviously be different for your app.
Limitations
Since this is for letting the user select approximate areas, I did not consider geographic precision to be critical. It doesn't sound like it should be in your app, either. Thus, I just draw rectangles with 90 degree angles, not accounting for earth curvature, etc. For areas of just a few miles, this should be fine.
I had to make some assumptions about your phrase touch based drawing. I decided that both the easiest way to implement the app, and the easiest for a touchscreen user to use, was to simply define the area with one single swipe. Drawing a rectangle with touches would require 4 swipes instead of one, introduce the complexity of non-closed rectangles, yield sloppy shapes, and probably not get the user what they even wanted. So, I tried to keep the UI simple. If you really want the user drawing on the map, see this related answer which does that.
This app was written before ARC, and not changed for ARC.
In my app, I actually do use mutex locking for some variables accessed on the main (UI) thread, and in the background (search) thread. I took that code out for this example. Depending on how your database search works, and how you choose to run the search (GCD, etc.), you should make sure to audit your own thread-safety.

ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#end
ViewController.m
#import "ViewController.h"
#import <MapKit/MapKit.h>
#interface ViewController () <MKMapViewDelegate>
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#property (nonatomic, weak) MKPolyline *polyLine;
#property (nonatomic, strong) NSMutableArray *coordinates;
#property (weak, nonatomic) IBOutlet UIButton *drawPolygonButton;
#property (nonatomic) BOOL isDrawingPolygon;
#end
#implementation ViewController
#synthesize coordinates = _coordinates;
- (NSMutableArray*)coordinates
{
if(_coordinates == nil) _coordinates = [[NSMutableArray alloc] init];
return _coordinates;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (IBAction)didTouchUpInsideDrawButton:(UIButton*)sender
{
if(self.isDrawingPolygon == NO) {
self.isDrawingPolygon = YES;
[self.drawPolygonButton setTitle:#"done" forState:UIControlStateNormal];
[self.coordinates removeAllObjects];
self.mapView.userInteractionEnabled = NO;
} else {
NSInteger numberOfPoints = [self.coordinates count];
if (numberOfPoints > 2)
{
CLLocationCoordinate2D points[numberOfPoints];
for (NSInteger i = 0; i < numberOfPoints; i++)
points[i] = [self.coordinates[i] MKCoordinateValue];
[self.mapView addOverlay:[MKPolygon polygonWithCoordinates:points count:numberOfPoints]];
}
if (self.polyLine)
[self.mapView removeOverlay:self.polyLine];
self.isDrawingPolygon = NO;
[self.drawPolygonButton setTitle:#"draw" forState:UIControlStateNormal];
self.mapView.userInteractionEnabled = YES;
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.isDrawingPolygon == NO)
return;
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];
[self addCoordinate:coordinate];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.isDrawingPolygon == NO)
return;
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];
[self addCoordinate:coordinate];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.isDrawingPolygon == NO)
return;
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];
[self addCoordinate:coordinate];
[self didTouchUpInsideDrawButton:nil];
}
- (void)addCoordinate:(CLLocationCoordinate2D)coordinate
{
[self.coordinates addObject:[NSValue valueWithMKCoordinate:coordinate]];
NSInteger numberOfPoints = [self.coordinates count];
if (numberOfPoints > 2) {
MKPolyline *oldPolyLine = self.polyLine;
CLLocationCoordinate2D points[numberOfPoints];
for (NSInteger i = 0; i < numberOfPoints; i++) {
points[i] = [self.coordinates[i] MKCoordinateValue];
}
MKPolyline *newPolyLine = [MKPolyline polylineWithCoordinates:points count:numberOfPoints];
[self.mapView addOverlay:newPolyLine];
self.polyLine = newPolyLine;
if (oldPolyLine) {
[self.mapView removeOverlay:oldPolyLine];
}
}
}
#pragma mark - MKMapViewDelegate
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
MKOverlayPathView *overlayPathView;
if ([overlay isKindOfClass:[MKPolygon class]])
{
overlayPathView = [[MKPolygonView alloc] initWithPolygon:(MKPolygon*)overlay];
overlayPathView.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
overlayPathView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
overlayPathView.lineWidth = 3;
return overlayPathView;
}
else if ([overlay isKindOfClass:[MKPolyline class]])
{
overlayPathView = [[MKPolylineView alloc] initWithPolyline:(MKPolyline *)overlay];
overlayPathView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
overlayPathView.lineWidth = 3;
return overlayPathView;
}
return nil;
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString * const annotationIdentifier = #"CustomAnnotation";
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if (annotationView)
{
annotationView.annotation = annotation;
}
else
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
annotationView.image = [UIImage imageNamed:#"annotation.png"];
annotationView.alpha = 0.5;
}
annotationView.canShowCallout = NO;
return annotationView;
}
#end
or You can find here the entire project :
https://github.com/tazihosniomar/MapKitDrawing
i hope it will help you.

this is my way how I convert the touches to CLLocation on the MKMapView.
it works with the the Google Maps and the Apple Maps as well:
- (void)viewDidLoad {
// ...
// ... where the _customMapView is a MKMapView object;
// find the gesture recogniser of the map
UIGestureRecognizer *_factoryDoubleTapGesture = nil;
NSArray *_gestureRecognizersArray = [_customMapView gestureRecognizers];
for (UIGestureRecognizer *_tempRecogniser in _gestureRecognizersArray) {
if ([_tempRecogniser isKindOfClass:[UITapGestureRecognizer class]]) {
if ([(UITapGestureRecognizer *)_tempRecogniser numberOfTapsRequired] == 2) {
_factoryDoubleTapGesture = _tempRecogniser;
break;
}
}
}
// my tap gesture recogniser
UITapGestureRecognizer *_locationTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(mapLocationTouchedUpInside:)];
if (_factoryDoubleTapGesture) [_locationTapGesture requireGestureRecognizerToFail:_factoryDoubleTapGesture];
[_customMapView addGestureRecognizer:_locationTapGesture];
// ...
}
and...
- (void)mapLocationTouchedUpInside:(UITapGestureRecognizer *)sender {
CGPoint _tapPoint = [sender locationInView:_customMapView];
CLLocationCoordinate2D _coordinates = [_customMapView convertPoint:_tapPoint toCoordinateFromView:_customMapView];
// ... do whatever you'd like with the coordinates
}

Try MKOverlayPathView. The problem in denoting a region by drawing a path on an MKMapView is, unless you know the zoom scale you don't know much. So you have to track that.

Related

Nevigating One ViewController to another on Spritekit

I have a SpriteKit project with two view controllers. One is default GameViewController and another I added a TableViewController.
I want to switch between GameViewController to TableViewController.It did not switch the view controller.
In GameScene.m
GameViewController *vc =(GameViewController*)self.view.window.rootViewController;
[vc moveToFriendsViewController];
NSLog(#"vc called from gamescene");
In GameViewController.h
#protocol ViewControllerDelegate <NSObject>
-(void)moveToFriendsViewController;
#end
#interface GameViewController : UIViewController<ViewControllerDelegate>
#end
In GameViewController.m
-(void)moveToFriendsViewController{
FriendsTableViewController *vc =[[FriendsTableViewController alloc] init];
// do any setup you need for myNewVC
[self.navigationController pushViewController:vc animated:YES];
NSLog(#"vc called from viewcontroller");
}
I created a sample project for you to understand what I meant by using a scrolling node. It is very generic in nature and you can tweak, modify and add your own values, code, etc...
I store the user's y position in the touchesBegan method. I then check for any changes in y during the touchesMoved method and move the menuNode accordingly. However, there are other ways of doing this. You could for example just add a "up" and "down" button and move the menu based on which one is touched. Different approach but same result.
To see if a menu item was selected, I compare the user's y position touch from the touchesBegan method to the y position in the touchesEnded method. If there is no change, the user did not swipe up or down and I NSLog the selected node. You can add a tolerance of a couple points here in case the user moves the touch just a little bit.
Again, it's generic code and there are many ways to do what you want but this should give you a couple of ideas to work with.
#import "GameScene.h"
#implementation GameScene {
// declare ivars
SKSpriteNode *menuNode;
float yTouch;
}
-(void)didMoveToView:(SKView *)view {
// add menu background
menuNode = [SKSpriteNode spriteNodeWithColor:[SKColor darkGrayColor] size:CGSizeMake(200, 1000)];
menuNode.name = #"menuNode";
menuNode.position = CGPointMake(100, 800);
menuNode.zPosition = 10;
[self addChild:menuNode];
float yPos = -450;
for (int i = 0; i < 23; i++) {
SKLabelNode *menuItem = [SKLabelNode labelNodeWithFontNamed:#"HelveticaNeue"];
menuItem.name = [NSString stringWithFormat:#"menuItem-%i",i];
menuItem.text = [NSString stringWithFormat:#"menuItem-%i",i];
menuItem.fontSize = 20;
menuItem.fontColor = [SKColor redColor];
menuItem.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
menuItem.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
menuItem.position = CGPointMake(0, yPos);
menuItem.zPosition = 25;
[menuNode addChild:menuItem];
yPos += 40;
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
// get starting y position of touch
yTouch = touchLocation.y;
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
// check for changes in touched y position and menuNode limits
if((touchLocation.y > yTouch) && (menuNode.position.y < 800)) {
menuNode.position = CGPointMake(menuNode.position.x, menuNode.position.y+15);
}
if((touchLocation.y < yTouch) && (menuNode.position.y > 200)) {
menuNode.position = CGPointMake(menuNode.position.x, menuNode.position.y-15);
}
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self.scene];
SKNode *node = [self nodeAtPoint:touchLocation];
// if current touch position y is same as when touches began
if(touchLocation.y == yTouch) {
NSLog(#"%#",node);
}
}
-(void)update:(CFTimeInterval)currentTime {
}
#end
If you are using xibs, your method should work, but in Storyboard the below method will work fine.
-(void)moveToFriendsViewController{
FriendsTableViewController *vc =[[FriendsTableViewController alloc] init];
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
UIViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"FriendsTableViewController"];
[self.navigationController pushViewController:vc animated:YES];
NSLog(#"vc called from viewcontroller");
}

Touched Move UILabel works until the value is fix - Bug or Features?

a very strange result.
Start a Single View Application
Add a UILabel
Put my Code in
import "ViewController.h"
#interface ViewController ()
{
float oldX, oldY;
bool dragging;
__weak IBOutlet UILabel *textLable;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// self.textLable.backgroundColor = [UIColor clearColor];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// get touch event
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
if ([touch view] == textLable)
{
int intValue = (int)((touchLocation.y * 24.0)/300.0);
NSString *anyValue = [NSString stringWithFormat:#"%d",intValue];
textLable.center= touchLocation;
// RUN WITHOUT THIS PART .... tochedMoved workt great
//
// Run With this PART ..... touchedMoved is damaged!!!!!
//textLable.text = anyValue;
NSLog(#"%f %f", touchLocation.y, textLable.frame.origin.y);
}
}
#end
Connect label with
__weak IBOutlet UILabel *textLable;
let it RUN
Now you can move the label by touching moving.
Ok...and NOW!!!!
change the change
// RUN WITHOUT THIS PART .... tochedMoved workt great
//
// Run With this PART ..... touchedMoved is damaged!!!!!
//textLable.text = anyValue;
to
// RUN WITHOUT THIS PART .... tochedMoved workt great
//
// Run With this PART ..... touchedMoved is damaged!!!!!
textLable.text = anyValue; //<------------------------------
Run the app and try the move!!! you will see, label jumps between new and start position, if you start touched moving.
I tried by using a UIView as container....same thing: Once you change the value of moved object (UIButton same), moving is not working right.
Report to Appel is already send...no answere!!!
Is it a Bug or a features???
The idea is about your frame and your location touch. You are centering Label to your touch. So nothing is about setting the text.
Try this code, detecting distance between touch and centre text when first touch and adding the center label to this point :
#interface ViewController ()
{
float distanceTouchX, distanceTouchY;
bool dragging;
CGPoint firstLocation;
__weak IBOutlet UILabel *textLable;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
distanceTouchX = textLable.center.x - touchLocation.x;
distanceTouchY = textLable.center.y - touchLocation.y;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// get touch event
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
if ([touch view] == textLable)
{
int intValue = (int)((touchLocation.y * 24.0)/320.0);
NSString *anyValue = [NSString stringWithFormat:#"%d",intValue];
CGPoint newTouchLocation = touchLocation;
newTouchLocation.x = newTouchLocation.x + distanceTouchX;
newTouchLocation.y = newTouchLocation.y + distanceTouchY;
textLable.center= newTouchLocation;
textLable.text = anyValue;
NSLog(#"%f %f", touchLocation.y, textLable.frame.origin.y);
}
}
#end

How to detect a touch in a subview that was dragged there (or fastest way to get which subview is underneath a tap in its superview)

I've got a Super View, and its got a bunch of subviews. I'd like a user to be able to drag their finger inside the super view and when they drag over the subviews, that subview changes.
I need this to be very efficient, because changes can happen really fast.
So far what i've tried, is in the super view I can get the touch events from touchesBegan:withEvent: and touchesMoved:withEvent: These two combined give me all the touches I need. On each call of these methods, I have to iterate through all of the subviews and test like so
for (UIView *view in self.subviews) {
if (CGRectContainsPoint(view.frame, touchPoint) {
return view;
}
}
Then change that view however I want to. Problem is, from my benchmarking this process is just two slow.
My immediate thought is to have the subview's themselves detect the touch and then just post a notification or something like that when they are tapped. This way I don't have to iterate through them all inside the superview every freaking touch event.
The problem I am encountering with this, is that I haven't found a way to get the subviews themselves to detect that they are touched when they are just dragged over and the touch actually originated on a different UIView.
I'm open to any suggestions that either speed up the first process, or have a different way to accomplish this.
Since your subviews are on a Grid and if they are equally sized, you should be able to calculate the highlighted one directly. You just need to store their references on creation in a 2D array, for performance I suggest to use a c-array.
int x = touchPoint.x/viewWidth;
int y = touchPoint.y/viewHeight;
return views[x][y];
Depending on your Layout, some border margins or spacings between the views should be taken into account.
This way you do not need to iterate the subviews array and you do not need to perform all these CGRectContainsPoint calculations.
Should be easy:
MyViewController.h
#import <UIKit/UIKit.h>
#interface MyViewController : UIViewController
#end
MyViewController.m
#import "MyViewController.h"
#define SIDE_LENGTH 60
#define NUM_SQUARES 15
#implementation MyViewController
{
UIView *_selectedView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self populateView];
}
- (void)populateView
{
for (int i = 0; i < 15; ++i)
{
CGRect frame = CGRectMake(drand48() * self.view.frame.size.width - SIDE_LENGTH,
drand48() *self.view.frame.size.height - SIDE_LENGTH,
SIDE_LENGTH,
SIDE_LENGTH);
UIView *view = [[UIView alloc] initWithFrame:frame];
view.backgroundColor = [UIColor colorWithRed:drand48()
green:drand48()
blue:drand48()
alpha:1.0f];
view.layer.cornerRadius = SIDE_LENGTH / 2.0f; // cuz circles r pretty
[self.view addSubview:view];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
for (UIView *view in self.view.subviews)
{
if (CGRectContainsPoint(view.frame, location)) {
_selectedView = view;
break;
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
_selectedView.center = location;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
_selectedView.center = location;
}
#end
Of course, for you, just get rid of the populateView method. I'm just using it to demonstrate how this works as a drop-in class.
If you're looking for speed, then subclass UIView and do this:
MyView.h
#import <UIKit/UIKit.h>
#class MyView;
#interface MyView : UIView
#end
MyView.m
#implementation MyView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
self.center = location;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
self.center = location;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
self.center = location;
}
#end
Then MyViewController.m becomes much simpler and faster:
#import "MyViewController.h"
#import "MyView.h"
#define SIDE_LENGTH 60
#define NUM_SQUARES 15
#implementation MyViewController
{
UIView *_selectedView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self populateView];
}
- (void)populateView
{
for (int i = 0; i < 15; ++i)
{
CGRect frame = CGRectMake(drand48() * self.view.frame.size.width - SIDE_LENGTH,
drand48() *self.view.frame.size.height - SIDE_LENGTH,
SIDE_LENGTH,
SIDE_LENGTH);
MyView *view = [[MyView alloc] initWithFrame:frame];
view.backgroundColor = [UIColor colorWithRed:drand48()
green:drand48()
blue:drand48()
alpha:1.0f];
view.layer.cornerRadius = SIDE_LENGTH / 2.0f;
[self.view addSubview:view];
}
}
#end

How can I draw one circle around each touch location on a device?

New programmer here trying to take things step by step. I am trying to find a way to draw a circle around each currently touched location on a device. Two fingers on the screen, one circle under each finger.
I currently have the working code to draw a circle at one touch location, but once I lay another finger on the screen, the circle moves to that second touch location, leaving the first touch location empty. and when I add a third, it moves there etc.
Ideally I would like to be able to have up to 5 active circle on the screen, one for each finger.
Here is my current code.
#interface TapView ()
#property (nonatomic) BOOL touched;
#property (nonatomic) CGPoint firstTouch;
#property (nonatomic) CGPoint secondTouch;
#property (nonatomic) int tapCount;
#end
#implementation TapView
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
NSArray *twoTouch = [touches allObjects];
if(touches.count == 1)
{
self.tapCount = 1;
UITouch *tOne = [twoTouch objectAtIndex:0];
self.firstTouch = [tOne locationInView:[tOne view]];
self.touched = YES;
[self setNeedsDisplay];
}
if(touches.count > 1 && touches.count < 3)
{
self.tapCount = 2;
UITouch *tTwo = [twoTouch objectAtIndex:1];
self.secondTouch = [tTwo locationInView:[tTwo view]];
[self setNeedsDisplay];
}
}
-(void)drawRect:(CGRect)rect
{
if(self.touched && self.tapCount == 1)
{
[self drawTouchCircle:self.firstTouch :self.secondTouch];
}
}
-(void)drawTouchCircle:(CGPoint)firstTouch :(CGPoint)secondTouch
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(ctx,0.1,0.1,0.1,1.0);
CGContextSetLineWidth(ctx,10);
CGContextAddArc(ctx,self.firstTouch.x,self.firstTouch.y,30,0.0,M_PI*2,YES);
CGContextStrokePath(ctx);
}
I do have setMultipleTouchEnabled:YES declared in my didFinishLaunchingWithOptions method in the appDelegate.m.
I have attempted to use an if statement in the drawTouchCircle method that changes the self.firstTouch.x to self.secondTouch.x based on a self.tapCount but that seems to break the whole thing, leaving me with no circles at any touch locations.
I'm having an immensely hard time trying to find my issue, and I am aware that it might be something quite simple.
I just wrote some code that seems to work. I've added an NSMutableArray property called circles to the view, which contains a UIBezierPath for each circle.
In -awakeFromNib I setup the array and set self.multipleTouchEnabled = YES - (I think you did this using a reference to the view in your appDelegate.m).
In the view I call this method in the -touchesBegan and -touchesMoved methods.
-(void)setCircles:(NSSet*)touches
{
[_circles removeAllObjects]; //clear circles from previous touch
for(UITouch *t in touches)
{
CGPoint pt= [t locationInView:self];
CGFloat circSize = 200; //or whatever you need
pt = CGPointMake(pt.x - circSize/2.0, pt.y - circSize/2.0);
CGRect circOutline = CGRectMake(pt.x, pt.y, circSize, circSize);
UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:circOutline];
[_circles addObject:circle];
}
[self setNeedsDisplay];
}
Touches ended is:
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
[_circles removeAllObjects];
[self setNeedsDisplay];
}
Then I loop over circles in -drawRect and call [circle stroke] on each one

Cocos2d Drawing App Making Lines

I am making a very simple drawing application. I got the lines to draw using the ccTouchMoved event. I am putting all the touch moved moved points into an array and then using a for loop to draw a line between all the points. Now, I do not want to join the points when I have lifted my finger and started new line drawing. I got that part working too but now whenever I begin a new drawing the whole screen flicker.
//
// HelloWorldLayer.mm
// DrawPuppets
//
// Created by Mohammad Azam on 12/11/12.
// Copyright __MyCompanyName__ 2012. All rights reserved.
//
// Import the interfaces
#import "DrawPuppetLayer.h"
#import "AppDelegate.h"
#import "PhysicsSprite.h"
enum {
kTagParentNode = 1,
};
#pragma mark - HelloWorldLayer
#interface DrawPuppetLayer()
-(void) initPhysics;
-(void) addNewSpriteAtPosition:(CGPoint)p;
-(void) createMenu;
#end
#implementation DrawPuppetLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
DrawPuppetLayer *layer = [DrawPuppetLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(id) init
{
if( (self=[super init])) {
// enable events
self.isTouchEnabled = YES;
self.isAccelerometerEnabled = YES;
index = -1;
canvas = [[NSMutableArray alloc] init];
// init physics
[self initPhysics];
[self scheduleUpdate];
}
return self;
}
-(void) draw
{
if([lineDrawing.points count] > 1)
{
for(int i = 0; i<([canvas count]) ;i++)
{
LineDrawing *drawing = (LineDrawing *) [canvas objectAtIndex:i];
for(int j=0;j<[drawing.points count] - 1;j++)
{
LinePoint *firstPoint = (LinePoint *) drawing.points[j];
LinePoint *secondPoint = (LinePoint *) drawing.points[j + 1];
CGPoint point1 = [[CCDirector sharedDirector] convertToGL:CGPointMake(firstPoint.x, firstPoint.y)];
CGPoint point2 = [[CCDirector sharedDirector] convertToGL:CGPointMake(secondPoint.x, secondPoint.y)];
ccDrawLine(point1, point2);
}
}
}
//
// IMPORTANT:
// This is only for debug purposes
// It is recommend to disable it
//
[super draw];
ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position );
kmGLPushMatrix();
world->DrawDebugData();
kmGLPopMatrix();
}
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
lineDrawing = [[LineDrawing alloc] init];
lineDrawing.points = [[NSMutableArray alloc] init];
[canvas addObject:lineDrawing];
}
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView: [touch view]];
LinePoint *linePoint = [[LinePoint alloc] init];
linePoint.x = point.x;
linePoint.y = point.y;
[lineDrawing.points addObject:linePoint];
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//Add a new body/atlas sprite at the touched location
for( UITouch *touch in touches ) {
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: location];
}
}
#end
Can anyone spot my mistake?
Try visiting it all down to a texture, there's going to be a time when your points array gets too large to be drawn nicely
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// get the node space location of our touch
CGPoint location = [self getNodeSpaceTouchLocationFromUIEvent:event];
// draw with our current location and a random colour
[_canvas begin]; // our rendertexture instance
// do your drawing here
[_pen drawPenWithPosition:location andColour:_colour];
// end capturing the current pen state
[_canvas end];
}
Here's a simple example project written for iOSDevUK 2012 it uses GL_POINTS in Cocos2d v1 and is based on the approach we took when developing SketchShare

Resources