Finger Painting from a UIView Class to Another UIView Class - ios

I have a UIViewController Class which holds 2 custom UIView classes which are:
ItemView
DrawingView
in UIViewController ViewDidLoad method I have the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
item = [[ItemView alloc] initWithFrame:CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/2, 230, 230)];
item.opaque = NO;
[self.view addSubview:item];
drawing = [[DrawingView alloc] initWithFrame:self.view.frame];
[self.view insertSubview:drawing aboveSubview:item];
[item release];
[drawing release];
}
The DrawingView class gets UITouches and accordingly draw on the screen.
My question is:
I can draw all over the screen except for on top of ItemView class object. DrawingView class cannot draw on to ItemView.
Let me explain it in another word:
The DrawView class works on the screen except for the area of itemView subview. On top of itemView, DrawView cannot make any draws but other than itemView area, it makes drawing.
What can I do?
What can be the problem?
How can I make drawing on all screen including the itemView area added by addSubview on ViewController class?
EDIT: I am adding a screenshot in order to explain the problem better
EDIT 2: I found that the problem is related to opacity. In the ItemView class, I changed the added UIImageView object alpha value to 0.5f.
The result is semi-transparent view and now my finger drawing is visible. However, this is not what I want. I want to draw on top of the view. I do not want to play with the alpha value.

Finally, I found the answer myself. It is not exactly what I wanted but my solution is based on CALayer.
Instead of using a custom UIView class like ItemView I added the following class method in ItemView Class:
+(CALayer *)imgLayer
{
UIImage *img = [UIImage imageNamed:#"face.png"];
CALayer *layer = [CALayer layer];
CGFloat nativeWidth = CGImageGetWidth(img.CGImage);
CGFloat nativeHeight = CGImageGetHeight(img.CGImage);
CGRect startFrame = CGRectMake(0, 0, nativeWidth, nativeHeight);
layer.contents = (id)img.CGImage;
layer.frame = startFrame;
return layer;
}
In My UIVIewController Class I called my static function as following:
CAGradientLayer *bgLayer = [GradientView greyGradient];
bgLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:bgLayer atIndex:0];
CALayer *imgLayer =[ItemView imgLayer];
imgLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:imgLayer above:bgLayer];
And it works! now I can make drawings on my image.

Related

How can I add a gradient that spans two views?

