UIBezierPath Rectangle with Circular Handles - ios

I'm trying to draw a rectangle which has four circular handles. Here's what it would look like:
o----o
| |
| |
o----o
The circular handles are "hot". In other words, when the user touches it, the handle can be moved around while the rest of the points are anchored. I wanted to know if anyone had an approach for coding this functionality. I'm looking at UIBezierPath to draw the rectangle with circles, but I'm having a hard time thinking about how to allow the user to tap only the circles. I was thinking it may need to be five different UIBezierPath objects, but eventually the UI will consist of multiples of these objects.
Any suggestions would be greatly appreciated. Thanks.

I wouldn't draw it as a single shape with complicated UIBezierPaths at all. I'd think about it as 6 different pieces. A Container, a rectangle, and 4 circles.
I would have a simple container UIView that has a rectangle view and four circular UIViews at its corners. Then put a UIPanGestureRecognizer on each circle. In the gesture handler, move the center of the circle and adjust the underlying rectangle rect by the same amount. This will avoid any complicated paths or math and make it simple add and subtract amounts on the rectangle itself.
Update: Code!
I created a self contained UIView subclass that handles everything. You can create one like so:
HandlesView *view = [[HandlesView alloc] initWithFrame:self.view.bounds];
[view setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth];
[view setBackgroundColor:[UIColor redColor]];
[self.view addSubview:view];
// A custom property that contains the selected area of the rectangle. Its updated while resizing.
[view setSelectedFrame:CGRectMake(128.0, 128.0, 200.0, 200.0)];
The frame of the view itself is the total draggable area. The selected frame is the inner visible rectangle.
//
// HandlesView.h
// handles
//
// Created by Ryan Poolos on 2/12/13.
// Copyright (c) 2013 Ryan Poolos. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface HandlesView : UIView
#property (nonatomic, readwrite) CGRect selectedFrame;
#end
And here is the implementation.
//
// HandlesView.m
// handles
//
// Created by Ryan Poolos on 2/12/13.
// Copyright (c) 2013 Ryan Poolos. All rights reserved.
//
#import "HandlesView.h"
#interface HandlesView ()
{
UIView *rectangle;
NSArray *handles;
NSMutableArray *touchedHandles;
UIView *circleTL;
UIView *circleTR;
UIView *circleBL;
UIView *circleBR;
}
#end
#implementation HandlesView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
rectangle = [[UIView alloc] initWithFrame:CGRectInset(self.bounds, 22.0, 22.0)];
[self addSubview:rectangle];
// Create the handles and position.
circleTL = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 44.0, 44.0)];
[circleTL setCenter:CGPointMake(CGRectGetMinX(rectangle.frame), CGRectGetMinY(rectangle.frame))];
circleTR = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 44.0, 44.0)];
[circleTR setCenter:CGPointMake(CGRectGetMaxX(rectangle.frame), CGRectGetMinY(rectangle.frame))];
circleBL = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 44.0, 44.0)];
[circleBL setCenter:CGPointMake(CGRectGetMinX(rectangle.frame), CGRectGetMaxY(rectangle.frame))];
circleBR = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 44.0, 44.0)];
[circleBR setCenter:CGPointMake(CGRectGetMaxX(rectangle.frame), CGRectGetMaxY(rectangle.frame))];
handles = #[ circleTL, circleTR, circleBL, circleBR ];
for (UIView *handle in handles) {
// Round the corners into a circle.
[handle.layer setCornerRadius:(handle.frame.size.width / 2.0)];
[self setClipsToBounds:YES];
// Add a drag gesture to the handle.
[handle addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)]];
// Add the handle to the screen.
[self addSubview:handle];
}
}
return self;
}
- (void)setSelectedFrame:(CGRect)selectedFrame
{
[rectangle setFrame:selectedFrame];
[circleTL setCenter:CGPointMake(CGRectGetMinX(rectangle.frame), CGRectGetMinY(rectangle.frame))];
[circleTR setCenter:CGPointMake(CGRectGetMaxX(rectangle.frame), CGRectGetMinY(rectangle.frame))];
[circleBL setCenter:CGPointMake(CGRectGetMinX(rectangle.frame), CGRectGetMaxY(rectangle.frame))];
[circleBR setCenter:CGPointMake(CGRectGetMaxX(rectangle.frame), CGRectGetMaxY(rectangle.frame))];
}
- (CGRect)selectedFrame
{
return rectangle.frame;
}
// Forward the background color.
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
// Set the container to clear.
[super setBackgroundColor:[UIColor clearColor]];
// Set our rectangle's color.
[rectangle setBackgroundColor:[backgroundColor colorWithAlphaComponent:0.5]];
for (UIView *handle in handles) {
[handle setBackgroundColor:backgroundColor];
}
}
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
// The handle we're moving.
UIView *touchedHandle = gesture.view;
// Keep track of touched Handles.
if (!touchedHandles) {
touchedHandles = [NSMutableArray array];
}
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
[touchedHandles addObject:touchedHandle];
break;
case UIGestureRecognizerStateChanged:
{
CGPoint tranlation = [gesture translationInView:self];
// Calculate this handle's new center
CGPoint newCenter = CGPointMake(touchedHandle.center.x + tranlation.x, touchedHandle.center.y + tranlation.y);
// Move corresponding circles
for (UIView *handle in handles) {
if (handle != touchedHandle && ![touchedHandles containsObject:handle]) {
// Match the handles horizontal movement
if (handle.center.x == touchedHandle.center.x) {
handle.center = CGPointMake(newCenter.x, handle.center.y);
}
// Match the handles vertical movement
if (handle.center.y == touchedHandle.center.y) {
handle.center = CGPointMake(handle.center.x, newCenter.y);
}
}
}
// Move this circle
[touchedHandle setCenter:newCenter];
// Adjust the Rectangle
// The origin and just be based on the Top Left handle.
float x = circleTL.center.x;
float y = circleTL.center.y;
// Get the width and height based on the difference between handles.
float width = abs(circleTR.center.x - circleTL.center.x);
float height = abs(circleBL.center.y - circleTL.center.y);
[rectangle setFrame:CGRectMake(x, y, width, height)];
[gesture setTranslation:CGPointZero inView:self];
}
break;
case UIGestureRecognizerStateEnded:
[touchedHandles removeObject:touchedHandle];
break;
default:
break;
}
}
#end
This is only a proof of concept. There are a lot of missing caveats like being able to drag outside the box, multitouch complications, negative sizes. All these problems can be handled very differently and are the secret sauce that makes something like this go from a nice idea to a beautiful custom interface. I'll leave that part up to you. :)

