Custom UIView not receiving touch events - ios

I am building a custom slider to my application.
I found a library on Github as inspiration.
I have created the file: NKSlider as a subclass of UIView and implemented on it the drawRect as the library did and implemented the methods: touchesMoved:withEvent: and touchesBegan:withEvent:. But none of the touches events are being called.
this is how i add this custom view to my view controller:
NKHandler *nk = [[NKHandler alloc] init];
nk.frame = CGRectMake(20, 20, 280, 36);
[self.view addSubview:nk];
[self.view addSubview:[[NKHandler alloc] initWithFrame:CGRectMake(20, 60, 280, 100)]];
and this are the classes. What am i doing wrong??
the .h
#import <UIKit/UIKit.h>
#interface NKHandler : UIView
#end
and .m
#import "NKHandler.h"
static inline CGPoint CGPointTopCenter(CGRect rect) {
CGPoint p; p.x = rect.origin.x + (rect.size.width / 2); p.y = rect.origin.y; return p;
}
static inline CGPoint CGPointBottomCenter(CGRect rect) {
CGPoint p; p.x = rect.origin.x + (rect.size.width / 2); p.y = rect.origin.y + rect.size.height; return p;
}
static inline CGPoint CGPointLeftCenter(CGRect rect) {
CGPoint p; p.x = rect.origin.x; p.y = rect.origin.y + (rect.size.height / 2); return p;
}
static inline CGPoint CGPointRightCenter(CGRect rect) {
CGPoint p; p.x = rect.origin.x + rect.size.width; p.y = rect.origin.y + (rect.size.height / 2); return p;
}
#implementation NKHandler {
int _selectedStep;
}
-(id)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = NO;
self.opaque = YES;
_selectedStep = 5;
}
return self;
}
-(void) setStep:(int) step {
_selectedStep = step;
[self setNeedsDisplay];
}
-(void)layoutSubviews {
... layout stuff ...
}
-(void)drawRect:(CGRect)rect {
... Boring drawing stuff ..
}
#pragma mark - Touch Handling
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"%s", __PRETTY_FUNCTION__);
[self setStep:(_selectedStep+1)%11];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"%s", __PRETTY_FUNCTION__);
}
#end

nk.userInteractionEnabled = YES;

If userInteractionEnabled trick not works maybe you should check the view's parentview autosizing attributes.
I changed it like this see below, because storyboard loaded it 'originally' in portrait mode, but I presented it in landscape, so the parent's view height become 0 px. And even dow the view itself kept its height the parents become 0 px so view did not get the event.

This is probably a super edge case, but I had this problem when accidentally had my custom view class descend from UICollectionViewCell in code, but in the XIB it the element used was UIView.

I've seen this line cause trouble with UIView events:
contentView.translatesAutoresizingMaskIntoConstraints = NO;
This line can cause headaches after you've pushed and popped a view controller, using the pushViewController and popViewControllerAnimated methods. Once your contentView is displayed again, you may find event handling doesn't work.

Related

Pan gesture interferes with scroll