I know how to do (1) but how can I do (2)?
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 50)];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = #[(id)[UIColor blueColor].CGColor, (id)[UIColor redColor].CGColor];
[view.layer insertSublayer:gradient atIndex:0];
There are several ways you could do this. Here's one way:
Create a UIView subclass named GradientView to manage the gradient layer. This is helpful because it means you can use the normal UIKit techniques to manage the gradient layout (auto layout constraints, autoresizing masks, UIKit animations).
For each view that should participate in the common gradient, add a GradientView subview. Set up each GradientView's colors, locations, and start and end points identically.
For each view that should participate in the common gradient, turn on clipsToBounds.
Use auto layout constraints to make each GradientView span all of the participating superviews. (It's important to understand that constraints can cross superview/subview boundaries).
With this approach, auto layout takes care of making the gradient cover all of the views even if they change size or move around. For example, you won't have to do anything special to make the gradients animate nicely when the user rotates the device.
Thus, for your two-view example, I'm proposing that you set up a view hierarchy like this:
In the view debugger screenshot above, I disabled clipping. You can see that the two gradient views have identical gradients and share the same screen space. The topGradient is a subview of topView and bottomGradient is a subview of bottomView.
If we turn clipping on, you'll only see the part of topGradient that fits inside topView's bounds, and you'll only see the part of bottomGradient that fits inside bottomView's bounds. Here's what it looks like with clipping enabled:
And here's a screen shot of my test program in the simulator:
Here's the source code for GradientView:
#interface GradientView: UIView
#property (nonatomic, strong, readonly) CAGradientLayer *gradientLayer;
#end
#implementation GradientView
+ (Class)layerClass { return CAGradientLayer.class; }
- (CAGradientLayer *)gradientLayer { return (CAGradientLayer *)self.layer; }
#end
Here's the code I used to create all of the views:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *topView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 50)];
topView.layer.cornerRadius = 10;
topView.clipsToBounds = YES;
UIView *topGradient = [self newGradientView];
[topView addSubview:topGradient];
[self.view addSubview:topView];
UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(20, 90, 100, 50)];
bottomView.layer.cornerRadius = 10;
bottomView.clipsToBounds = YES;
UIView *bottomGradient = [self newGradientView];
[bottomView addSubview:bottomGradient];
[self.view addSubview:bottomView];
[self constrainView:topGradient toCoverViews:#[topView, bottomView]];
[self constrainView:bottomGradient toCoverViews:#[topView, bottomView]];
}
- (GradientView *)newGradientView {
GradientView *gv = [[GradientView alloc] initWithFrame:CGRectZero];
gv.translatesAutoresizingMaskIntoConstraints = NO;
gv.gradientLayer.colors = #[(__bridge id)UIColor.blueColor.CGColor, (__bridge id)UIColor.redColor.CGColor];
return gv;
}
And here's how I create the constraints that make a GradientView (or any view) cover a set of views:
- (void)constrainView:(UIView *)coverer toCoverViews:(NSArray<UIView *> *)coverees {
for (UIView *coveree in coverees) {
NSArray<NSLayoutConstraint *> *cs;
cs = #[
[coverer.leftAnchor constraintLessThanOrEqualToAnchor:coveree.leftAnchor],
[coverer.rightAnchor constraintGreaterThanOrEqualToAnchor:coveree.rightAnchor],
[coverer.topAnchor constraintLessThanOrEqualToAnchor:coveree.topAnchor],
[coverer.bottomAnchor constraintGreaterThanOrEqualToAnchor:coveree.bottomAnchor]];
[NSLayoutConstraint activateConstraints:cs];
cs = #[
[coverer.leftAnchor constraintEqualToAnchor:coveree.leftAnchor],
[coverer.rightAnchor constraintEqualToAnchor:coveree.rightAnchor],
[coverer.topAnchor constraintEqualToAnchor:coveree.topAnchor],
[coverer.bottomAnchor constraintEqualToAnchor:coveree.bottomAnchor]];
for (NSLayoutConstraint *c in cs) { c.priority = UILayoutPriorityDefaultHigh; }
[NSLayoutConstraint activateConstraints:cs];
}
}
The greaterThanOrEqual/lessThanOrEqual constraints, which (by default) have required priority, ensure that coverer covers the entire frame of each coveree. The equal constraints, which have lower priority, then ensure that coverer occupies the minimum space required to cover each coveree.
You can do this by adding a view on top of the view with the gradient, then cutting out the shapes by making a mask out of a UIBezierPath, then adding that to the view on top (let's call it topView):
let yourPath: UIBezierPath = //create the desired bezier path for your shapes
let mask = CAShapeLayer()
mask.path = yourPath.cgPath
topView.layer.mask = mask

Passing view-controller into another view-controller

i have 10 uiviewcontrollers each creating a type of question to the user.
Each has several layers, uilabels and data.
All 10 should call the 'disp_correct_anim' viewcontroller when the user's answer is correct and the 'disp_fail_anim' when the answer is wrong.
These two new controllers need to have all the subviews and data of the main viewcontroller.
They then create a few more layers and then a animation will start.
My problem is that i don't know how to pass the entire viewcontroller to the 'disp_fail_anim' lets say
#import "ViewController.h"
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Calling the NSobject to create the data of the app
// ================================================================================
circleModel = [[Circles_3 alloc] init];
// Draw a shape
// ================================================================================
UIColor * colr = [UIColor colorWithRed:0.55 green:0.55 blue:0.55 alpha:1.0];
CAShapeLayer * down_rect = [CAShapeLayer layer];
CGRect low_rect= [[circleModel.Architectural_rectangles objectForKey:#"Control_rect"] CGRectValue];
down_rect.path = [UIBezierPath bezierPathWithRect:low_rect].CGPath;
down_rect.fillColor = [UIColor clearColor].CGColor;
down_rect.strokeColor = colr.CGColor;
down_rect.lineWidth = 1.5;
[self.view.layer addSublayer:down_rect];
// Display some text
// ================================================================================
NSAttributedString * Que_Text=circleModel.Question_string;
CGRect recty= [[circleModel.Architectural_rectangles objectForKey:#"Question_rect"] CGRectValue];
UILabel *Question_text = [[UILabel alloc] initWithFrame:recty];
Question_text.AttributedText = Que_Text;
Question_text.numberOfLines = 0;
Question_text.adjustsFontSizeToFitWidth = YES;
Question_text.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:Question_text];
}
// Then i have some touch methods and when the touch is over
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (circleModel.Correct_answer==button)
disp_correct_anim * correct_anim = [[disp_correct_anim alloc] init];
else
disp_fail_anim * fail_anim = [[disp_fail_anim alloc] init];
}
// The 'disp_fail_anim' and 'disp_correct_anim' are 2 animations each being a viewcontroller.
You sound confused. Animations are not view controllers. View controllers don't share views with other view controllers.
It sounds to me like you need to create a parent class for all your view controllers that implements a couple of common animation methods, one for a correct answer, and one for a wrong answer. All 10 of your view controllers would be subclasses of this base class and therefore have the method built into them.

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");
}
}

