How to cut out or disable a section of a UIView? - ios

I have a custom subclass of UIView, LineDrawingView. I have this as a #property in my main view controller and I add it to the view. This works fine - it creates a transparent view on top of the current view and uses UIBezierPath, touchesBegan, touchesMoved etc so you can draw all over the view by dragging your finger around.
I need to make that drawing view "L" shaped, so I can have an area in the bottom left corner where various controls are located. I can think of no way to make the drawing view "L" shaped, except maybe to add two separate rectangular drawing views, but this doesn't work because touches are interrupted when you drag your finger from one rectangle into the other. The other solution I've come up with is to add another view on top of the drawing view. This view should prevent drawing and I could also locate my controls within it so they are still usable while drawing is enabled.
I tried creating a UIView and adding it as a subview of the drawing view. I gave it a tint so I could check it was present and in the right place. I expected this to prevent drawing within the area of the new UIView, but drawing continues all over the area of the LineDrawingView. I also tried inserting the new UIView at index 2, with the LineDrawingView inserted at index 1. It still didn't affect the drawing.
self.drawView = [[LineDrawingView alloc] initWithFrame:CGRectMake(0, 50, 768, 905)];
[self.drawView setBackgroundColor:[UIColor clearColor]];
// not effective in preventing drawing!!
UIView *controlView = [[UIView alloc] initWithFrame:CGRectMake(0, 530, 310, 575)];
[controlView setUserInteractionEnabled:NO];
[controlView setBackgroundColor:[UIColor grayColor]];
[controlView setAlpha:0.3];
[self.view addSubview:drawView];
[self.drawView addSubview:controlView];
I would love to know: how can I either...
Create an "L" shaped drawing view?
OR
Cut out a section of the drawing view so users can interact with what is behind it?
OR
Impose an area on top of the drawing view where I can disable drawing and add my controls?

Here is a tutorial on creating a transparent rounded rectangle UIView, which I think you could modify in a straightforward manner to make it L shaped.
The most important thing to keep in mind that you'll have to implement your own drawRect and I believe also your own hitTest or touches (e.g. event handling like touchedBegan:withEvent:) methods.

I contacted Apple about this. It turns out there is not a way to create an "L" shaped view. The solution was to, in the drawing view, test whether touches are within the forbidden area and, if so, prevent the line from being drawn. My controls have been moved to a separate UIView which is moved to the front when drawing is enabled. This means the controls remain active the whole time and drawing cannot take place within the area of the controls.

