Objective C: Drawing with Fingers on UIScrollView - objective-c

I am trying to make a sketch pad app.
I used UIScrollView for paging and UIImageView for the drawing.
I put the UIImageView on top of the scrollView but it's not added to UIScrollView so it will not scroll.
The issue now...
It's not writing when...
[scrollView setScrollEnable:YES];
[scrollView setUserInteractionEnabled:YES];
i need to set it to NO with the use of a button for it to write,
is there a way that i can scroll and write at the same time without using any button??

This is definitely not the correct way of building such an application.
A UIScrollView is meant for scrolling content not for drawing. And you don't need a UIImageView to draw content either, a simple UIView would be enough.
Here you're best bet would be to create one UIScrollView and disable it's scrolling because you'll be handling it with two fingers, while the drawing will be handled pan another gesture recognizer.
UIPanGestureRecognizer *twoFingerScrolling = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(onTwoFingerScroll:)] autorelease];
[twoFingerScrolling setMinimumNumberOfTouches:2];
[twoFingerScrolling setMaximumNumberOfTouches:2];
UIPanGestureRecognizer *oneFingerDraw = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(onOneFingerDraw:)] autorelease];
[oneFingerDraw setMinimumNumberOfTouches:1];
[oneFingerDraw setMaximumNumberOfTouches:1];
[yourScollView addGestureRecognizer:twoFingerScrolling];
[yourScollView addGestureRecognizer:oneFingerDraw];
And later on in your code you can easily process both events, the scrolling:
- (void)onTwoFingerScroll:(UIPanGestureRecognizer*)sender
{
// Calculate the content offset from the shifting that occured
//[yourScrollView setContentOffset:theContentOffset]
}
And the drawing (which can be done by the Quartz Tookit)
- (void)onOneFingerDraw:(UIPanGestureRecognizer*)sender
{
// Processing the drawing by using comparing:
if (sender.state == UIGestureRecognizerStateBegan)
{ /* drawing began */ }
else if (iRecognizer.state == UIGestureRecognizerStateChanged)
{ /* drawing occured */ }
else if (iRecognizer.state == UIGestureRecognizerStateEnded)
{ /* drawing ended /* }
}
Hope this helps.

Related

When showing the real time resizing of a pane, it struggles while dragging and top of the pane is hidden after released

I went through several posts on dragging but couldn't find an answer to my problem.
I can use the mouseDown and mouseUp events to track the current positions and redraw the resized pane. What I want is to show the real time movement of the pane. Everytime mouseDragged event is fired, y coordinate of the new location is taken and setFrame is called to redraw. The window seems to flicker and gets stuck finally (title bar goes out of bounds and hidden) as it seems to miss the final events in the run loop.
Is there a way to solve this problem?
The view has been implemented in the following way
NSSplitView (divided into sections left dock, right dock, etc.)
NSView is used to implement a sub view inside the dock
NSTableView is used inside the NSView to hold multiple "panels"
There can be several panels inside this table view (one below another)
I need to resize these panels by dragging the border line. For this I'm using an NSButton in the bottom.(I want to show a thicker separation line there)
Here is the code for mouseDown, mouseUp, mouseDragged callbacks, used to resize the panel
-(void)mouseDown:(NSEvent *)theEvent {
draggingInProgress = YES;
firstDraggingPointFound = NO;
}
-(void)mouseUp:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
}
draggingInProgress = NO;
[_delegate heightChangingEnded]; //draggingInProgress is set to NO
}
-(void)mouseDragged:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
} else {
firstDraggingPointFound = YES;
}
previousDragPosition = point.y;
}
//Delegate method
-(void)heightChanged:(NSNumber *)change {
NSRect f = [[self view] frame];
f.size.height += [change floatValue];
if (f.size.height < 100) {
f.size.height = 100;
}
[[self view] setFrame:f];
[self.panelViewModel setPanelHeight:f.size.height];
if (_heightChangeDelegate) {
[_heightChangeDelegate heightChangedForPanel:self];
}
[[self view] setFrame:f];
}
What would be the problem here?
Is there a better way to do this?
First off, I wouldn’t use the word “Panel” to describe what you want, since an “NSPanel” is a kind of “NSWindow”. Let’s call them “panes.”
Second, I wouldn’t use an NSTableView to contain your panes. NSTableView really isn’t designed for that, it’s for tabular data. You can use an NSStackView, which IS designed for it, or just use raw constraints and autolayout, manually setting the top of each pane to equal the bottom of the previous one.

UIPanGestureRecognizer beyondBounds

I have this code that retrieves the coordinates of an object when it is panned:
UITapGestureRecognizer *moveBuildingTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(moveobject:)];
Method moveobject contents:
CGPoint tapPoint=[recognizer locationOfTouch:0 inView:self.view];
I use this to change the frame of - move it -an imageview using these coordinates.
However, upon dragging the image around - triggering the uipangesturerecognizer action, I found that when I drag it to the absolute bottom, I get an error that -[UIPanGestureRecognizer locationOfTouch:inView:]: index (0) beyond bounds (0).
How can I solve this exception and prevent the user from dragging past this point?
Thanks
It's weird that your moveobject: method gets called even though the private touches array of the gesture recognizer seems to be empty.
Anyway, in general, if you don't handle multitouch gestures within gesture recognizer, I would suggest to use [recognizer locationInView:] rather then locationOfTouch:inView:.
Btw:
Your talking about a UIPanGestureRecognizer while in the code you're using a UITapGestureRecognizer.
The code I would recommend to handle dragging of a particular view looks like this:
//...
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[someView addGestureRecognizer:panGR];
//...
- (void)handlePan:(UIPanGestureRecognizer *)gr
{
CGPoint translation = [gr translationInView:gr.view];
gr.view.frame = CGRectOffset(gr.view.frame, translation.x, translation.y);
[gr setTranslation:CGPointZero inView:gr.view];
}
You should check the numberOfTouches in your UIGestureRecognizer like:
if (recognizer.numberOfTouches) {
CGPoint tapPoint = [recognizer locationOfTouch:0 inView:self.view];
}

MKMapView and the responder chain

I have an MKMapView with a single subview:
MKMapView *mapView = [[MKMapView alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(0, 200, 200, 200)];
subView.backgroundColor = [UIColor grayColor];
[mapView addSubview:subView];
I would expect that because the subview does not handle any touch events, all touch events would be passed along to the parent map view (via the responder chain). I would then expect that panning and pinching in the subview would pan and pinch the map.
This, unfortunately, does not appear to be the case. Does anyone know of a way to get the map view in to the responder chain?
I realize overriding hitTest in my subview can achieve what I'm expecting here, but I can't use that approach because I have other gestures I need to respond to in the subview.
How about handling all gestures with UIGestureRecognizers (properly setup to ignore other gesture recognizer or to fire simultanously with them) added to your mapView and disabling userInteractionEnabled of your subview?
I use the following code to listen to Taps on the mapView without interfering with the standard gestures:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(mtd_handleMapTap:)];
// we require all gesture recognizer except other single-tap gesture recognizers to fail
for (UIGestureRecognizer *gesture in self.gestureRecognizers) {
if ([gesture isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *systemTap = (UITapGestureRecognizer *)gesture;
if (systemTap.numberOfTapsRequired > 1) {
[tap requireGestureRecognizerToFail:systemTap];
}
} else {
[tap requireGestureRecognizerToFail:gesture];
}
}
- (void)mtd_handleMapTap:(UITapGestureRecognizer *)tap {
if ((tap.state & UIGestureRecognizerStateRecognized) == UIGestureRecognizerStateRecognized) {
// Get view frame rect in the mapView's coordinate system
CGRect viewFrameInMapView = [self.mySubview.superview convertRect:self.mySubview.frame toView:self.mapView];
// Get touch point in the mapView's coordinate system
CGPoint point = [tap locationInView:self.mapView];
// Check if the touch is within the view bounds
if (CGRectContainsPoint(viewFrameInMapView, point)) {
// tap was on mySubview
}
}
}

How do I pass the user touch from one object to another object?

I am developing an application that allows the user at a certain point to drag and drop 10 images around. So there are 10 images, and if he/she drags one image onto another, these two are swapped.
A screenshot of how this looks like:
So when the user drags one photo I want it to reduce its opacity and give the user a draggable image on his finger which disappears again if he drops it outside of any image.
The way I have developed this is the following. I have set a UIPanGesture for these UIImageViews as:
for (UIImageView *imgView in editPhotosView.subviews) {
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(photoDragged:)];
[imgView addGestureRecognizer:panGesture];
imgView.userInteractionEnabled = YES;
[panGesture release];
}
Then my photoDragged: method:
- (void)photoDragged:(UIPanGestureRecognizer *)gesture {
UIView *view = gesture.view;
if ([view isKindOfClass:[UIImageView class]]) {
UIImageView *imgView = (UIImageView *)view;
if (gesture.state == UIGestureRecognizerStateBegan) {
imgView.alpha = 0.5;
UIImageView *newView = [[UIImageView alloc] initWithFrame:imgView.frame];
newView.image = imgView.image;
newView.tag = imgView.tag;
newView.backgroundColor = imgView.backgroundColor;
newView.gestureRecognizers = imgView.gestureRecognizers;
[editPhotosView addSubview:newView];
[newView release];
}
else if (gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:view.superview];
[view setCenter:CGPointMake(view.center.x + translation.x, view.center.y + translation.y)];
[gesture setTranslation:CGPointZero inView:view.superview];
}
else { ... }
}
}
Thus as you see I add a new UIImageView with 0.5 opacity on the same spot as the original image when the user starts dragging it. So the user is dragging the original image around. But what I want to do is to copy the original image when the user drags it and create a "draggable" image and pass that to the user to drag around.
But to do that I have to pass the user touch on to the newly created "draggable" UIImageView. While it's actually set to the original image (the one the user touches when he starts dragging).
So my question is: How do I pass the user's touch to another element?.
I hope that makes sense. Thanks for your help!
Well, you can pass the UIPanGestureRecognizer object to another object by creating a method in your other object which takes the gesture recognizer as a parameter.
- (void)myMethod:(UIPanGestureRecognizer *)gesture
{
// Do stuff
}
And call from your current gesture recognizer using....
[myOtherObject myMethod:gesture];
Not entirely sure I'm understanding your question here fully. :-/
Maybe:
[otherObject sendActionsForControlEvents:UIControlEventTouchUpInside];
Or any other UIControlEvent
In the end I decided to indeed drag the original image and leave a copy at the original place.
I solved the issue with the gesture recognizers I was having by re-creating them and assigning them to the "copy", just like PragmaOnce suggested.

Why does UIScrollView scroll violently when I quickly swipe twice in the same direction?

when scrolling horizontally in a UIScrollview, if I quickly swipe twice in the same direction the scroll view jumps violently. Is there anyway to prevent this from happening? To explain in detail, here's an event log from the scrollview where in most delegate methods I just print the x coordinate.
scrollViewWillBeginDragging:
14:55:12.034 Will begin dragging!
14:55:12.037 - Position -0.000000
scrollViewWillBeginDeceleration:
14:55:12.129 Deceleration rate 0.998000
14:55:12.152 + Position 314.000000
scrollViewWillBeginDragging:
14:55:12.500 Will begin dragging!
14:55:12.522 - Position 1211.000000
scrollViewWillBeginDeceleration:
14:55:12.530 Deceleration rate 0.998000
14:55:12.533 + Position 1389.000000
scrollViewDidScroll: (printing values < 0 && > 6000 (bounds.size.width)
14:55:12.595 !!! Position 7819.000000
14:55:12.628 !!! Position 9643.000000
14:55:12.658 !!! Position 10213.000000
14:55:12.688 !!! Position 10121.000000
14:55:12.716 !!! Position 9930.000000
... contentoffset.x drops with around 400 each scrollviewdidscroll call ...
14:55:13.049 !!! Position 6508.000000
scrollViewDidEndDecelerating:
14:55:13.753 Will end deceleration
14:55:13.761 * Position 6144.000000
The most notable thing in the log is right after scrollViewWillBeginDeceleration when the contentoffset.x jumps with ~6000 points in a matter of milliseconds.
Implementation
The uiscrollview and uiscrollviewdelegate are in the same class, a subclass of uiscrollview which also implements the uiscrollviewdelegate protocol, nothing special is done to contentoffset, and the only properties set on the scrollview are:
self.showsHorizontalScrollIndicator = YES;
self.scrollsToTop = NO;
self.delegate = self;
The scrollview subviews are added once from a viewwillappear call in a uiviewcontroller which hosts the uiscrollview (and the contentSize is set appropriately). Scrolling, waiting a little while, and scrolling again works perfectly.
Please set the scroll view in your required direction and as well as co-ordinate for which you want the scroll and use the proper connection to XIB then it will work fine.
you can try it like this
- (void)enableScrollView
{
scrollView.userInteractionEnabled = YES;
}
- (void)delayScroll:(id)sender
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//delay userInteraction 0.1 second, prevent second swipe
[NSThread sleepForTimeInterval:0.1];
[self performSelectorOnMainThread:#selector(enableScrollView) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
scrollView.userInteractionEnabled = NO;
[NSThread detachNewThreadSelector:#selector(delayScroll:) toTarget:self withObject:nil];
}
May be you need to setup the bouncesZoom property?
scrollviewobject.bouncesZoom = NO;
If you want the UIScrollView to scroll a bit slower you could for example add a NSTimer when user scroll's and set the scrollers contentOffset a bit back with an animation like so:
Declare in .h File:
UIScrollView *myScrollView;
NSTimer *scrollCheckTimer;
BOOL delayScroll;
.m File:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (delayScroll == YES) {
[scrollCheckTimer invalidate];
delayScroll = NO;
[myScrollView setContentOffset:CGPointMake(myScrollView.contentOffset.x - 40, contentOffset.y) animated:YES];
}
delayScroll = YES;
scrollCheckTimer = [NSTimer scheduledTimerWithTimeInterval:0.9 target:self selector:#selector(removeDelayBecouseOfTime) userInfo:nil repeats:NO];
}
- (void)removeDelayBecouseOfTime { delayScroll = NO; }