Move an UIImageView around in a UIScrollView - iphone

I'm creating an application where I want to let the user move (not pan) an UIImageView around by dragging it on the screen. Additionally, I want the user to be able to zoom the UIImageView in and out.
As such I've been using a custom UIScrollView that forwards single touches to the 'contentView':
#implementation JM_UIScrollView
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
{
NSSet *allTouches = [event allTouches];
NSLog(#"Checking for touches: %d", [allTouches count]);
if ([allTouches count] == 1) {
return YES;
}
return NO;
}
#end
Along with a custom UIImageView that implements touchesBegan and touchesMoved to determine where to move the UIImageView:
#implementation JM_UIImageView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
if ([allTouches count] == 0)
return;
UITouch *firstTouch = [[allTouches allObjects] objectAtIndex: 0];
CGPoint touchLoc = [firstTouch locationInView: [self superview]];
touchOffset= CGPointMake(touchLoc.x-self.center.x,touchLoc.y-self.center.y);
NSLog(#"Currently at: %3.3f x %3.3f", touchLoc.x, touchLoc.y);
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
if ([allTouches count] == 0)
return;
UITouch *firstTouch = [[allTouches allObjects] objectAtIndex: 0];
CGPoint touchLoc = [firstTouch locationInView: [self superview]];
if ([allTouches count] == 1)
{
if ([firstTouch view] == self)
{
touchLoc.x -= touchOffset.x;
touchLoc.y -= touchOffset.y;
NSLog(#"Moved to: %3.3f x %3.3f", touchLoc.x, touchLoc.y);
self.center = touchLoc;
}
}
}
#end
This is then all glued together:
scrollView = [[JM_UIScrollView alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
scrollView.delegate = self;
scrollView.bouncesZoom = YES;
scrollView.scrollEnabled = NO;
scrollView.backgroundColor = [UIColor redColor];
scrollView.clipsToBounds = YES;
UIImage *image = [UIImage imageNamed:#"berg.jpg"];
imageView = [[JM_UIImageView alloc] initWithFrame: CGRectMake(0, 0, 140, 230)];
imageView.image = image;
imageView.center = CGPointMake(200,300);
[imageView setUserInteractionEnabled: YES];
[scrollView addSubview: imageView];
scrollView.contentSize = CGSizeMake(140,230);
scrollView.minimumZoomScale = 0.2;
scrollView.maximumZoomScale = 1.1;
[window addSubview: scrollView];
// Override point for customization after application launch
[window makeKeyAndVisible];
The problem:
When I start the application, I can move the UIImageView just fine and fluently. I can zoom-in and still be able to move it around.
However, it seems that whenever I zoom back out to the maximum level I seem then unable to move the UIImageView around anymore. It will jump at times, but by a maximum of 10 pixels. Use of NSLog() shows the touchesBegan/touchesMoved methods on the JM_UIImageView are no longer called.
Does anyone have any idea on what I might be missing here?
EDIT:
Would also accept an answer for wether or not this is the only way of implementing pinch-zooming with the zoombounce animation.

Related

touch locations, frames. CGRectContainsPoint not returning true

I have multiple UIImageView subclasses:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint _location = [touch locationInView:touch.view];
[self processTouch:_location];
}
}
-(void)processTouch:(CGPoint)location {
DLog(#"location: %f .. bounds: %f : %f" ,location.x, [twoThirdOfTheWayCoordinateX floatValue], [oneThirdOfTheWayCoordinateX floatValue]);
if(CGRectContainsPoint(self.frame, location)) {
DLog(#"1");
if(location.x > [twoThirdOfTheWayCoordinateX floatValue]){
[self oneStar];
} else if (location.x < [oneThirdOfTheWayCoordinateX floatValue]) {
[self zeroStars];
} else if( [twoThirdOfTheWayCoordinateX floatValue] >= location.x && location.x >= [oneThirdOfTheWayCoordinateX floatValue]){
[self halfStars];
}
}
}
The DLog(#"1"); never gets called. The UIImageView Subclass is in the middle of the iphone screen. when it is in the top left corner, this code works.
NSLog:
-[Star processTouch:] location: 44.000000 .. bounds: 33.333332 : 16.666666
I assume the problem is because:
if(CGRectContainsPoint(self.frame, location)) {
never gets called.
Why is that so? the first DLog gets called.
Code i use to initialize the instance:
self.instance = [[Class alloc] initWithFrame:CGRectMake(10, 10, 50 , 50)];
self.instance.userInteractionEnabled = YES;
self.instance.image = [UIImage imageNamed:#"0-0.png"];
[self.view addSubview: self.instance];
[self.instance setNeedsDisplay];
replacing self.frame with self.bounds fixed the issue.
kudos to august on #iphonedev on freenode.

Limitting UIImageView size while using CGAffineTransform

I want to limit my uiimageview size while using CGAffineTransform. I am limitting scale but I can't limit the size of the my uiimageview. When I run the app I see it is limitted but in the background the size of my uiimageview keeps increasing. How can I make this?
Here is my code:
- (id)initWithFrame:(CGRect)frame
{
if ([super initWithFrame:frame] == nil) {
return nil;
}
originalTransform = CGAffineTransformIdentity;
touchBeginPoints = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
self.userInteractionEnabled = YES;
self.multipleTouchEnabled = YES;
self.exclusiveTouch = YES;
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSMutableSet *currentTouches = [[[event touchesForView:self] mutableCopy] autorelease];
[currentTouches minusSet:touches];
if ([currentTouches count] > 0) {
[self updateOriginalTransformForTouches:currentTouches];
[self cacheBeginPointForTouches:currentTouches];
}
[super touchesBegan:touches withEvent:event];
[self cacheBeginPointForTouches:touches];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGAffineTransform incrementalTransform = [self incrementalTransformWithTouches:[event touchesForView:self]];
self.transform = CGAffineTransformConcat(originalTransform, incrementalTransform);
CGAffineTransform transform = self.transform;
float scale = sqrt(transform.a*transform.a + transform.c*transform.c);
NSLog(#"%f",self.frame.size.height);
if (scale > SCALE_MAX){
self.transform = CGAffineTransformScale(transform, SCALE_MAX/scale, SCALE_MAX/scale);
}
else if (scale < SCALE_MIN){
self.transform = CGAffineTransformScale(transform, SCALE_MIN/scale, SCALE_MIN/scale);
}
[super touchesMoved:touches withEvent:event];
}
-(void)updateOriginalTransformForTouches:(NSSet *)touches{
CGAffineTransform transform = self.transform;
float scale = sqrt(transform.a*transform.a + transform.c*transform.c);
if (scale > SCALE_MAX){
self.transform = CGAffineTransformScale(transform, SCALE_MAX/scale, SCALE_MAX/scale);
}
else if (scale < SCALE_MIN){
self.transform = CGAffineTransformScale(transform, SCALE_MIN/scale, SCALE_MIN/scale);
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
if (touch.tapCount >= 2) {
[self.superview bringSubviewToFront:self];
}
}
[self updateOriginalTransformForTouches:[event touchesForView:self]];
[self removeTouchesFromCache:touches];
NSMutableSet *remainingTouches = [[[event touchesForView:self] mutableCopy] autorelease];
[remainingTouches minusSet:touches];
[super touchesEnded:touches withEvent:event];
[self cacheBeginPointForTouches:remainingTouches];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
You can know CGRect of UIImagView before applying transform on it like below example :
//Create transform
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1.2, 1.2);
//Apply transform on UIImageView to get CGRect
CRect newRect = CGRectApplyAffineTransform(yourImgView.frame, scaleTransform);
//Check if rect is in valid limitation
if(newRect.size.height > 200 && newRect.size.width >200) //your condition to limit UIImageView's Size
{
//Valid so apply transform
yourImageView.transform = scaleTransform
}
else
{
//rect increases so no transform
// no transform to UIImageView
NSLog(#"%#",NSStringFromCGRect(yourImageView.frame))
}
I was facing the a similar problem, I have an image view that's rotating according to compass heading data and also has to move position according to device change in interface orientation
I created a tempImageView to help maintain frame, and kept reseting it with every change in device orientation
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
// equal tempImageView to imageView
_tempImageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.view.frame.size.width-75, 150, 64, 61)];
[_tempImageView setImage:[UIImage imageNamed:#"image.gif"]];
_tempImageView.transform = _imageView.transform;
// reset imageView
[_imageView removeFromSuperview];
_imageView = _tempImageView;
[self.view insertSubview:_imageView atIndex:1];
}

How to achieve continuous drag drop menu effect?

I'm trying to achieve a Drag and Drop menu affect. I'm not sure how to go about this, perhaps someone has experience with this exact effect.
Quite simply, when a user touches down on a menu item, I want a graphic to appear at their touch location. Their touch will now control the panning of the graphic. Upon releasing the touch, the graphic will sit in its place and assume full alpha.
I'm already familiar with creating pan gestures and instantiating a graphic. So far, I can create the graphic where the menu item is touched. The biggest issue is how I "pass over" the touch gesture so it is a single and continuous motion.
Also, should the menu item be UIButton or UIImageView?
Any help appreciated. Thanks
I had some fun with this one. The following code will grab the image from the button when touched, drag that image at alpha=0.5, and drop it wherever your touches end at alpha=1.0. It will continue to be draggable thereafter.
After importing QuartzCore, create a new file. The .h should read:
#import <Foundation/Foundation.h>
#import <QuartzCore/CAGradientLayer.h>
#import <QuartzCore/CALayer.h>
#interface DraggableImage : CAGradientLayer
- (void)draw:(UIImage *)image;
- (void)moveToFront;
- (void)appearDraggable;
- (void)appearNormal;
#end
and the .m should read:
#import "DraggableImage.h"
#implementation DraggableImage
- (void)draw:(UIImage *)image{
CGRect buttonFrame = self.bounds;
int buttonWidth = buttonFrame.size.width;
int buttonHeight = buttonFrame.size.height;
UIGraphicsBeginImageContext( CGSizeMake(buttonWidth, buttonHeight) );
[image drawInRect:self.bounds];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[newImage drawInRect:self.bounds];
}
- (void)moveToFront {
CALayer *superlayer = self.superlayer;
[self removeFromSuperlayer];
[superlayer addSublayer:self];
}
- (void)appearDraggable {
self.opacity = 0.5;
}
- (void)appearNormal {
self.opacity = 1.0;
}
#end
Now in your main view controller, add:
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "DraggableImage.h"
#interface YourViewController : UIViewController{
DraggableImage *heldImage;
DraggableImage *imageForFrame[5]; // or however many
UIButton *buttonPressed;
int imageCount;
}
#property (weak, nonatomic) IBOutlet UIButton *imageButton;
-(IBAction)buildImageLayerForButton:(UIButton *)sender;
- (void)moveHeldImageToPoint:(CGPoint)location;
- (CALayer *)layerForTouch:(UITouch *)touch;
The imageButton in this case would be your apple Button. Now in your .m file, add this:
#synthesize imageButton;
#pragma - mark Touches
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CALayer *hitLayer = [self layerForTouch:[touches anyObject]];
if ([hitLayer isKindOfClass:[DraggableImage class]]) {
DraggableImage *image = (DraggableImage *)hitLayer;
heldImage = image;
[heldImage moveToFront];
}
hitLayer = nil;
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
if (heldImage)
{
UITouch *touch = [touches anyObject];
UIView *view = self.view;
CGPoint location = [touch locationInView:view];
[self moveHeldImageToPoint:location];
}
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
if (heldImage) {
[heldImage appearNormal];
heldImage = nil;
}
}
- (void)dragBegan:(UIControl *)c withEvent:ev {
}
- (void)dragMoving:(UIControl *)c withEvent:ev {
UITouch *touch = [[ev allTouches] anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
[self moveHeldImageToPoint:touchPoint];
}
- (void)dragEnded:(UIControl *)c withEvent:ev {
UITouch *touch = [[ev allTouches] anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
[self moveHeldImageToPoint:touchPoint];
[heldImage appearNormal];
heldImage = nil;
}
-(IBAction)buildImageLayerForButton:(UIButton *)sender{
DraggableImage *image = [[DraggableImage alloc] init];
buttonPressed = sender;
CGRect buttonFrame = sender.bounds;
int buttonWidth = buttonFrame.size.width;
int buttonHeight = buttonFrame.size.height;
image.frame = CGRectMake(120, 24, buttonWidth*3, buttonHeight*3);
image.backgroundColor = [UIColor lightGrayColor].CGColor;
image.delegate = self;
imageForFrame[imageCount] = image;
[self.view.layer addSublayer:image];
[image setNeedsDisplay];
[image moveToFront];
[image appearDraggable];
heldImage = image;
[self moveHeldImageToPoint:sender.center];
imageCount++;
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
UIGraphicsPushContext(ctx);
DraggableImage *image = (DraggableImage *)layer;
[image draw:[buttonPressed imageForState:UIControlStateNormal]];
UIGraphicsPopContext();
}
- (void)moveHeldImageToPoint:(CGPoint)location
{
float dx = location.x;
float dy = location.y;
CGPoint newPosition = CGPointMake(dx, dy);
[CATransaction begin];
[CATransaction setDisableActions:TRUE];
heldImage.position = newPosition;
[CATransaction commit];
}
- (CALayer *)layerForTouch:(UITouch *)touch
{
UIView *view = self.view;
CGPoint location = [touch locationInView:view];
location = [view convertPoint:location toView:nil];
CALayer *hitPresentationLayer = [view.layer.presentationLayer hitTest:location];
if (hitPresentationLayer)
{
return hitPresentationLayer.modelLayer;
}
return nil;
}
-(void)viewDidLoad{
[imageButton addTarget:self action:#selector(dragBegan:withEvent:) forControlEvents: UIControlEventTouchDown];
[imageButton addTarget:self action:#selector(dragMoving:withEvent:) forControlEvents: UIControlEventTouchDragInside | UIControlEventTouchDragOutside];
[imageButton addTarget:self action:#selector(dragEnded:withEvent:) forControlEvents: UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
[super viewDidLoad];
}
- (void)viewDidUnload {
[self setImageButton:nil];
[super viewDidUnload];
}
Et voila! Connect your button, set its image, and throw copies all over the screen. :)
Note: I didn't comment much, but would be happy to answer any questions.
Cheers!
EDIT: fixed the -(void)draw:(UIImage *)image{} so that it would resize the image properly.
if what you want is to pass the touch function to the second graphic (the big one) i think you can do something like this
on .h you have to declare the images that you're going to drag and float variable to remember previous point of the dragable button (i'm assuming you use IOS 5 SDK)
#property(nonatomic, strong) UIImageView* myImage;
#property float pointX;
#property float pointY;
then, at .m you can do this
myImage = [[UIImageView alloc]initWithImage:#"appleImage.jpg"];
myImage.alpha = 0;
//default UIImageView interaction is disabled, so lets enabled it first
myImage.userInteractionEnabled = YES;
[button addTarget:self action:#selector(wasDragged:withEvent:) forControlEvents:UIControlEventTouchDragInside];
and then make the drag function
- (void)wasDragged:(UIButton *)button withEvent:(UIEvent *)event
{
self.myImage.alpha = 0.5;
UITouch *touch = [[event touchesForView:button] anyObject];
CGPoint previousLocation = [touch previousLocationInView:button];
CGPoint location = [touch locationInView:button];
CGFloat delta_x = location.x - previousLocation.x;
CGFloat delta_y = location.y - previousLocation.y;
// move button, to keep the dragging effect
button.center = CGPointMake(button.center.x + delta_x,
button.center.y + delta_y);
// moving the image
button.center = CGPointMake(button.center.x + delta_x,
button.center.y + delta_y);
self.pointX = previousLocation.x;
self.pointY = previousLocation.y;
[button addTarget:self action:#selector(dragRelease:withEvent:) forControlEvents:UIControlEventTouchUpInside];
}
finally, make the dragRelease function where you return the button to its original place and set the alpha of the images to 1
-(void)dragRelease:(UIButton *)button withEvent:(UIEvent *)event
{
self.myImage.alpha = 1;
button.center = CGPointMake(pointX, pointY);
}
and you're done :3
this is just the basic idea though, maybe this isn't what you want, but i hope this helps
edit* : oh and don't forget to synthesize all the properties, also if you're using SDK below 5.0, you can change the "strong" property to "retain"

Adding vertical alphabets selector in UIView

Hi
Can I add the vertical alphabets selector thing like that we use in UITableView, in UIView?
Best Regards
Yes.
If you create the UIView yourself you can do whatever you want.
It's not even that hard in your case. Some UILabels as subviews and some logic in touchesDidSomething:withEvent: to figure out which label is near the touch.
And a delegate method that tells which section was touched.
I think I could need something like that, so I decided to try it.
//myIndexSelectorView.m
- (id)initWithFrame:(CGRect)frame andLabels:(NSArray *)l {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor lightGrayColor];
self.layer.cornerRadius = frame.size.width/2;
labels = [l retain];
NSInteger count;
for (NSString *string in labels) {
CGFloat margin = 5.0f;
CGFloat yPosition = count*(frame.size.height/[labels count]);
UILabel *label = [[[UILabel alloc] initWithFrame:CGRectMake(margin, yPosition, frame.size.width-2*margin, frame.size.height/[labels count])] autorelease];
label.backgroundColor = [UIColor clearColor];
label.textColor = [UIColor darkGrayColor];
label.textAlignment = UITextAlignmentCenter;
label.text = string;
[self addSubview:label];
count++;
}
}
return self;
}
- (void)touch:(UITouch *)touch {
CGPoint touchPosition = [touch locationInView:self];
NSInteger index = touchPosition.y / (self.bounds.size.height / [labels count]);
NSLog(#"Touched: %#", [labels objectAtIndex:index]);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
[self touch:touch];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
[self touch:touch];
}
- (void)dealloc {
[labels release];
[super dealloc];
}
works as expected and looks similar to the index selector of uitableview
as usual, I didn't check for bugs, and this should not be a copy&paste solution.
You can use different UILabels, and set tags to them. Now to detect touches on appropriate label, you can use UITapGestureRecognizer classes
If you are talking about keypad, then that cannot be positioned other than the default position with a reason that apple does not allow to change its position.Hope that help you.Thanks.

UIImage detecting touch and dragging

Fairly common question this, to which I have a few answers and I'm nearly there. I have a button which when pressed, will create an image (code as follows)
(numImages is set on load to ZERO and is used as a count up for the tag numbers of all images created)
UIImage *tmpImage = [[UIImage imageNamed:[NSString stringWithFormat:#"%i.png", sender.tag]] retain];
UIImageView *myImage = [[UIImageView alloc] initWithImage:tmpImage];
numImages += 1;
myImage.userInteractionEnabled = YES;
myImage.tag = numImages;
myImage.opaque = YES;
[self.view addSubview:myImage];
[myImage release];
I then have a touchesBegan method which will detect what's touched. What I need it to do is to allow the user to drag the newly created image. It's nearly working, but the image flickers all over the place when you drag it. I can access the image which you click on as I can get it's TAG, but I just cannot drag it nicely.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
if (touch.view.tag > 0) {
touch.view.center = location;
}
NSLog(#"tag=%#", [NSString stringWithFormat:#"%i", touch.view.tag]);
}
- (void) touchesMoved:(NSSet *)touches withEvent: (UIEvent *)event {
[self touchesBegan:touches withEvent:event];
}
It works, in that I get an output of the tag for each image as I click on them. But when I drag, it flashes... any ideas?
In answer to my own question - I decided to create a class for handling the images I place on the view.
Code if anyone's interested....
Draggable.h
#import <Foundation/Foundation.h>
#interface Draggable : UIImageView {
CGPoint startLocation;
}
#end
Draggable.m
#import "Draggable.h"
#implementation Draggable
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
// Retrieve the touch point
CGPoint pt = [[touches anyObject] locationInView:self];
startLocation = pt;
[[self superview] bringSubviewToFront:self];
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
// Move relative to the original touch point
CGPoint pt = [[touches anyObject] locationInView:self];
CGRect frame = [self frame];
frame.origin.x += pt.x - startLocation.x;
frame.origin.y += pt.y - startLocation.y;
[self setFrame:frame];
}
#end
and to call it
UIImage *tmpImage = [[UIImage imageNamed:"test.png"] retain];
UIImageView *imageView = [[UIImageView alloc] initWithImage:tmpImage];
CGRect cellRectangle;
cellRectangle = CGRectMake(0,0,tmpImage.size.width ,tmpImage.size.height );
UIImageView *dragger = [[Draggable alloc] initWithFrame:cellRectangle];
[dragger setImage:tmpImage];
[dragger setUserInteractionEnabled:YES];
[self.view addSubview:dragger];
[imageView release];
[tmpImage release];
Usually you get an implicit animation when you change center. Are you messing with -contentMode or calling -setNeedsDisplay by any chance?
You can explicitly request animation to avoid the delete and re-draw this way:
if (touch.view.tag > 0) {
[UIView beginAnimations:#"viewMove" context:touch.view];
touch.view.center = location;
[UIView commitAnimations];
}
Do note that NSLog() can be very slow (much slower than you'd expect; it's much more complicated than a simple printf), and that can cause trouble in something called as often as touchesMoved:withEvent:.
BTW, you're leaking tmpImage.