I have three view controllers that are part of a UIScrollView. I want to be able to swipe between the three, although one of the view controllers has a UIPanGestureRecognizer. I use this pan gesture recognizer to allow the user to drag their finger up and down to increase and decrease the height of a rectangular UIView. Therefore, this UIPanGestureRecognizer only really needs to know about the upwards/downwards panning, and the scroll view can use the horizontal panning.
An example of this, is like the home screen; you can swipe left or right, but also swipe down to get spotlight. I want this kind of mechanism.
This is my code for the pan:
- (void)pan:(UIPanGestureRecognizer *)aPan; // When pan guesture is recognised
{
CGPoint location = [aPan locationInView:self.view]; // Location of finger on screen
CGRect secondRect = CGRectMake(210.0, 45.0, 70.0, 325.0); // Rectangles of maximimum bar area
CGRect minuteRect = CGRectMake(125.0, 45.0, 70.0, 325.0);
CGRect hourRect = CGRectMake(41.0, 45.0, 70.0, 325.0);
if (CGRectContainsPoint(secondRect, location)) { // If finger is inside the 'second' rectangle
CGPoint currentPoint = [aPan locationInView:self.view];
currentPoint.y -= 80; // Make sure animation doesn't go outside the bars' rectangle
if (currentPoint.y < 0) {
currentPoint.y = 0;
}
else if (currentPoint.y > 239) {
currentPoint.y = 239;
}
currentPoint.y = 239.0 - currentPoint.y;
CGFloat pointy = currentPoint.y - fmod(currentPoint.y, 4.0);
[UIView animateWithDuration:0.01f // Animate the bars to rise as the finger moves up and down
animations:^{
CGRect oldFrame = secondBar.frame;
secondBar.frame = CGRectMake(oldFrame.origin.x, (oldFrame.origin.y - (pointy - secondBar.frame.size.height)), oldFrame.size.width, (pointy));
}];
CGFloat result = secondBar.frame.size.height - fmod(secondBar.frame.size.height, 4.0);
secondInt = (result / 4.0); // Update labels with new time
self->secondLabel.text = [NSString stringWithFormat:#"%02d", secondInt];
}
The code is basically repeated for three separate rectangular UIViews.
If anyone can tell me how to get the homescreen-style panning/swiping into my app, that would be great!!
Alright, here is the short answer:
You have to use UIGestureRecognizer's method -requireGestureRecognizerToFail:.
And here is the long answer:
You have to make the pan gesture recognizer of your scroll view to succeed only if the pan gesture recognizer of TimerViewController fails. However that gesture (TimerViewController's gesture) should only succeed if the initial movement is vertical. If it is horizontal it should fail.
To accomplish this we have to subclass UIPanGestureRecognizer and modify it to fit those needs.
Here is what you have to do:
Disregard ALL the changes you made from my previous answer
Add VerticalPanGestureRecognizer to your project.
Modify TimerViewController as shown.
Modify ScrollViewController as shown.
VerticalPanGestureRecognizer.h
#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface VerticalPanGestureRecognizer : UIPanGestureRecognizer
#end
VerticalPanGestureRecognizer.m
#import "VerticalPanGestureRecognizer.h"
#interface VerticalPanGestureRecognizer ()
#property (nonatomic, assign) CGPoint origLoc;
#end
#implementation VerticalPanGestureRecognizer
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.origLoc = [[touches anyObject] locationInView:self.view.superview];
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.state == UIGestureRecognizerStatePossible) {
CGPoint loc = [[touches anyObject] locationInView:self.view.superview];
CGFloat deltaX = fabs(loc.x - self.origLoc.x);
CGFloat deltaY = fabs(loc.y - self.origLoc.y);
if (deltaY < deltaX)
self.state = UIGestureRecognizerStateFailed;
}
[super touchesMoved:touches withEvent:event];
}
#end
TimerViewController.h
// Your imports here
#interface TimerViewController : UIViewController
{
// Your ivars here
}
// Add the following property
#property (nonatomic, strong) UIPanGestureRecognizer *pan;
// Your methods here
#end
TimerViewController.m
#import "TimerViewController.h"
#import "VerticalPanGestureRecognizer.h"
#implementation TimerViewController
#synthesize pan = _pan;
// prefersStatusBarHidden method here
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil // Initialise view controller
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Instantiate the pan gesture as "VerticalPanGestureRecognizer"
self.pan = [[VerticalPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan:)]; // Create recogniser for a pan guesture
self.pan.maximumNumberOfTouches = self.pan.minimumNumberOfTouches = 1;
[self.view addGestureRecognizer:self.pan];
}
return self;
}
// The rest of your code here
#end
ScrollViewController.m
- (void)viewDidLoad
{
// Your code here
TimerViewController *tvc = [[TimerViewController alloc]init];
CGRect frame = tvc.view.frame;
frame.origin.x = 320;
tvc.view.frame = frame;
// Add the following line
[self.scrollView.panGestureRecognizer requireGestureRecognizerToFail:tvc.pan];
[self addChildViewController:tvc];
[self.scrollView addSubview:tvc.view];
[tvc didMoveToParentViewController:self];
// More code here
}
This new approach works perfectly. I tested it.
Let me know if you have more questions.
Cheers!
UPDATE
To answer the question you posted on the comments, here is what you have to do:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
BarsViewController *bvc = [[BarsViewController alloc]init];
[self addChildViewController:bvc];
[self.scrollView addSubview:bvc.view];
[bvc didMoveToParentViewController:self];
TimerViewController *tvc = [[TimerViewController alloc]init];
CGRect frame = tvc.view.frame;
frame.origin.x = 320;
tvc.view.frame = frame;
[self.scrollView.panGestureRecognizer requireGestureRecognizerToFail:tvc.pan];
[self addChildViewController:tvc];
[self.scrollView addSubview:tvc.view];
[tvc didMoveToParentViewController:self];
StopwatchViewController *svc = [[StopwatchViewController alloc] init];
frame = svc.view.frame;
frame.origin.x = 320*2;
svc.view.frame = frame;
[self addChildViewController:svc];
[self.scrollView addSubview:svc.view];
[svc didMoveToParentViewController:self];
self.scrollView.contentSize = CGSizeMake(320*3, self.view.frame.size.height);
self.scrollView.pagingEnabled = YES;
[self.scrollView setShowsHorizontalScrollIndicator:NO];
}
Again, I tested it and it's working. You just have to add the gesture recognizer for the bars

