I have a drawing view on a UIScrollView.
What I want to do is draw lines with one finger, and scrolling with two fingers.
The drawing view is to draw lines through touchesMoved as below.
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch;
CGPoint lastTouch, currentTouch;
for (touch in touches)
{
lastTouch = [touch previousLocationInView:self];
currentTouch = [touch locationInView:self];
CGContextRef ctx = CGLayerGetContext(drawLayer);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, lastTouch.x, lastTouch.y);
CGContextAddLineToPoint(ctx, currentTouch.x, currentTouch.y);
CGContextStrokePath(ctx);
}
[self setNeedsDisplay];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGContextDrawLayerInRect(drawContext, self.bounds, drawLayer);
CGContextClearRect(CGLayerGetContext(drawLayer), self.bounds);
[self setNeedsDisplay];
}
and on a viewController,
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
[_scrollView setContentSize:CGSizeMake(320, 800)];
[self.view addSubview:_scrollView];
_drawingView = [[DrawingView alloc] initWithFrame:CGRectMake(0, 0, 320, 800)];
[_scrollView addSubview:_drawingView];
for (UIGestureRecognizer *gestureRecognizer in _scrollView.gestureRecognizers)
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
}
}
It works ok on simulator however the drawing is too slow on a real device. What is wrong and any suggestion?
Ty!
I solved.
Shouldn't draw whole screen with [self setNeedsDisplay]. Should draw a area where need to redraw with [self setNeedsDisplay withRect:]
Better use panGesture recogniger than touchesBegin~End. There's delay between touchesBegin and touchesEnd.
Related
I am wondering how I can use a touch method for a UIImageView inside a UIScrollView in xCode.
When I add the UIImageView subview to the self.view, I can use the touch method. But when I add the UIImageView subview to the UIScrollView, I can't. How can I solve this?
This is my code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
touches = [event allTouches];
for (UITouch *touch in touches) {
NSLog(#"Image Touched");
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 44, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height * 0.9)];
scrollView.scrollEnabled = TRUE;
scrollView.bounces = TRUE;
scrollView.contentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
scrollView.userInteractionEnabled = YES;
[self.view addSubview:scrollView];
UIImageView *ImageView = [UIImageView alloc] initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width * 0.04, 10, [UIScreen mainScreen].bounds.size.width * 0.28, [UIScreen mainScreen].bounds.size.height * 0.22)];
ImageView.image = [UIImage imageNamed(#"image.png")];
ImageView.layer.borderColor = [UIColor whiteColor].CGColor;
ImageView.layer.borderWidth = 1;
ImageView.userInteractionEnabled = YES;
[scrollView addSubview:ImageView];
}
Give UIGestureRecognizers a try. They are far easier to manage with multiple layers of touch management.
- (void)touchedImage:(UITapGestureRecognizer *)gesture {
// When the gesture has ended, perform your action.
if (gesture.state == UIGestureRecognizerStateEnded) {
NSLog(#"Touched Image");
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 44, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height * 0.9)];
scrollView.scrollEnabled = TRUE;
scrollView.bounces = TRUE;
scrollView.contentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
scrollView.userInteractionEnabled = YES;
[self.view addSubview:scrollView];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width * 0.04, 10, [UIScreen mainScreen].bounds.size.width * 0.28, [UIScreen mainScreen].bounds.size.height * 0.22)];
imageView.image = [UIImage imageNamed:#"image.png"];
imageView.layer.borderColor = [UIColor whiteColor].CGColor;
imageView.layer.borderWidth = 1;
imageView.userInteractionEnabled = YES;
[scrollView addSubview:imageView];
// Create a tap gesture
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(touchedImage:)];
[imageView addGestureRecognizer:tap];
}
if you're dealing with multiple imageviews using the same gesture recognizer, it won't work. Try using a new gesture recognizer per image view.
User interaction of UIImageView is disabled by default you can enable it by setting imageView.userInteractionEnabled = YES;
You'll have to derive a CustomScrollView from UIScrollView and override all touches method like touchesBegan,Moved,Ended and Cancelled like below:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UIView *view in self.subviews)
{
UITouch *touch = [touches anyObject];
CGPoint pt = [touch locationInView:view];
if (CGRectContainsPoint(view.frame, pt))
{
[view touchesMoved:touches withEvent:event];
}
}
}
After you do this. When you add any view in the instance of customScrollView, you'll get the touches in the added view properly.
I had the same problem with an image view as a subview in a scroll view when applying a transformation.
Upon scroll my gesture recognizer no longer worked so I checked the offset of the scroll view and reset my image so the gesture was recognized.
And of course we need to set userInterActionEnabled to true and add the gesture in viewDidLoad()
override func viewDidLoad() {
imageView.userInteractionEnabled = true
addGesture()
}
func addGesture(){
let singleTap = UITapGestureRecognizer(target: self, action: #selector(MyViewController.enableGesture(_:)));
singleTap.numberOfTapsRequired = 1
self.imageView.addGestureRecognizer(singleTap)
self.imageView.tag = 1
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
if offset > 0 {
imageView.layer.transform = avatarTransform
}
else {
imageView.transform = CGAffineTransformIdentity
}
}
Here is the relevant .m that I am currently using.
- (void)drawRect:(CGRect)rect
{
[[UIColor redColor] setStroke];
for (UIBezierPath *_path in pathArray)
[_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
#pragma mark -
#pragma mark - Touch Methods
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
myPath=[[UIBezierPath alloc]init];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
if([ud objectForKey:#"lineThickness"] == nil) {
myPath.lineWidth=5;
}
else {
float thicknessFloat = [ud floatForKey:#"lineThickness"];
myPath.lineWidth= 10. * thicknessFloat;
}
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath moveToPoint:[mytouch locationInView:self]];
[pathArray addObject:myPath];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
if([ud objectForKey:#"lineThickness"] == nil) {
myPath.lineWidth=5;
}
else {
float thicknessFloat = [ud floatForKey:#"lineThickness"];
myPath.lineWidth= 10. * thicknessFloat;
}
[myPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}
It works great, but since this is tutorial code that is slightly modified by me, I do not know how to approach the problem of wanting to draw lines between two points, and have the framework connect the the points each time a point is added.
Can anyone please point me in a good direction on how to accomplish this please?
The particulars of how to implement this depend upon the effect that you're looking for. If you're just tapping on a bunch of points and want to add them to a UIBezierPath you can do something like the following in your view controller:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
CGPoint location = [mytouch locationInView:self.view];
// I'm assuming you have a myPath UIBezierPath which is an ivar which is
// initially nil. In that case, we'll check if it's nil and if so, initialize
// it, otherwise, it's already been initialized, then we know we're just
// adding a line segment.
if (!myPath)
{
myPath = [UIBezierPath bezierPath];
[myPath moveToPoint:location];
shapeLayer = [[CAShapeLayer alloc] initWithLayer:self.view.layer];
shapeLayer.lineWidth = 1.0;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
[self.view.layer addSublayer:shapeLayer];
}
else
{
[myPath addLineToPoint:location];
shapeLayer.path = myPath.CGPath;
}
}
If you wanted something where you can draw with your finger (e.g. dragging your finger draws), then it might look something like:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
CGPoint location = [mytouch locationInView:self.view];
myPath = [UIBezierPath bezierPath];
[myPath moveToPoint:location];
shapeLayer = [[CAShapeLayer alloc] initWithLayer:self.view.layer];
shapeLayer.lineWidth = 1.0;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
[self.view.layer addSublayer:shapeLayer];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
CGPoint location = [mytouch locationInView:self.view];
[myPath addLineToPoint:location];
shapeLayer.path = myPath.CGPath;
}
I would not use UIBezierPath since it is intended more for drawing curved paths.
The most efficient way to accomplish this would be to use core graphics draw commands within drawRect while using an array to store the points you want to draw; this array is appended to in your touch methods.
- (void)drawRect:(CGRect)rect {
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat black[4] = {0, 0,
0, 1};
CGContextSetStrokeColor(c, black);
CGContextBeginPath(c);
CGContextMoveToPoint(c, 100, 100);
CGContextAddLineToPoint(c, 100, 200); //call this in a loop that goes through the point array
CGContextStrokePath(c);
}
There is much more information here: Quartz 2D Programming Guide
Hope this helps!
I am creating a zoomable and pan-able map for my game (using CCPanZoomController) . Within this map I would like to have a tappable sprite, and when it is tapped, "do something"…
I can get the two things to work perfectly in separate projects, however when I try to combine them, nothing happens when I tap my sprite.
I have included an image to demonstrate further:
//in my init section
self.isTouchEnabled = YES;
mapBase = [CCSprite spriteWithFile:#"MapBase.png"];
mapBase.anchorPoint = ccp(0, 0);
[self addChild:mapBase z:-10];
gym = [CCSprite spriteWithFile:#"Gym.png"];
gym.scale = 0.3;
gym.position = ccp(1620, 250);
[self addChild:gym z:1];
CGRect boundingRect = CGRectMake(0, 0, 2499, 1753);
_controller = [[CCPanZoomController controllerWithNode:self] retain];
_controller.boundingRect = boundingRect;
_controller.zoomOutLimit = _controller.optimalZoomOutLimit;
_controller.zoomInLimit = 2.0f;
[_controller enableWithTouchPriority:1 swallowsTouches:YES];
//end of init
-(void) registerWithTouchDispatcher
{
[[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self
priority:0 swallowsTouches:NO];
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
CGPoint touchPoint1 = [touch locationInView:[touch view]];
if (CGRectContainsPoint(gym.boundingBox, touchPoint1)) return YES;
return NO;
}
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{
CGPoint touchPoint2 = [touch locationInView:[touch view]];
if (CGRectContainsPoint(gym.boundingBox, touchPoint2)){
CCLOG(#"SPRITE HAS BEEN TAPPED");
}
}
I want to beable to zoom in/out and pan the wholemap (including the ‘Gym’ sprite).
And if the sprite 'Gym' is tapped by the user, then i would like to “do something”.
If anyone can figure this out, I would be extremely grateful!
Thanks.
I'm trying to do a program that everytime my touches end, another UIView will appear using a loop for as many as UIView I want. How can I set a loop of UIView in my touches end? or should I create it in viewDidLoad and call it in touches end?
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event//upon leaving
{
UITouch *touch = [touches anyObject];
for (int i=0; i < 100; i++) {
UIImageView *layerView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
[layerView setAlpha:.05];
[self.view addSubview:layerView];
}
}
Hope you can help me guys..
If you want to add a single view after each tap, you don't need a for loop. Just do
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event//upon leaving
{
UITouch *touch = [touches anyObject];
UIImageView *layerView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
[layerView setAlpha:.05];
// If you just want to add a line, add another uiview would the easiest way. For example:
UIView *line = [UIView alloc] initWithFrame:CGRectMake(0, layerView.center.y, layerView.frame.size.width, 1)];
line.backgroundColor = [UIColor blackColor];
[layerView addSubview:line];
[self.view addSubview:layerView];
}
Of course, you can also subclass a UIView and draw whatever you want in
- (void)drawRect:(CGRect)rect
I have a UIScrollView with UIView added as subview. Now how do I open a new view when i touch on the view ?
I want new view to appear .before this i have buttons on my view...so button have the method "addtarget perform selector"...from that i am loading a new view
here is an image of my view
For example try following code (add it to your view):
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
NSUInteger tapCount = [touch tapCount];
CGPoint location = [touch locationInView:self.view];
switch (tapCount)
{
case 1:
{
UIView *view = [[UIView alloc] initWithFrame: CGRectMake(0.0f, 0.0f, 320.0f, 480.0f)];
view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:view];
[view release];
}
break;
}
}