iOS: DrawRect does not move my view

I’m a new developer on iOS and I’m struggling with the DrawRect method: the first time it gets called, it actually draws what I want where I want, but all the next calls to DrawRect fail to move my view (although they do resize it).
I therefore made a minimalist test app to reproduce my problem but still could not isolate the cause.
This app just draws a blue rectangle in the top left corner, and each time you tap into it, it’s supposed to:
Switch width and height (this does work)
Move the rectangle by 10 points to the right (this does not work)
Of course I checked that DrawRect actually gets called and that its bounds did change to what I wanted, but still, my view does not want to move.
==============================================
Here’s my ViewController (RVTViewController.m)
==============================================
#implementation RVTViewController
(void)viewDidLoad`
{
[super viewDidLoad];
if (!self.myView)
{
CGRect viewBounds = CGRectMake(0,0,100,150);
self.myView = [[RVTView alloc] initWithFrame:viewBounds];
[self.view addSubview:self.myView];
UITapGestureRecognizer* gestRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap:)];
[self.myView addGestureRecognizer:gestRec];
}
}
-(void) tap:(UITapGestureRecognizer*) gesture
{
CGRect newViewBounds = CGRectMake(self.myView.bounds.origin.x + 10,
self.myView.bounds.origin.y,
self.myView.bounds.size.height,
self.myView.bounds.size.width);
self.myView.bounds = newViewBounds;
[self.myView setNeedsLayout];
[self.myView setNeedsDisplay];
}
#end
=====================================
And here’s my custom view (RVTView.m)
=====================================
#implementation RVTView
- (void)drawRect:(CGRect)rect
{
CGRect newBounds = self.bounds;
UIBezierPath* newRect = [UIBezierPath bezierPathWithRect:newBounds];
[[UIColor blueColor] setFill];
UIRectFill(self.bounds);
[newRect stroke];
}
#end
Can someone please tell me what I am getting wrong ?
Using [self.myView setFrame:newViewBounds]; instead of self.myView.bounds = newViewBounds; fixed the problem.

How to round off one corner of a resizable UIView in IOS?

I'm using this code to round off one corner of my UIView:
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:
self.view.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii:
CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
self.view.layer.mask = maskLayer;
self.view.layer.masksToBounds = NO;
This code works, as long as I don't ever resize the view. If I make the view larger, the new area does not appear because it's outside the bounds of the mask layer (this mask layer does not automatically resize itself with the view). I could just make the mask as large as it will ever need to be, but it could be full-screen on the iPad so I'm worried about performance with a mask that big (I'll have more than one of these in my UI). Also, a super-sized mask wouldn't work for the situation where I need the upper right corner (alone) to be rounded off.
Is there a simpler, easier way to achieve this?
Update: here is what I'm trying to achieve: http://i.imgur.com/W2AfRBd.png (the rounded corner I want is circled here in green).
I have achieved a working version of this, using a subclass of UINavigationController and overriding viewDidLayoutSubviews like so:
- (void)viewDidLayoutSubviews {
CGRect rect = self.view.bounds;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect
byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(8.0, 8.0)];
self.maskLayer = [CAShapeLayer layer];
self.maskLayer.frame = rect;
self.maskLayer.path = maskPath.CGPath;
self.view.layer.mask = self.maskLayer;
}
I then instantiate my UINavigationController subclass with my view controller, and then I offset the frame of the nav controller's view by 20px (y) to expose the status bar and leave a 44-px high navigation bar, as shown in the picture.
The code is working, except that it doesn't handle rotation very well at all. When the app rotates, viewDidLayoutSubviews gets called before the rotation and my code creates a mask that fits the view after rotation; this creates an undesirable blockiness to the rotation, where bits that should be hidden are exposed during the rotation. Also, whereas the app's rotation is perfectly smooth without this mask, with the mask being created the rotation becomes noticeably jerky and slow.
The iPad app Evomail also has rounded corners like this, and their app suffers from the same problem.
The problem is, CoreAnimation properties do not animate in UIKit animation blocks. You need to create a separate animation which will have the same curve and duration as the UIKit animation.
I created the mask layer in viewDidLoad. When the view is about to be layout, I only modify the path property of the mask layer.
You do not know the rotation duration inside the layout callback methods, but you do know it right before rotation (and before layout is triggered), so you can keep it there.
The following code works well.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
//Keep duration for next layout.
_duration = duration;
}
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
UIBezierPath* maskPath = [UIBezierPath bezierPathWithRoundedRect:self.view.bounds byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(10, 10)];
CABasicAnimation* animation;
if(_duration > 0)
{
animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[animation setDuration:_duration];
//Set old value
[animation setFromValue:(id)((CAShapeLayer*)self.view.layer.mask).path];
//Set new value
[animation setToValue:(id)maskPath.CGPath];
}
((CAShapeLayer*)self.view.layer.mask).path = maskPath.CGPath;
if(_duration > 0)
{
[self.view.layer.mask addAnimation:animation forKey:#"path"];
}
//Zero duration for next layout.
_duration = 0;
}
I know this is a pretty hacky way of doing it but couldn't you just add a png over the top of the corner?
Ugly I know, but it won't affect performance, rotation will be fine if its a subview and users won't notice.
Two ideas:
Resize the mask when the view is resized. You don't get automatic resizing of sublayers the way you get automatic resizing of subviews, but you still get an event, so you can do manual resizing of sublayers.
Or... If this a view whose drawing and display you are in charge of, make the rounding of the corner a part of how you draw the view in the first place (by clipping). That is in fact the most efficient approach.
You could subclass the view you are using and override "layoutSubviews"method. This one gets called everytime your view dimensions change.
Even if "self.view"(referenced in your code) is your viewcontroller's view, you can still set this view to a custom class in your storyboard. Here's the modified code for the subclass:
- (void)layoutSubviews {
[super layoutSubviews];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:
self.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii:
CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
self.layer.masksToBounds = NO;
}
I think you should create a custom view that updates itself any time it is needed, which means anytime that setNeedsDisplay is called.
What I'm suggesting is to create a custom UIView subclass to be implemented as follows:
// OneRoundedCornerUIView.h
#import <UIKit/UIKit.h>
#interface OneRoundedCornerUIView : UIView //Subclass of UIView
#end
// OneRoundedCornerUIView.m
#import "OneRoundedCornerUIView.h"
#implementation OneRoundedCornerUIView
- (void) setFrame:(CGRect)frame
{
[super setFrame:frame];
[self setNeedsDisplay];
}
// Override drawRect as follows.
- (void)drawRect:(CGRect)rect
{
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:
self.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii:
CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
self.layer.masksToBounds = NO;
}
#end
Once you've done this you simply need to make your view an OneRoundedCornerUIView instance instead of an UIView one and your view will be updated smoothly every time you resize or change its frame. I've just done some testing and it seems to work perfectly.
This solution can also be easily customised in order to have a view for which you can easily set which corners should be on and which corners should not from your View Controller. Implementation as follows:
// OneRoundedCornerUIView.h
#import <UIKit/UIKit.h>
#interface OneRoundedCornerUIView : UIView //Subclass of UIView
// This properties are declared in the public API so that you can setup from your ViewController (it also works if you decide to add/remove corners at any time as the setter of each of these properties will call setNeedsDisplay - as shown in the implementation file)
#property (nonatomic, getter = isTopLeftCornerOn) BOOL topLeftCornerOn;
#property (nonatomic, getter = isTopRightCornerOn) BOOL topRightCornerOn;
#property (nonatomic, getter = isBottomLeftCornerOn) BOOL bottomLeftCornerOn;
#property (nonatomic, getter = isBottomRightCornerOn) BOOL bottomRightCornerOn;
#end
// OneRoundedCornerUIView.m
#import "OneRoundedCornerUIView.h"
#implementation OneRoundedCornerUIView
- (void) setFrame:(CGRect)frame
{
[super setFrame:frame];
[self setNeedsDisplay];
}
- (void) setTopLeftCornerOn:(BOOL)topLeftCornerOn
{
_topLeftCornerOn = topLeftCornerOn;
[self setNeedsDisplay];
}
- (void) setTopRightCornerOn:(BOOL)topRightCornerOn
{
_topRightCornerOn = topRightCornerOn;
[self setNeedsDisplay];
}
-(void) setBottomLeftCornerOn:(BOOL)bottomLeftCornerOn
{
_bottomLeftCornerOn = bottomLeftCornerOn;
[self setNeedsDisplay];
}
-(void) setBottomRightCornerOn:(BOOL)bottomRightCornerOn
{
_bottomRightCornerOn = bottomRightCornerOn;
[self setNeedsDisplay];
}
// Override drawRect as follows.
- (void)drawRect:(CGRect)rect
{
UIRectCorner topLeftCorner = 0;
UIRectCorner topRightCorner = 0;
UIRectCorner bottomLeftCorner = 0;
UIRectCorner bottomRightCorner = 0;
if (self.isTopLeftCornerOn) topLeftCorner = UIRectCornerTopLeft;
if (self.isTopRightCornerOn) topRightCorner = UIRectCornerTopRight;
if (self.isBottomLeftCornerOn) bottomLeftCorner = UIRectCornerBottomLeft;
if (self.isBottomRightCornerOn) bottomRightCorner = UIRectCornerBottomRight;
UIRectCorner corners = topLeftCorner | topRightCorner | bottomLeftCorner | bottomRightCorner;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:
self.bounds byRoundingCorners:(corners) cornerRadii:
CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
self.layer.masksToBounds = NO;
}
#end
I'm a fan of doing what #Martin suggests. As long as there isn't animated content behind the rounded-corner then you can pull this off - even with a bitmap image displayed behind the frontmost view needing the rounded corner.
I created a sample project to mimic your screenshot. The magic happens in a UIView subclass called TSRoundedCornerView. You can place this view anywhere you want - above the view you want to show a rounded corner on, set a property to say what corner to round (adjust the radius by adjusting the size of the view), and setting a property that is the "background view" that you want to be visible in the corner.
Here's the repo for the sample: https://github.com/TomSwift/testRoundedCorner
And here's the drawing magic for the TSRoundedCornerView. Basically we create an inverted clip path with our rounded corner, then draw the background.
- (void) drawRect:(CGRect)rect
{
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextSaveGState(gc);
{
// create an inverted clip path
// (thanks rob mayoff: http://stackoverflow.com/questions/9042725/drawrect-how-do-i-do-an-inverted-clip)
UIBezierPath* bp = [UIBezierPath bezierPathWithRoundedRect: self.bounds
byRoundingCorners: self.corner // e.g. UIRectCornerTopLeft
cornerRadii: self.bounds.size];
CGContextAddPath(gc, bp.CGPath);
CGContextAddRect(gc, CGRectInfinite);
CGContextEOClip(gc);
// self.backgroundView is the view we want to show peering out behind the rounded corner
// this works well enough if there's only one layer to render and not a view hierarchy!
[self.backgroundView.layer renderInContext: gc];
//$ the iOS7 way of rendering the contents of a view. It works, but only if the UIImageView has already painted... I think.
//$ if you try this, be sure to setNeedsDisplay on this view from your view controller's viewDidAppear: method.
// CGRect r = self.backgroundView.bounds;
// r.origin = [self.backgroundView convertPoint: CGPointZero toView: self];
// [self.backgroundView drawViewHierarchyInRect: r
// afterScreenUpdates: YES];
}
CGContextRestoreGState(gc);
}
I thought about this again and I think there is a simpler solution. I updated my sample to showcase both solutions.
The new solution is to simply create a container view that has 4 rounded corners (via CALayer cornerRadius). You can size that view so only the corner you're interested in is visible on screen. This solution doesn't work well if you need 3 corners rounded, or two opposite (on the diagonal) corners rounded. I think it works in most other cases, including the one you've described in your question and screenshot.
Here's the repo for the sample: https://github.com/TomSwift/testRoundedCorner
Try this. Hope this will helps you.
UIView* parent = [[UIView alloc] initWithFrame:CGRectMake(10,10,100,100)];
parent.clipsToBounds = YES;
UIView* child = [[UIView alloc] new];
child.clipsToBounds = YES;
child.layer.cornerRadius = 3.0f;
child.backgroundColor = [UIColor redColor];
child.frame = CGRectOffset(parent.bounds, +4, -4);
[parent addSubView:child];
If you want to do it in Swift I could advice you to use an extension of an UIView. By doing so all subclasses will be able to use the following method:
import QuartzCore
extension UIView {
func roundCorner(corners: UIRectCorner, radius: CGFloat) {
let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSizeMake(radius, radius))
var maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
}
self.anImageView.roundCorner(UIRectCorner.TopRight, radius: 10)

Resources