How to create a dynamic rectangle on uiimage in ios?

i am trying to crate a dynamic rectangle on imageview. it means rectangle should start on the touch and as long as user move the touch it should be bigger in that direction.
friend suggest me, please.
You need to use Core Graphics for that, try this tutorial:
Quartz 2D Programming Guide
It sounds like maybe you want a selection rectangle on top of the UIImage, not just the ability to resize the image.
If that's the case, I'd recommend the following structure:
Load the UIImage into a UIImageView and place that on screen.
Create a UIView that is a sibling of the UIImageView
Override the -drawRect: method of the UIView from #2 to simply draw a bordered outline.
Detect your user's touches using -touchesBegan:/-touchesMoved:/-touchesEnded:, etc.
Based on your logic and math from #4, adjust the UIView's frame in an animation block.
In my experience, animations of this nature are easier to make functional when using shocks and struts and -touchesBegan, etc, as opposed to Autolayout and UIGestureRecognizers, but YMMV.
Of course, what you're doing might require some advanced math, based on how you answer questions like the following:
Can the user move the selection rectangle?
Do I want the user to grab an "edge" to be able to resize it?
How close does the user have to be to "grab" the edge?
If not, is the user just "pushing" in a direction to make the rectangle bigger, no matter where their finger is?
The following code does the following:
Loads an image into a UIImageView
Allows the user to place a 100x100 selection rectangle (at first)
Allows the user to move the selection rectangle by touching/dragging in interior of rectangle
Allows the user to resize the select rectangle by grabbing one of 8 edge areas
Updates drawing of selection rectangle to indicate "active" mode from "inactive" mode
Assumptions made by the code:
User will interact with screen using 1 finger
UIImageView takes up the entire view
Possible updates needed by this code:
User can move selection rectangle partly offscreen
This code doesn't DO anything with the selection rectangle (like, take a snapshot/image of selection rectangle contents)
#define GRAB_DISTANCE 10
#define VIEW_PLACEMENT_ANIMATION_DURATION 0.1
#define VIEW_SIZING_ANIMATION_DURATION 0.1
typedef enum {
kMSSLineTypeDashed,
kMSSLineTypeSolid
} MSSLineType;
typedef enum {
kMSSRectangleGrabZoneBottom,
kMSSRectangleGrabZoneBottomLeft,
kMSSRectangleGrabZoneBottomRight,
kMSSRectangleGrabZoneLeft,
kMSSRectangleGrabZoneNone,
kMSSRectangleGrabZoneRight,
kMSSRectangleGrabZoneTop,
kMSSRectangleGrabZoneTopLeft,
kMSSRectangleGrabZoneTopRight
} MSSRectangleGrabZone;
typedef enum {
kMSSRectangleStatusNone,
kMSSRectangleStatusPlacement,
kMSSRectangleStatusResizing
} MSSRectangleStatus;
#interface MSSSelectionView : UIView
#property (assign, nonatomic) MSSLineType currentLineType;
#property (strong, nonatomic) UIColor *borderColor;
#end
#implementation MSSSelectionView
- (void)awakeFromNib {
[super awakeFromNib];
self.currentLineType = kMSSLineTypeSolid;
}
- (void)drawRect:(CGRect)rect {
// Just make a border, 2 points wide, 1 point inset (so it is all contained by the view)
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect borderRect = CGRectInset(rect, 1, 1);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, 2.0f);
switch (self.currentLineType) {
case kMSSLineTypeDashed:
{
CGFloat lengths[2] = {3, 4};
CGContextSetLineDash(context, 0.0f, lengths, 2);
}
break;
case kMSSLineTypeSolid:
{
CGContextSetLineDash(context, 0.0f, NULL, 0);
}
break;
default:
break;
}
[self.borderColor setStroke];
CGContextStrokeRect(context, borderRect);
}
#end
#import "MSSViewController.h"
#interface MSSViewController ()
#property (assign, nonatomic) BOOL selectionIsVisible;
#property (assign, nonatomic) MSSRectangleGrabZone currentGrabZone;
#property (assign, nonatomic) MSSRectangleStatus currentStatus;
#property (strong, nonatomic) IBOutlet MSSSelectionView *selectionView;
#property (strong, nonatomic) IBOutlet UIImageView *imageView;
#end
#implementation MSSViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.imageView.image = [UIImage imageNamed:#"image.jpg"];
self.currentGrabZone = kMSSRectangleGrabZoneNone;
self.currentStatus = kMSSRectangleStatusNone;
}
#pragma mark - Touch Handling
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Get a touch object (assuming just a 1-finger touch here)
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
if (self.selectionIsVisible == NO) {
// Showing the selection view for the first time
// Update instance property
self.selectionIsVisible = YES;
// Place the rectangle (centered around touch)
self.selectionView.center = location;
// Unhide the rectangle
self.selectionView.hidden = NO;
// Set the status flag to placement
self.currentStatus = kMSSRectangleStatusPlacement;
// Change the border color to indicate that it's active
self.selectionView.borderColor = [UIColor lightGrayColor];
self.selectionView.currentLineType = kMSSLineTypeDashed;
[self.selectionView setNeedsDisplay];
} else {
// Selection view already visible, so first make sure the touch was inside the selection view
if (CGRectContainsPoint(self.selectionView.frame, location)) {
// The touch was inside the selection view, so update the selection view's line drawing properties
self.selectionView.borderColor = [UIColor lightGrayColor];
self.selectionView.currentLineType = kMSSLineTypeDashed;
[self.selectionView setNeedsDisplay];
// Set status flag based on proximity to edge
BOOL edgeGrabbed = [self location:location inGrabZoneForRect:self.selectionView.frame];
if (edgeGrabbed == YES) {
// The user has grabbed the edge, so allow selection view resizing
self.currentStatus = kMSSRectangleStatusResizing;
self.currentGrabZone = [self zoneGrabbedForPoint:location inRect:self.selectionView.frame];
} else {
// The user has touched the interior, so allow selection view movement/placement
self.currentStatus = kMSSRectangleStatusPlacement;
self.currentGrabZone = kMSSRectangleGrabZoneNone;
}
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// Get a touch object (assuming just a 1-finger touch here)
UITouch *touch = [touches anyObject];
CGPoint currentLocation = [touch locationInView:self.view];
CGPoint previousLocation = [touch previousLocationInView:self.view];
CGFloat xDelta = currentLocation.x - previousLocation.x;
CGFloat yDelta = currentLocation.y - previousLocation.y;
CGRect frame = self.selectionView.frame;
switch (self.currentStatus) {
case kMSSRectangleStatusNone:
// Do nothing
break;
case kMSSRectangleStatusPlacement:
{
// The entire selection view should be moved under the user's finger
frame.origin.x = frame.origin.x + xDelta;
frame.origin.y = frame.origin.y + yDelta;
}
break;
case kMSSRectangleStatusResizing:
{
switch (self.currentGrabZone) {
case kMSSRectangleGrabZoneBottom:
{
// Make the view's frame taller or shorter based on yDelta
frame.size.height = frame.size.height + yDelta;
}
break;
case kMSSRectangleGrabZoneBottomLeft:
{
// Make the view's frame taller or shorter based on yDelta
// Make the view's frame wider or narrower PLUS move origin based on xDelta
frame.origin.x = frame.origin.x + xDelta;
frame.size.height = frame.size.height + yDelta;
frame.size.width = frame.size.width - xDelta;
}
break;
case kMSSRectangleGrabZoneBottomRight:
{
// Make the view's frame taller or shorter based on yDelta
// Make the view's frame wider or narrower based on xDelta
frame.size.height = frame.size.height + yDelta;
frame.size.width = frame.size.width + xDelta;
}
break;
case kMSSRectangleGrabZoneLeft:
{
// Make the view's frame wider or narrower PLUS move origin based on xDelta
frame.origin.x = frame.origin.x + xDelta;
frame.size.width = frame.size.width - xDelta;
}
break;
case kMSSRectangleGrabZoneNone:
// Do nothing
break;
case kMSSRectangleGrabZoneRight:
{
// Make the view's frame wider or narrower based on xDelta
frame.size.width = frame.size.width + xDelta;
}
break;
case kMSSRectangleGrabZoneTop:
{
// Make the view's frame taller or shorter PLUS move origin based on yDelta
frame.origin.y = frame.origin.y + yDelta;
frame.size.height = frame.size.height - yDelta;
}
break;
case kMSSRectangleGrabZoneTopLeft:
{
// Make the view's frame wider or narrower PLUS move origin based on xDelta
// Make the view's frame taller or shorter PLUS move origin based on yDelta
frame.origin.x = frame.origin.x + xDelta;
frame.origin.y = frame.origin.y + yDelta;
frame.size.width = frame.size.width - xDelta;
frame.size.height = frame.size.height - yDelta;
}
break;
case kMSSRectangleGrabZoneTopRight:
{
// Make the view's frame wider or narrower based on xDelta
// Make the view's frame taller or shorter PLUS move origin based on yDelta
frame.origin.y = frame.origin.y + yDelta;
frame.size.height = frame.size.height - yDelta;
frame.size.width = frame.size.width + xDelta;
}
break;
default:
break;
}
}
break;
default:
break;
}
// Any frame changes made above should be animated here
[UIView animateWithDuration:VIEW_PLACEMENT_ANIMATION_DURATION
animations:^{
self.selectionView.frame = frame;
}
completion:^(BOOL finished) {
[self.selectionView setNeedsDisplay];
}
];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// Nothing much to do, just make the border black to indicate activity is done
self.currentGrabZone = kMSSRectangleGrabZoneNone;
self.currentStatus = kMSSRectangleStatusNone;
self.selectionView.borderColor = [UIColor blackColor];
self.selectionView.currentLineType = kMSSLineTypeSolid;
[self.selectionView setNeedsDisplay];
}
#pragma mark - Rectangle helper methods
- (BOOL)location:(CGPoint)location inGrabZoneForRect:(CGRect)rect {
if (CGRectContainsPoint(rect, location)) {
// The point is inside the rectangle, so determine if it's in the grab zone or the interior
CGRect nonGrabZoneRect = CGRectInset(rect, GRAB_DISTANCE, GRAB_DISTANCE);
if (CGRectContainsPoint(nonGrabZoneRect, location)) {
// This point is in the interior (non-grab zone)
return NO;
} else {
// This point is in the grab zone
return YES;
}
} else {
// The point is not inside the rectangle, which means they didn't grab the edge/border
return NO;
}
}
- (MSSRectangleGrabZone)zoneGrabbedForPoint:(CGPoint)point inRect:(CGRect)rect {
CGRect topLeftGrabZone = CGRectMake(rect.origin.x, rect.origin.y, GRAB_DISTANCE, GRAB_DISTANCE);
CGRect topGrabZone = CGRectMake(rect.origin.x + GRAB_DISTANCE, rect.origin.y, rect.size.width - (2 * GRAB_DISTANCE), GRAB_DISTANCE);
CGRect topRightGrabZone = CGRectMake(rect.origin.x + rect.size.width - GRAB_DISTANCE, rect.origin.y, GRAB_DISTANCE, GRAB_DISTANCE);
CGRect leftGrabZone = CGRectMake(rect.origin.x, rect.origin.y + GRAB_DISTANCE, GRAB_DISTANCE, rect.size.height - (2 * GRAB_DISTANCE));
CGRect rightGrabZone = CGRectMake(rect.origin.x + rect.size.width - GRAB_DISTANCE, rect.origin.y + GRAB_DISTANCE, GRAB_DISTANCE, rect.size.height - (2 * GRAB_DISTANCE));
CGRect bottomLeftGrabZone = CGRectMake(rect.origin.x, rect.origin.y + rect.size.height - GRAB_DISTANCE, GRAB_DISTANCE, GRAB_DISTANCE);
CGRect bottomGrabZone = CGRectMake(rect.origin.x + GRAB_DISTANCE, rect.origin.y + rect.size.height - GRAB_DISTANCE, rect.size.width - (2 * GRAB_DISTANCE), GRAB_DISTANCE);
CGRect bottomRightGrabZone = CGRectMake(rect.origin.x + rect.size.width - GRAB_DISTANCE, rect.origin.y + rect.size.height - GRAB_DISTANCE, GRAB_DISTANCE, GRAB_DISTANCE);
if (CGRectContainsPoint(topLeftGrabZone, point)) {
return kMSSRectangleGrabZoneTopLeft;
} else if (CGRectContainsPoint(topGrabZone, point)) {
return kMSSRectangleGrabZoneTop;
} else if (CGRectContainsPoint(topRightGrabZone, point)) {
return kMSSRectangleGrabZoneTopRight;
} else if (CGRectContainsPoint(leftGrabZone, point)) {
return kMSSRectangleGrabZoneLeft;
} else if (CGRectContainsPoint(rightGrabZone, point)) {
return kMSSRectangleGrabZoneRight;
} else if (CGRectContainsPoint(bottomLeftGrabZone, point)) {
return kMSSRectangleGrabZoneBottomLeft;
} else if (CGRectContainsPoint(bottomGrabZone, point)) {
return kMSSRectangleGrabZoneBottom;
} else if (CGRectContainsPoint(bottomRightGrabZone, point)) {
return kMSSRectangleGrabZoneBottomRight;
} else {
return kMSSRectangleGrabZoneNone;
}
}
#end
You can create imageview programmatically and resize its frame as touch moves. See below link may it help you.
How can I draw dynamic rectangle on screen according to touchMove event on iOS

Rotating rectangle around circumference of a circle (iOS)?

I am trying to rotate the rectangle around the circle. So far after putting together some code I found in various places (mainly here: https://stackoverflow.com/a/4657476/861181) , I am able to rotate rectangle around it's center axis.
How can I make it rotate around the circle?
Here is what I have:
OverlaySelectionView.h
#import <QuartzCore/QuartzCore.h>
#interface OverlaySelectionView : UIView {
#private
UIView* dragArea;
CGRect dragAreaBounds;
UIView* vectorArea;
UITouch *currentTouch;
CGPoint touchLocationpoint;
CGPoint PrevioustouchLocationpoint;
}
#property CGRect vectorBounds;
#end
OverlaySelectionView.m
#import "OverlaySelectionView.h"
#interface OverlaySelectionView()
#property (nonatomic, retain) UIView* vectorArea;
#end
#implementation OverlaySelectionView
#synthesize vectorArea, vectorBounds;
#synthesize delegate;
- (void) initialize {
self.userInteractionEnabled = YES;
self.multipleTouchEnabled = NO;
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(rotateVector:)];
panRecognizer.maximumNumberOfTouches = 1;
[self addGestureRecognizer:panRecognizer];
}
- (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 {
if (vectorBounds.origin.x){
UIView* area = [[UIView alloc] initWithFrame: vectorBounds];
area.backgroundColor = [UIColor grayColor];
area.opaque = YES;
area.userInteractionEnabled = NO;
vectorArea = area;
[self addSubview: vectorArea];
}
}
- (void)rotateVector: (UIPanGestureRecognizer *)panRecognizer{
if (touchLocationpoint.x){
PrevioustouchLocationpoint = touchLocationpoint;
}
if ([panRecognizer numberOfTouches] >= 1){
touchLocationpoint = [panRecognizer locationOfTouch:0 inView:self];
}
CGPoint origin;
origin.x=240;
origin.y=160;
CGPoint previousDifference = [self vectorFromPoint:origin toPoint:PrevioustouchLocationpoint];
CGAffineTransform newTransform =CGAffineTransformScale(vectorArea.transform, 1, 1);
CGFloat previousRotation = atan2(previousDifference.y, previousDifference.x);
CGPoint currentDifference = [self vectorFromPoint:origin toPoint:touchLocationpoint];
CGFloat currentRotation = atan2(currentDifference.y, currentDifference.x);
CGFloat newAngle = currentRotation- previousRotation;
newTransform = CGAffineTransformRotate(newTransform, newAngle);
[self animateView:vectorArea toPosition:newTransform];
}
-(CGPoint)vectorFromPoint:(CGPoint)firstPoint toPoint:(CGPoint)secondPoint
{
CGPoint result;
CGFloat x = secondPoint.x-firstPoint.x;
CGFloat y = secondPoint.y-firstPoint.y;
result = CGPointMake(x, y);
return result;
}
-(void)animateView:(UIView *)theView toPosition:(CGAffineTransform) newTransform
{
[UIView setAnimationsEnabled:YES];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.0750];
vectorArea.transform = newTransform;
[UIView commitAnimations];
}
#end
here is attempt to clarify. I am creating the rectangle from a coordinates on a map. Here is the function that creates that rectangle in the main view. Essentially it is the middle of the screen:
overlay is the view created with the above code.
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
if (!circle){
circle = [MKCircle circleWithCenterCoordinate: userLocation.coordinate radius:100];
[mainMapView addOverlay:circle];
CGPoint centerPoint = [mapView convertCoordinate:userLocation.coordinate toPointToView:self.view];
CGPoint upPoint = CGPointMake(centerPoint.x, centerPoint.y - 100);
overlay = [[OverlaySelectionView alloc] initWithFrame: self.view.frame];
overlay.vectorBounds = CGRectMake(upPoint.x, upPoint.y, 30, 100);
[self.view addSubview: overlay];
}
}
Here is the sketch of what I am trying to achieve:
Introduction
A rotation is always done around (0,0).
What you already know:
To rotate around the center of the rectangle you translate the rect to origin, rotate and translate back.
Now for your question:
to rotate around a center point of a circle, simply move the center of the rectangle such that the circle is at (0,0) then rotate, and move back.
start positioning the rectangle at 12 o clock, with the center line at 12.
1) as explained you always rotate around 0,0, so move the center of the circle to 0,0
CGAffineTransform trans1 = CGAffineTransformTranslation(-circ.x, -circ.y);
2) rotate by angle
CGAffineTransform transRot = CGAffineTransformRotation(angle); // or -angle try out.
3) Move back
CGAffineTransform transBack = CGAffineTransformTranslation(circ.x, circ.y);
Concat these 3 rotation matrices to one combibed matrix, and apply it to the rectangle.
CGAffineTransformation tCombo = CGAffineTransformConcat(trans1, transRot);
tCombo = CGTransformationConcat(tCombo, transback);
Apply
rectangle.transform = tCombo;
You probably should also read the chapter about Transformation matrices in Quartz docu.
This code is written with a text editor only, so expect slighly different function names.