You will want to store the circle bezier paths in your class for when you implement gesture recognizers.
There is an Apple document describing how to implement a UIView or UIControl that accepts touch events with pictures and sample code.
http://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/multitouch_background/multitouch_background.html#//apple_ref/doc/uid/TP40009541-CH5-SW9

Related

How do I calculate the correct CGRect origin on a scaled UIView subview?

I need to calculate the visible CGRect of a UIView subview, in the coordinates of the original view. I've got it working if the scale is 1, but if one of the superviews or the view itself is scaled (pinch), the visible CGRect origin is offset slightly.
This works when the scale of the views is 1 or the view is a subview of the root view:
// return the part of the passed view that is visible
// TODO: figure out why result origin is wrong for scaled subviews
//
- (CGRect)getVisibleRect:(UIView *)view {
// get the root view controller (and it's view is vc.view)
UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;
// get the view's frame in the root view's coordinate system
CGRect frame = [vc.view convertRect:view.frame fromView:view.superview];
// get the intersection of the root view bounds and the passed view frame
CGRect intersection = CGRectIntersection(vc.view.bounds, frame);
// adjust the intersection coordinates thru any nested views
UIView *loopView = view;
do {
intersection = [loopView convertRect:intersection fromView:loopView.superview];
loopView = loopView.superview;
} while (loopView != vc.view);
return intersection; // may be same as the original view frame
}
When a subview is scaled, the size of the resultant view is correct, but the origin is offset by a small amount. It appears that the convertRect does not calculate the origin properly for scaled subviews.
I tried adjusting the origin relative to the X/Y transform scale but I could not get the calculation correct. Perhaps someone can help?
To save time, here is a complete test ViewController.m, where a box with an X is drawn on the visible part of the views - just create a reset button in the Main.storyboard and connect it to the reset method:
//
// ViewController.m
// VisibleViewDemo
//
// Copyright © 2018 ByteSlinger. All rights reserved.
//
#import "ViewController.h"
CG_INLINE void drawLine(UIView *view,CGPoint point1,CGPoint point2, UIColor *color, NSString *layerName) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:point1];
[path addLineToPoint:point2];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = color.CGColor;
shapeLayer.lineWidth = 2.0;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.name = layerName;
[view.layer addSublayer:shapeLayer];
}
CG_INLINE void removeShapeLayers(UIView *view,NSString *layerName) {
if (view.layer.sublayers.count > 0) {
for (CALayer *layer in [view.layer.sublayers copy]) {
if ([layer.name isEqualToString:layerName]) {
[layer removeFromSuperlayer];
}
}
}
}
CG_INLINE void drawXBox(UIView *view, CGRect rect,UIColor *color) {
NSString *layerName = #"xbox";
removeShapeLayers(view, layerName);
CGPoint topLeft = CGPointMake(rect.origin.x,rect.origin.y);
CGPoint topRight = CGPointMake(rect.origin.x + rect.size.width,rect.origin.y);
CGPoint bottomLeft = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height);
CGPoint bottomRight = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
drawLine(view,topLeft,topRight,color,layerName);
drawLine(view,topRight,bottomRight,color,layerName);
drawLine(view,topLeft,bottomLeft,color,layerName);
drawLine(view,bottomLeft,bottomRight,color,layerName);
drawLine(view,topLeft,bottomRight,color,layerName);
drawLine(view,topRight,bottomLeft,color,layerName);
}
#interface ViewController ()
#end
#implementation ViewController
UIView *view1;
UIView *view2;
UIView *view3;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CGFloat width = [UIScreen mainScreen].bounds.size.width / 2;
CGFloat height = [UIScreen mainScreen].bounds.size.height / 4;
view1 = [[UIView alloc] initWithFrame:CGRectMake(width / 2, height / 2, width, height)];
view1.backgroundColor = UIColor.yellowColor;
[self.view addSubview:view1];
[self addGestures:view1];
view2 = [[UIView alloc] initWithFrame:CGRectMake(width / 2, height / 2 + height + 16, width, height)];
view2.backgroundColor = UIColor.greenColor;
[self.view addSubview:view2];
[self addGestures:view2];
view3 = [[UIView alloc] initWithFrame:CGRectMake(10, 10, width / 2, height / 2)];
view3.backgroundColor = [UIColor.blueColor colorWithAlphaComponent:0.5];
[view1 addSubview:view3]; // this one will behave differently
[self addGestures:view3];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self checkOnScreen:view1];
[self checkOnScreen:view2];
[self checkOnScreen:view3];
}
- (IBAction)reset:(id)sender {
view1.transform = CGAffineTransformIdentity;
view2.transform = CGAffineTransformIdentity;
view3.transform = CGAffineTransformIdentity;
[self.view setNeedsLayout];
}
- (void)addGestures:(UIView *)view {
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:#selector(handlePan:)];
[view addGestureRecognizer:panGestureRecognizer];
UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc]
initWithTarget:self action:#selector(handlePinch:)];
[view addGestureRecognizer:pinchGestureRecognizer];
}
// return the part of the passed view that is visible
- (CGRect)getVisibleRect:(UIView *)view {
// get the root view controller (and it's view is vc.view)
UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;
// get the view's frame in the root view's coordinate system
CGRect frame = [vc.view convertRect:view.frame fromView:view.superview];
// get the intersection of the root view bounds and the passed view frame
CGRect intersection = CGRectIntersection(vc.view.bounds, frame);
// adjust the intersection coordinates thru any nested views
UIView *loopView = view;
do {
intersection = [loopView convertRect:intersection fromView:loopView.superview];
loopView = loopView.superview;
} while (loopView != vc.view);
return intersection; // may be same as the original view
}
- (void)checkOnScreen:(UIView *)view {
CGRect visibleRect = [self getVisibleRect:view];
if (CGRectEqualToRect(visibleRect, CGRectNull)) {
visibleRect = CGRectZero;
}
drawXBox(view,visibleRect,UIColor.blackColor);
}
//
// Pinch (resize) an image on the ViewController View
//
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer {
static CGAffineTransform initialTransform;
if (recognizer.state == UIGestureRecognizerStateBegan) {
[self.view bringSubviewToFront:recognizer.view];
initialTransform = recognizer.view.transform;
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
} else {
recognizer.view.transform = CGAffineTransformScale(initialTransform,recognizer.scale,recognizer.scale);
[self checkOnScreen:recognizer.view];
[self.view setNeedsLayout]; // update subviews
}
}
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
static CGAffineTransform initialTransform;
if (recognizer.state == UIGestureRecognizerStateBegan) {
[self.view bringSubviewToFront:recognizer.view];
initialTransform = recognizer.view.transform;
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
} else {
//get the translation amount in x,y
CGPoint translation = [recognizer translationInView:recognizer.view];
recognizer.view.transform = CGAffineTransformTranslate(initialTransform,translation.x,translation.y);
[self checkOnScreen:recognizer.view];
[self.view setNeedsLayout]; // update subviews
}
}
#end
So you need to know the real visible frame of a view that is somehow derived from bounds+center+transform and calculate everything else from that, instead of the ordinary frame value. This means you'll also have to recreate convertRect:fromView: to be based on that. I always sidestepped the problem by using transform only for short animations where such calculations are not necessary. Thinking about coding such a -getVisibleRect: method makes me want to run away screaming ;)
What is a frame?
The frame property is derived from center and bounds.
Example:
center is (60,50)
bounds is (0,0,100,100)
=> frame is (10,0,100,100)
Now you change the frame to (10,20,100,100). Because the size of the view did not change, this results only in a change to the center. The new center is now (60,70).
How about transform?
Say you now transform the view, by scaling it to 50%.
=> the view has now half the size than before, while still keeping the same center. It looks like the new frame is (35,45,50,50). However the real result is:
center is still (60,50): this is expected
bounds is still (0,0,100,100): this should be expected too
frame is still (10,20,100,100): this is somewhat counterintuitive
frame is a calculated property, and it doesn't care at all about the current transform. This means that the value of the frame is meaningless whenever transform is not the identity transform. This is even documented behaviour. Apple calls the value of frame to be "undefined" in this case.
Consequences
This has the additional consequences that methods such as convertRect:fromView: do not work properly when there are non-standard transforms involved. This is because all these methods rely on either frame or bounds of views, and they break as soon as there are transforms involved.
What can be done?
Say you have three views:
view1 (no transform)
view2 (scale transform 50%)
view3 (no transform)
and you want to know the coordinates of view3 from the point of view of view1.
From the point of view of view2, view3 has frame view3.frame. Easy.
From the point of view of view1, view2 has not frame view2.frame, but the visible frame is a rectangle with size view2.bounds/2 and center view2.center.
To get this right you need some basic linear algebra (with matrix multiplications). (And don't forget the anchorPoint..)
I hope it helps..
What can be done for real?
In your question you said that there is an offset. Maybe you can just calculate the error now? The error should be something like 0.5 * (1-scale) * (bounds.size) . If you can calculate the error, you can subtract it and call it a day :)
Thanks to #Michael for putting in so much effort in his answer. It didn't solve the problem but it made me think some more and try some other things.
And voila, I tried something that I'm certain I had done before, but this time I started with my latest code. It turns out a simple solution did the trick. The builtin UIView convertRect:fromView and convertRect:toView worked as expected when used together.
I apologize to anyone that has spent time on this. I'm humbled in my foolishness and how much time I have spent on this. I must have made a mistake somewhere when I tried this before because it didn't work. But this works very well now:
// return the part of the passed view that is visible
- (CGRect)getVisibleRect:(UIView *)view {
// get the root view controller (and it's view is vc.view)
UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;
// get the view's frame in the root view's coordinate system
CGRect rootRect = [vc.view convertRect:view.frame fromView:view.superview];
// get the intersection of the root view bounds and the passed view frame
CGRect rootVisible = CGRectIntersection(vc.view.bounds, rootRect);
// convert the rect back to the initial view's coordinate system
CGRect visible = [view convertRect:rootVisible fromView:vc.view];
return visible; // may be same as the original view frame
}
If someone uses the Viewcontroller.m from my question, just replace the getVisibleRect method with this one and it will work very nicely.
NOTE: I tried rotating the view and the visible rect is rotated too because I displayed it on the view itself. I guess I could reverse whatever the view rotation is on the shape layers, but that's for another day!