if you want to draw an L shape drawing view you can do this by concatenating two lines only .. just like i have created an arrow .. and if you want to stop drawing while you are moving (dragging ) any object .. you just need to apply a check in your touches moved method (like is moved , )..
here is the code to draw an arrow (you can use this as a reference to draw any kind of shape) : How can I draw an arrow using Core Graphics?
code to check if you have hit the path (means your touch point is in the bezier path )
if([[self tapTargetForPath:((UIBezierPath *)[testDict objectForKey:#"path"])] containsPoint:startPoint])// if starting touch is in bezierpath
{
ishitInPath = YES;
isMoving = YES;
}
// to easily detect (or select a bezier path object)
- (UIBezierPath *)tapTargetForPath:(UIBezierPath *)path
{
if (path == nil) {
return nil;
}
CGPathRef tapTargetPath = CGPathCreateCopyByStrokingPath(path.CGPath, NULL, fmaxf(35.0f, path.lineWidth), path.lineCapStyle, path.lineJoinStyle, path.miterLimit);
if (tapTargetPath == NULL) {
return nil;
}
UIBezierPath *tapTarget = [UIBezierPath bezierPathWithCGPath:tapTargetPath];
CGPathRelease(tapTargetPath);
return tapTarget;
}

Related

Drawing geometry (circle or rect) on zoomable CGPDF document using QuartzCore

I am trying to draw on touches (mouse tap) on the pdf document generated by CGContextRef from my resource bundle. I have started from Apple zoomingpdfviewer
where I have a UIScrollView and Pdf is generated by CGContextDrawPDFPage(context, pdfPage);
I am have also added a CATiledLayer on the scrollView and it is drawn each time in layoutSubviews - when UIScrollView is zoomed. I am confused a bit that should I draw the mouse points on scrollView CGContext or TileLayers.
--> Moving ahead, I wanted to add a rect/ circle/point on the pdf document where the user taps. Just to start with I am : CGContextMoveToPoint(context, 5,5);
CGContextSetLineWidth(context, 12.0f);
CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
CGContextAddRect(context, CGRectMake(50, 50, 20, 50));
CGContextAddLineToPoint(context, 25, 5);
CGContextStrokePath(context);
drawing it to the current context. Similarly I plan to draw when user taps in. But when I zoom and TitledLayer is redrawn to match the zoom content, these drawn rect/ circle disappears. If I am not wrong then CATiledLayer is being drawn above the currentContext rects/ circle . The end functionality is quite similar to the Maps App where the Tile Layers are added but the dropped points are located exactly on the same location even after the map is zoomed. Quite lost after seeing many such posts as drawing on scrollView , pdf iOS viewer. Does anyone know how can I draw geomtry (rect/points) on the pdf document and keep their location exactly same even if PdfView Zoom in and out? Should I convert a pdf into image or any other way to do this?
After searching and trying some stuff on CoreAnimation,CALayer I came up with:
If you want to add anything on zoomable PDF viewer,
1) Add a UITapGestureRecognizer for handling single touch
UITapGestureRecognizer *singleTapGestureRecognizer=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTapOnScrollView:)];
singleTapGestureRecognizer.numberOfTapsRequired=1;
singleTapGestureRecognizer.delegate=self;
[self addGestureRecognizer:singleTapGestureRecognizer];
[singleTapGestureRecognizer requireGestureRecognizerToFail:doubleTapGestureRecognizer]; // added if you have a UITapGestureRecognizer for double taps
2) Simply add the new views (circle,rect any geometry) as layers on the Tiled PDF view
[self.titledPdfView.layer addSublayer:view1.layer];
where you can subclass your geometry views to draw with CGContext currentContext. This automatically reposition the layers when pdf is zoomed in/out
I was trying to add the views by drawing context on drawLayer:(CALayer*)layer inContext:(CGContextRef)context of titledPdfView class which gave a positional error
You can also implement a TouchesMoved, TouchesEnded method if you want to draw a resizable TextView, arow, Line.
I hope this helps for someone who needs to draw customize objects on Apple's ZoomingPDFViewer.
Further to add (if anyone arrives here), replace the Apple code of TiledPdfView *titledPDFView = [[TiledPdfView alloc] initWithFrame:pageRect scale:pdfScale];
[titledPDFView setPage:pdfPage];
[self addSubview:titledPdfView];
[titledPDFView setNeedsDisplay];
[self bringSubviewToFront:titledPdfView];
self.titledPdfView = titledPDFView;
With :
titledPdfView = [[TiledPdfView alloc] initWithFrame:pageRect scale:pdfScale];
[titledPdfView setPage:pdfPage];
[self addSubview:titledPdfView];
[self bringSubviewToFront:titledPdfView];
I dont know why have they added a view like that (member object to class object) which restricts the -(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context being before zooming. I have tried with setNeedsDisplay with no effect then replaced n it works. Esp. this is annoying if you want to draw annots on your TileLayer and nothing appears. Hope this helps!!

Drawing inside a subview of a UIView subclass

I have more of a generic question. I have a custom class that subclasses UIView. I override '-drawRect' to draw points in a line which I successfully did. Now I declare another UIView object inside my class that I want to animate. It should be a white circle outline which I'd like to move with an animation and this is why I want it to be of UIView type. But how can I draw inside it? What if I have 3 other similar cases for example? So my point is, I want to keep all of the functionality inside the component's class and have custom drawing for one or more UIView subviews. I think I am missing something on conceptional level.
In ActionScript 3 I would do it so:
var first:MovieClip = new MovieClip();
first.graphics...
......
var nTh:MovieClip = new MovieClip();
nTh.graphics...
This is what I want to achieve (as I said I've already made the drawing of the dots inside -drawRect, now I want to make the bigger circle which has to be animated later):
What I did is creating an UIView instance and setting its bounds and position in -initWithFrame:
//circleFrameSideSize will serve as the diameter of the circle
UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(0, circleYPos, circleFrameSideSize, circleFrameSideSize)];
circle.layer.borderColor = [UIColor whiteColor].CGColor;
circle.layer.borderWidth = 1;
//here we do the trick for making the circle with using layer's corner radius
circle.layer.cornerRadius = circleFrameSideSize/2;
[self addSubview:circle];

iOS: How to catch a tap event on a marker on a map that has a CALayer on top of it?

I have a mapView (RouteMe mapView) on which there are annotations (markers).
On the mapView there is a touchesEnded function on which I usually catch all events.
Some markers have an added layer on top of them.
That layer has some animation images and as much as I know this is the only way I can show these animation images on top of the markers.
The problem is that I don't know how I can intercept a touch on a marker that has that layer on top of it. When I test the hit on the touchesEnded I recognize a CALayer class rather than a RMMArker class (obv0iously, because the layer is on top of the marker, therefore is first to intercept the event).
How can I reach the Marker once the top CALayer is tapped?
Thanks
Hackish workaround: Create an RMMapLayer instead of a CALayer. Remember to set the annotation on the sublayer to get things like callouts to work, e.g. in your RMMapLayer subclass:
RMMapLayer *sublayer = [[RMMapLayer alloc] init];
sublayer.annotation = ann;
sublayer.contents = (id)img2.CGImage;
sublayer.contentsScale = img2.scale;
sublayer.position = CGPointMake(CGRectGetWidth(self.bounds)/2, CGRectGetHeight(self.bounds)/2);
sublayer.bounds = CGRectMake(0, 0, img2.size.width, img2.size.height);
[self addSublayer:sublayer];
I don't know how many other things this has potential for breaking though, so you can follow this issue for any updates:
https://github.com/mapbox/mapbox-ios-sdk/issues/190
You can instruct the touches to passthrough. Take a look at this question: How can I click a button behind a transparent UIView?
Since the CALayer was on top of a CALAyer, all it took was to access it ancestor blockingLayer.superlayer.
By checking on the ancestor, I could have all that I needed.
This solved the problem.

iPad app - Drawing objects on the screen one at a time

What is the best way to go about making an app that does the following: the user taps at locations, and on the locations, a square is drawn. There is no way to erase the squares. You simply tap wherever you want and squares of a predefined size are drawn.
I was thinking to make a custom UIView and override the drawRect method by keeping a list of all the (x,y) locations of the squares and then calling [customView setNeedsDisplay] and drawing all the squares everytime a new square is drawn.
Is there a better way?
In Java, I would use an offscreen image, draw the square onto the offscreen image, and then draw the image onto the screen on every repaint() call. But, is this good to do for iPad? If so, what is code that will let me initialize a UIImage and draw a square onto it?
You could just add an empty UIView, which you only set the background color on, and then add it to the view you are tapping.
Example of adding a yellow square in the viewcontroller's viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *square = [[UIView alloc] initWithFrame:CGRectMake(30, 30, 30, 30)];
square.backgroundColor = [UIColor yellowColor];
[self.view addSubview:square];
[square release];
}
Note: You can also give each square an id in its tag and later retrieve them with viewWithTag: from their superview (i.e. self.view from the controller)

Fill In UIView With Multiple Colors iOS

I'd like to fill in a UIView Background with multiple colors. I want to use it as a status bar of sorts, so if 1/2 the necessary steps are completed, the UIView Background will be 1/2 green and 1/2 red. When the user completes more steps (say 2/3), more of the UIView Background turns green (2/3 in this case).
I'm guessing I need to override
-(void) drawREct: (CGRect) rect
I imagine I would get the UIView, figure out how big it is, and divide it into 2 rectangles and then fill in those rectangles.
Another option would be to add 2 UIViews programmatically, but I'm a fan of IB.
Is it possible to divy up a UIView like I want?
Thanks
This is not an IB solution, but you can subclass UIView and override the drawRect method to add a custom gradient. This will allow you to put any number of colors you like and have them transition hard or smoothly. There are many nice tutorials online that should different ways to do this (some more elaborate, some quite simple).
Another option that is fairly simple, is to override drawRect, make the background red, then fill a rectangle that takes up the bottom half of the view. It doesn't allow for fancy or smooth transitions between colors, but it's very easy to implement. For instance, something along these lines should work:
-(void)drawRect:(CGRect)rect {
CGRect upperRect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height * percentDone);
CGRect lowerRect = CGRectMake(rect.origin.x, rect.origin.y + (rect.size.height * percentDone), rect.size.width, rect.size.height *(1-percentDone));
[[UIColor redColor] set];
UIRectFill(upperRect);
[[UIColor greenColor] set];
UIRectFill(lowerRect);
}
Here percentDone is a float (declare, property(nonatomic), synthesize) that you can tie to the user's steps. Then just update the view when the user does something by
splitView.percentDone = .5;
[splitView setNeedsDisplay];
You can smooth this out with animations as well.
An easy way would be to set the background color for your background view to, say, red. Then add another view to that background view and call that the indicator view. Size and position the indicator view to cover the background, and set its background color to green. Connect the indicator view to an outlet in your view controller, and have the view controller adjust its width (or height) as necessary to correspond with the progress of the task at hand.
Another way would be as #PengOne suggests: create a custom view, give it a 'progress' property, and override -drawRect: to draw the contents appropriately. If you're going this route, there's nothing to stop you from getting a little creative. Instead of just filling two rectangles, make the boundary between the two colors a little more interesting. You could add ripples or bubbles that, with an appropriate sound effect, might look like a container filling with liquid. Or you could do a Qix-like animation that slowly fills the screen... ;-)

Resources