How to drag an uiimageview without overlapping views?

I have more than one view in superview. How can i drag an uiimageview without overlapping or touching other views. Any help is appreciated..
I have implemented similar type of thing. Here I am posting code snippet.
Draggable is class which you need to import in other class which contain images.
1) Draggable.h
#import <UIKit/UIKit.h>
#interface Draggable : UIImageView
{
CGPoint startLocation;
}
#end
2) Draggable.m
#import "Draggable.h"
#implementation Draggable
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (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
3) ProfilePicViewController.m - my class with images
#import "Draggable.h"
UIImageView *dragger;
-(void)viewWillAppear:(BOOL)animated
{
UIImage *tmpImage = [UIImage imageNamed:#"icon.png"];
CGRect cellRectangle;
cellRectangle = CGRectMake(0,0,tmpImage.size.width ,tmpImage.size.height );
dragger = [[Draggable alloc] initWithFrame:cellRectangle];
[dragger setImage:tmpImage];
[dragger setUserInteractionEnabled:YES];
[self.view addSubview:dragger];
}
here you can drag "dragger" on other images. Make sure to have proper image size. size of icon.png is 48X48. So just have image size that fits into your screen.
Hope this can help you bit.

(iOS) Using UIGestureRecognizer to zoom and pan around a view

I'm trying to perform zooming and panning similar to the UIScrollView by using UIGestures.
My view is drawn from a matrix of ON/OFF cells and needs to be able to support thousands of cells. The drawRect: method takes care of translating the matrix coordinates to screen coordinates. The view has a property for the zoom amount and a CGPoint which holds the offset.
I think if I can figure out the zooming and panning of the following, I should be good. Sorry for the wall of code below, but it represents a complete implementation which mirrors my more complex program.
Right now, the zoom does scale everything, but it needs a way to center itself, just like the UIScrollView zooming does.
The panning just does not work right at all.
ZoomView.h
ZoomView takes care of drawing the matrix of bools.
#import <Foundation/Foundation.h>
#import "ZoomModel.h"
#interface ZoomView : UIView
{
ZoomModel *m;
}
#property (nonatomic) float zoomScale;
#property (nonatomic) CGPoint offset;
- (id)initWithFrame:(CGRect)frame
andModel:(ZoomModel *)model;
- (BOOL)checkCellAt:(float)x
andY:(float)y;
- (CGSize)resize;
#end
ZoomView.m
The drawRect: method does the calculations for determining which matrix element should is in the visible portion of the screen. The visible portion of the screen is determined by the zoomScale and the offset.
#import "ZoomView.h"
#import <QuartzCore/QuartzCore.h>
#implementation ZoomView
#synthesize zoomScale, offset, holdZoom;
- (id)initWithFrame:(CGRect)frame
andModel:(ZoomModel *)model
{
self = [super initWithFrame:frame];
if (self) {
m = model;
zoomScale = 1.0;
offset = CGPointMake(0, 0);
}
return self;
}
- (void)setZoomScale:(float)s
{
zoomScale *= s;
if (zoomScale < 1.0) {
zoomScale = 1.0;
}
}
- (void)setOffset:(CGPoint)o
{
//This function is to make sure we don't pan outside the content range
//it needs some work, I'm having trouble getting the panning to work
float size = m.cellSize * zoomScale;
offset = o;
if ((offset.x - self.frame.size.width/size) <= 0) {
//offset.x = self.frame.size.width;
NSLog(#"X MIN");
}
if ((offset.x + self.frame.size.width/size) >= (m.gridLength*size)) {
// offset.x = (m.gridLength*size) - self.frame.size.width;
NSLog(#"X MAX");
}
if ((offset.y - self.frame.size.height/size) <= 0) {
//offset.y = self.frame.size.height;
NSLog(#"Y MIN");
}
if ((offset.y + self.frame.size.height/size) >= (m.gridLength*size)) {
// offset.y = (m.gridHeight*size) - self.frame.size.height;
NSLog(#"Y MAX");
}
}
- (BOOL)checkCellAt:(float)x
andY:(float)y
{
int X = (int)(x/m.cellSize * zoomScale);
int Y = (int)(y/m.cellSize * zoomScale);
return [m cellAtX:X andY:Y];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
[[UIColor blackColor] setFill];
CGContextFillRect(ctx, rect);
float size = m.cellSize * zoomScale;
[[UIColor whiteColor] setFill];
float a = offset.x;
float b = offset.y;
//the -5 is there to give a little buffer so that half cells can be seen
// -a is taken because the offset is negative
int startX = (int)(-a/size) - 5;
int startY = (int)(-b/size) - 5;
int endX = (int)(startX) + (int)(rect.size.width/size) + 10;
int endY = (int)(startY) + (int)(rect.size.height/size) + 10;
if (startX < 0)
startX = 0;
if (startY < 0)
startY = 0;
if (endX > m.gridLength)
endX = m.gridLength;
if (endY > m.gridHeight)
endY = m.gridHeight;
[[UIColor whiteColor] setFill];
for (float i=startX; i<endX; ++i) {
for (float j=startX; j<endY; ++j) {
if ([m cellAtX:(int)i andY:(int)j]) {
//ii and jj are there to make the drawing start on the top left corner of the view
float ii = i - startX;
float jj = j - startY;
CGRect cell = CGRectMake(size*ii, size*jj, size, size);
CGContextFillRect(ctx, cell);
}
}
}
}
#end
ZoomViewController.h
This view controller contains the gesture recognizers and handlers
#import <Foundation/Foundation.h>
#import "ZoomModel.h"
#import "ZoomView.h"
#interface ZoomViewController : UIViewController <UIGestureRecognizerDelegate>
{
ZoomModel *m;
ZoomView *v;
}
- (void)handleZoom:(UIPinchGestureRecognizer *)recognizer;
- (void)handlePan:(UIPanGestureRecognizer *)recognizer;
#end
ZoomViewController.m
The zoomView is set inside a UIView which has the screen frame as its frame. The zoomView itself is made a little bit larger than the screen as to allow half cells to be drawn.
#import "ZoomViewController.h"
#import <QuartzCore/QuartzCore.h>
#implementation ZoomViewController
- (void)loadView
{
CGRect screenRect = [[UIScreen mainScreen] bounds];
UIView *mainView = [[UIView alloc] initWithFrame:screenRect];
float cellSize = 1;
int ni = (int)(screenRect.size.width/cellSize);
int nj = (int)(screenRect.size.height/cellSize);
CGRect zoomRect = CGRectMake(0, 0, 1.2*screenRect.size.width, 1.2*screenRect.size.height);
m = [[ZoomModel alloc] initWithLength:ni andHeight:nj andCellSize:cellSize];
v = [[ZoomView alloc] initWithFrame:zoomRect andModel:m];
v.center = CGPointMake(v.frame.size.width/2.0, v.frame.size.height/2.0);
UIPinchGestureRecognizer *zRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
action:#selector(handleZoom:)];
zRecognizer.delegate = self;
[v addGestureRecognizer:zRecognizer];
UIPanGestureRecognizer *pRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:#selector(handlePan:)];
[pRecognizer setMaximumNumberOfTouches:1];
[pRecognizer setMinimumNumberOfTouches:1];
pRecognizer.delegate = self;
[v addGestureRecognizer:pRecognizer];
[mainView addSubview:v];
[self setView:mainView];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (void)handleZoom:(UIPinchGestureRecognizer *)recognizer
{
[v setZoomScale:recognizer.scale];
//need code to zoom around the center instead of the top left corner
recognizer.scale = 1;
[v setNeedsDisplay];
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer
{
//Adjusts the offset of the view, which is used in its drawRect:
CGPoint translation = [recognizer translationInView:self.view];
CGPoint newOffset = CGPointMake(v.offset.x - translation.x, v.offset.y - translation.y);
[v setOffset:newOffset];
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
[v setNeedsDisplay];
}
#end
ZoomModel.h
This class just populates a matrix of bools with random ON/OFF values, just so we can see something on the screen It simulates my more complex app model in its accessor method.
#import <Foundation/Foundation.h>
#interface ZoomModel : NSObject
{
bool *grid;
}
#property (nonatomic) int gridLength;
#property (nonatomic) int gridHeight;
#property (nonatomic) float cellSize;
- (id)initWithLength:(int)l
andHeight:(int)h
andCellSize:(float)s;
- (BOOL)cellAtX:(int)x
andY:(int)y;
#end
ZoomModel.m
#import "ZoomModel.h"
#implementation ZoomModel
#synthesize gridHeight, gridLength, cellSize;
- (id)initWithLength:(int)l
andHeight:(int)h
andCellSize:(float)s
{
self = [super init];
if (self) {
grid = malloc(l*h*sizeof(bool));
gridHeight = h;
gridLength = l;
cellSize = s;
for (int i=0; i<h*l; i++) {
if (arc4random()%6 >= 4)
grid[i] = true;
else
grid[i] = false;
}
}
return self;
}
- (BOOL)cellAtX:(int)x andY:(int)y
{
return (BOOL)grid[x*gridLength + y];
}
#end

Resources