How to add a gesture recognizer to a shape drawn by uibezierpath

I am drawing a circle in the drawRect function in a subclass of UIView
- (void)drawRect:(CGRect)rect
{
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(contextRef, 2.0);
CGContextSetRGBFillColor(contextRef, 0, 0, 1.0, 1.0);
CGContextSetRGBStrokeColor(contextRef, 0, 0, 1.0, 1.0);
CGRect circlePoint = (CGRectMake(self.bounds.size.width/3, self.bounds.size.height/2, 200.0, 200.0));
CGContextFillEllipseInRect(contextRef, circlePoint);
}
I want to add a gesture recognizer to the circle to make it tappable
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSingleTap:)];
[self.view addGestureRecognizer:singleFingerTap];
I thought of dragging a UIGestureRecognizer onto the view (in storyboard) in the location where the big circle will be, but the circle is much bigger than the UIGestureRecognizer widget.
How can I either combine the code or assign a UIGestureRecognizer to an area of the view that's exactly the same as the size and location of the circle?
The short answer is that you can't. Gesture recognizers are attached to views, not shapes or layers. You would have to create a custom view object for each shape. You could certainly do that.
What I suggest you do is to create a custom subclass of UIView that manages all your shapes. (I'll call it ShapesView) Have that custom ShapesView manage an array of custom shape objects. Attach a gesture recognizer to your ShapesView. In the code that responds to gestures, have it do custom hit testing to determine which shape was tapped, and move the shapes around.
UIBezierPath includes a containsPoint method that would allow you to do hit testing if you maintained a bezier path for each shape.
I'm not sure how to do it using drawRect the way you are, but I've done something similar using UIBezierPath. I subclassed UIView, and made this view the main view of my controller. This is the code in that view,
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
self.shape = [UIBezierPath bezierPathWithOvalInRect:(CGRectMake(self.bounds.size.width/3, self.bounds.size.height/3, 200.0, 200.0))];
}
return self;
}
-(void)drawRect:(CGRect)rect {
[[UIColor blueColor] setFill];
[self.shape fill];
}
shape is a property declared in the .h file. In the view controller .m file, I add the gesture recognizer, and check if the touch is inside the shape,
#interface ViewController ()
#property (strong,nonatomic) RDView *mainView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.mainView = (RDView *)self.view;
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[self.view addGestureRecognizer:singleFingerTap];
}
-(void)handleSingleTap:(UITapGestureRecognizer *) tapper {
if ([self.mainView.shape containsPoint:[tapper locationInView:self.mainView]]) {
NSLog(#"tapped");
}
}

Lines drawn using drawRect method not getting scrolled

I am creating an app where I want to draw lines on scrollView. I am able to draw lines.
Here is my code
#interface GraphOnScrollView : UIScrollView
#property(strong,nonatomic)NSMutableArray *intensityArray;
#end
import "GraphOnScrollView.h"
#implementation GraphOnScrollView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.userInteractionEnabled=YES;
self.scrollEnabled=YES;
UIButton *DirectMsgBtn1 =[UIButton buttonWithType:UIButtonTypeCustom];
DirectMsgBtn1.titleLabel.font =[UIFont systemFontOfSize:12.0];
[DirectMsgBtn1 setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[DirectMsgBtn1 setTitle:#"direct message" forState:UIControlStateNormal];
[DirectMsgBtn1 setBackgroundColor:[UIColor clearColor]];
[DirectMsgBtn1 addTarget:self
action:#selector(DirectMessageViewPopUp:)
forControlEvents:UIControlEventTouchDown];
DirectMsgBtn1.frame = CGRectMake(0.0, 0, 100, 30);
[self addSubview:DirectMsgBtn1];
DirectMsgBtn1 = nil;
}
return self; }
// Only override drawRect: if you perform custom drawing. // An
empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGFloat y_Axix =20.0;
CGFloat lineWidth=1.0;
for (int i=0; i<[self.intensityArray count]; i++)
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(c, 2.0);
lineWidth+=1;
CGFloat red[4] = {1.0f, 0.5f, 0.0f, 1.0f};
CGContextSetStrokeColor(c, red);
CGContextBeginPath(c);
CGFloat width=[[self.intensityArray objectAtIndex:i] floatValue];
CGContextMoveToPoint(c, 5.0f, y_Axix);
CGContextAddLineToPoint(c, width, y_Axix);
CGContextStrokePath(c);
CGContextAddArc(c,width,y_Axix,1.0f,0,2*3.1415926535898,1);
CGContextDrawPath(c,kCGPathStroke);
y_Axix=y_Axix+50;
}
NSLog(#"intensity array %#", self.intensityArray);
self.contentSize = CGSizeMake(self.frame.size.width, 1000);
}
This is code I am using for adding scrollview on my view
GraphOnScrollView *GraphView =[[GraphOnScrollView
alloc]initWithFrame:CGRectMake(0.0, 150.0, 320.0, 280.0)];
GraphView.backgroundColor =[UIColor whiteColor];
GraphView.intensityArray =[NSMutableArray arrayWithObjects:#"60",#"100",#"40",#"10",#"20",#"40",#"40",#"100",
nil];
[self.view addSubview:GraphView];
Using this code the lines which i have drawn is not getting scrolled but the scrollview is scrolled. I don't know what is the problem.
Thanks
While drawing on any view it draws everything on the canvas of that view.
In the case of your, you are drawing on UIScrollView so all drawing performs on the canvas of the UIScrollView, so it is not scrollable.
To solve that problem you can create on separate UIView with the size you want, perform any drawing on it and add that view in scrollview. I think this will be the best solution.
You can also refer to this link for some more help.
Alternate Approach (i actually used uiview for drawing and made it look like scroll)
save the touch location in
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
record the touch point in touches moved.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}
now you can translate coordinate of your points as
newX=x+(touchstart.x-touchMoved.x);//do this for x of every point
newy=y+(touchstart.y-touchMoved.y);// do this for y of every point
put the above lines in a function and call them from touches moved you can do it in touches ended method as well if lag is ok with you.
Point is translate the point and calculate the degree of translation from user touch began and moved or swipe

DrawRect not displaying shape?

I am trying to implement two subclasses at one time.
I am using the UIViewController <CLLocationManagerDelegate> subclass in my viewController to be able to work with the gps. While using UIView subclass to draw.
This is a summary of the code I have:
MapViewController.h:
#interface MapViewController: UIViewController <CLLocationManagerDelegate>{
DrawCircle *circleView;
}
#property (nonatomic, retain) DrawCircle *circleView;
#end
DrawCircle.h:
#interface DrawCircle : UIView
-(void)addPoint:(CGPoint)point;
#end
What I was trying to do here was to make DrawCircle a property of MapViewController. However I am still having no luck drawing anything to the screen.
Here is my DrawCircle.m code if it helps at all:
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if(self) {
_points = [[NSMutableArray alloc] init];
}
return self;
}
-(void)addPoint:(CGPoint)point {
//Wrap the point in an NSValue to add it to the array
[_points addObject:[NSValue valueWithCGPoint:point]];
//This will tell our view to redraw itself, calling drawRect
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
for(NSValue *pointValue in _points) {
CGPoint point = [pointValue CGPointValue];
CGContextAddEllipseInRect(ctx, CGRectMake(point.x - 10, point.y - 10, 20, 20));
}
CGContextSetFillColor(ctx, CGColorGetComponents([[UIColor redColor] CGColor]));
CGContextFillPath(ctx);
}
Also as a last bit of information I can give to you all. Here is a snapshot of my map view controller scene.
I have a feeling that the shape is being drawn, but being covered up somehow.
EDIT
After further investigation, it appears that the shapes being drawn are being covered by my UIImage. I was just messing around and removed the UIImage, and my shapes were there. Now the question is, how do i "move to back" my UIImage, so that my shapes are up front?

Static subview background image effect

In the latest Expedia app for iOS, they have a very interesting effect that I am trying to wrap my head around. The have two columns of infinitely scrolling subviews which I know can be accomplished with 2 scrollviews. The interesting part is that the overall scrollview appears to have a linen background that stays static and can be seen in the gap between each of the subview cells. The really cool part is that the subviews have a different background that stays in place. In the screenshot below it is city skyline image. When the subviews scroll, the city image can only be seen behind the subview cells. It appears to be some sort of masking trick but I can't quite figure out how the effect is done. How can I achieve the same result?
Essentially, how can you show a static background behind subviews that act as little windows and not show the linen. The linen should only be shown around the cells.
You can download the app, hit airplane mode and try it for yourself.
Here is a screenshot:
Here is another to show that the cells scrolled but the city stays the same:
I'd like to found an elegant solution, for now I would do it by tracking the visible subviews offset and configuring their appearance.
Please check the result at sample project.
For the future reference I'll attach the code below:
ViewController.m
//
// OSViewController.m
// ScrollMasks
//
// Created by #%$^Q& on 11/30/12.
// Copyright (c) 2012 Demo. All rights reserved.
//
#import "OSViewController.h"
#interface OSViewController ()
// subviews
#property (strong) IBOutlet UIScrollView * scrollView;
// all the subviews
#property (strong) NSArray * maskedSubviews;
// subviews visible at scrollview, we'll update only them
#property (strong) NSArray * visibleMaskedSubviews;
// updates the views from visibleMaskedSubviews
-(void) updateVisibleSubviews;
// updates the visibleMaskedSubviews array with the given scrollView offset
-(void) updateVisibleSubviewsArrayForOffset:(CGPoint) offset;
#end
#implementation OSViewController
-(void) unused {}
#pragma mark - view
-(void) viewWillAppear:(BOOL)animated {
[self updateVisibleSubviews];
[super viewWillAppear:animated];
}
- (void)viewDidLoad
{
[super viewDidLoad];
/*
See -updateVisibleSubviews comment for the class comments
*/
UIView * newMaskedView = nil;
NSMutableArray * newMaskedSubviews = [NSMutableArray array];
const CGSize scrollViewSize = self.scrollView.bounds.size;
const int totalSubviews = 10;
const float offset = 20.;
const float height = 100.;
UIImage * maskImage = [UIImage imageNamed:#"PeeringFrog.jpg"];
/*
// Uncomment to compare
UIImageView * iv = [[UIImageView alloc] initWithFrame:self.scrollView.bounds];
iv.image = maskImage;
[self.view insertSubview:iv atIndex:0];
*/
// add scrollview subviews
for (int i = 0; i < totalSubviews; i++) {
CGRect newViewFrame = CGRectMake(offset, offset*(i+1) + height*i, scrollViewSize.width - offset*2, height);
newMaskedView = [UIView new];
newMaskedView.frame = newViewFrame;
newMaskedView.clipsToBounds = YES;
newMaskedView.backgroundColor = [UIColor redColor];
newMaskedView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
UIImageView * maskImageView = [UIImageView new];
maskImageView.frame = CGRectMake(0, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
maskImageView.image = maskImage;
[newMaskedView addSubview:maskImageView];
[self.scrollView addSubview:newMaskedView];
[newMaskedSubviews addObject:newMaskedView];
}
self.scrollView.contentSize = CGSizeMake(scrollViewSize.width, (height+offset)*totalSubviews + offset*2);
self.maskedSubviews = [NSArray arrayWithArray:newMaskedSubviews];
[self updateVisibleSubviewsArrayForOffset:self.scrollView.contentOffset];
}
-(void) updateVisibleSubviews {
[self updateVisibleSubviewsArrayForOffset:self.scrollView.contentOffset];
for (UIView * view in self.visibleMaskedSubviews) {
/*
TODO:
view must be UIView subclass with the imageView initializer and imageView frame update method
*/
CGPoint viewOffset = [self.view convertPoint:CGPointZero fromView:view];
UIImageView * subview = [[view subviews] objectAtIndex:0];
CGRect subviewFrame = subview.frame;
subviewFrame = CGRectMake(-viewOffset.x, -viewOffset.y, subviewFrame.size.width, subviewFrame.size.height);
subview.frame = subviewFrame;
}
}
#pragma mark - scrollview delegate & utilities
-(void) scrollViewDidScroll:(UIScrollView *)scrollView {
[self updateVisibleSubviews];
}
-(void) updateVisibleSubviewsArrayForOffset:(CGPoint) offset {
NSMutableArray * newVisibleMaskedSubviews = [NSMutableArray array];
for (UIView * view in self.maskedSubviews) {
CGRect intersectionRect = CGRectIntersection(view.frame, self.scrollView.bounds);
if (NO == CGRectIsNull(intersectionRect)) {
[newVisibleMaskedSubviews addObject:view];
}
}
self.visibleMaskedSubviews = [NSArray arrayWithArray:newVisibleMaskedSubviews];
}
#pragma mark - memory
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
ViewController.h
//
// OSViewController.h
// ScrollMasks
//
// Created by #%$^Q& on 11/30/12.
// Copyright (c) 2012 Demo. All rights reserved.
//
/*
PeeringFrog image is taken (and resized) from Apple sample project "PhotoScroller"
*/
#import <UIKit/UIKit.h>
#interface OSViewController : UIViewController <UIScrollViewDelegate>
#end
I did something similar a few years ago. At first I tried using the CGImageMaskCreate stuff, but found it far easier just to create an image that had transparent "cutouts" and then used animation effects to scroll the picture(s) under it.
For your case, I'd find an image of linen the size of the screen. Then I'd use a image editor (I use GIMP) to draw some number of boxes on the linen using a flat color. Then I'd map that box color to transparent to make the cutouts. There's other ways to do this, but that's the way I do it.
In your app, add two or more image views to the main view. Don't worry about the placement because that will be determined at run time. You'll want to set these image views to contain the images you want to have "scroll" under. Then add your linen-with-cutouts UIImageView so it's on top and it's occupying the entire screen size. Make sure that the top UIImageView's background is set to transparent.
When the app starts, layout your "underneath" imageviews, top to bottom, and then start a [UIView beginAnimation] that scrolls your underneath images views up by modifying the "y" position. This animation should have a done callback that gets called when the top image view is completely off the screen. Then, in the done callback, layout the current state and start the animation again. Here's the guts of the code I used (but note, my scrolling was right to left, not bottom to top and my images were all the same size.)
- (void)doAnimationSet
{
[iv1 setFrame:CGRectMake(0, 0, imageWidth, imageHeight)];
[iv2 setFrame:CGRectMake(imageWidth, 0, imageWidth, imageHeight)];
[iv3 setFrame:CGRectMake(imageWidth*2, 0, imageWidth, imageHeight)];
[self loadNextImageSet];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:10];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[iv1 setFrame:CGRectMake(-imageWidth, 0, imageWidth, imageHeight)];
[iv2 setFrame:CGRectMake(0, 0, imageWidth, imageHeight)];
[iv3 setFrame:CGRectMake(imageWidth, 0, imageWidth, imageHeight)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(doneAnimation:finished:context:)];
[UIView commitAnimations];
}
- (void)doneAnimation:(NSString *)aid finished:(BOOL)fin context:(void *)context
{
[self doAnimationSet];
}
This should give you the effect that you are looking for. Good luck :)

Resources