Is it possible to create such a UIView fill with color, but in the middle is transparent?
I'm thinking about to create 5 UIViews here. Just wondering is it possible to accomplish by using only ONE UIView
From Duncan C, I get to know where should I start, then I found CALayer with transparent hole in it.
UIBezierPath *overlayPath = [UIBezierPath bezierPathWithRect:self.view.bounds];
UIBezierPath *transparentPath = [UIBezierPath bezierPathWithRect:CGRectMake(60, 120, 200, 200)];
[overlayPath appendPath:transparentPath];
[overlayPath setUsesEvenOddFillRule:YES];
CAShapeLayer *fillLayer = [CAShapeLayer layer];
fillLayer.path = overlayPath.CGPath;
fillLayer.fillRule = kCAFillRuleEvenOdd;
fillLayer.fillColor = [UIColor colorWithRed:255/255.0 green:20/255.0 blue:147/255.0 alpha:1].CGColor;
[self.view.layer addSublayer:fillLayer];
Make use of 2 UIBezierPath, then fill with color that I want (in my question is pink color), then add as sublayer
You create a view as subclass of UIView and add these codes:
In YourView.h
#import <UIKit/UIKit.h>
#interface YourView : UIView
#property (nonatomic, assign) CGRect rectForClearing;
#property (nonatomic, strong) UIColor *overallColor;
#end
In YourView.m
#import "YourView.h"
#implementation NBAMiddleTransparentView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder // support init from nib
{
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
[super drawRect:rect];
CGContextRef ct = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(ct, self.overallColor.CGColor);
CGContextFillRect(ct, self.bounds);
CGContextClearRect(ct, self.rectForClearing);
}
#end
Using:
yourView.overallColor = [UIColor redColor];
yourView.rectForClearing = CGRectMake(20, 20, 20, 20);
Hope this helps!
Yes it's possible. You could attach a mask layer to your view's layer, and "punch out" the center portion of the mask. It would actually be quite easy.
Related
I am trying to create my own custom show case view , In which i have left button , right button and a tab bar view.
I want to highlight the buttons with round circle around it.
I am able to create a single Circle around my button by subclass UIView
But what i do so user swipe from left to right it will change the Circle to right button and the left button hide.
Here is my subclass of UIView
//--------.h class
#import <UIKit/UIKit.h>
#interface IntroView : UIView
- (void)drawRect:(CGRect)rect withBtnRect:(CGRect)btnrect ;
#end
//------. m class
#import "IntroView.h"
#implementation IntroView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
self.backgroundColor= [[UIColor blackColor] colorWithAlphaComponent:0.3];
return self;
}
- (void)drawRect:(CGRect)rect {
CGRect maskRect= CGRectMake(5, 5, 100, 100);//set and pass this maskRect
CGRect rBounds = self.bounds;
CGContextRef context = UIGraphicsGetCurrentContext();
// Fill background with 40% gray
CGContextSetFillColorWithColor(context, [[[UIColor grayColor] colorWithAlphaComponent:0.4] CGColor]);
CGContextFillRect(context, rBounds);
// Draw the window 'frame'
CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGContextSetLineWidth(context,2);
CGContextStrokeEllipseInRect(context, maskRect);
// make the window transparent
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextFillEllipseInRect (context, maskRect);
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
And inside my tabbar first view i call using
#import "IntroView.h"
then
- (void)viewDidLoad
{
IntroView *vw_intro=[[IntroView alloc]initWithFrame:CGRectMake(0, 0, 320, 480)];
vw_intro.tag=1000;
[self.view addSubview:vw_intro];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
Here is my output
And expected Output on Slide right to left , and so or in reverse (like we do in showcase android library )
In IntroView.h
#import <UIKit/UIKit.h>
#interface IntroView : UIView
- (id)initWithFrame:(CGRect)frame introElements:(NSArray *)iElements;
- (void)showIntro;
#end
In IntroView.m
#import "IntroView.h"
#implementation IntroView
{
CAShapeLayer *mask;
NSArray *introElements;
NSUInteger currentIndex;
}
- (id)initWithFrame:(CGRect)frame introElements:(NSArray *)iElements
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
introElements = iElements;
mask = [CAShapeLayer layer];
[mask setFillColor:[[UIColor grayColor] colorWithAlphaComponent:0.8f].CGColor];
[mask setFillRule:kCAFillRuleEvenOdd];
[self.layer addSublayer:mask];
UISwipeGestureRecognizer *swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(userDidSwipe)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[self addGestureRecognizer:swipeGestureRecognizer];
}
return self;
}
- (void)userDidSwipe
{
[self showIntroAtIndex:currentIndex+1];
}
- (void)showIntro
{
//Initial index by default
[self showIntroAtIndex:0];
}
- (void)showIntroAtIndex:(NSUInteger)index
{
currentIndex = index;
CGRect rect = [[introElements objectAtIndex:index] CGRectValue];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:self.bounds];
UIBezierPath *elementPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:3.0f];
[maskPath appendPath:elementPath];
// Animate
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
animation.duration = 0.3f;
animation.fromValue = (__bridge id)(mask.path);
animation.toValue = (__bridge id)(maskPath.CGPath);
[mask addAnimation:animation forKey:#"path"];
mask.path = maskPath.CGPath;
}
In ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSArray *viewElements = [NSArray arrayWithObjects:[NSValue valueWithCGRect:button1.frame],[NSValue valueWithCGRect:button2.frame], nil];
IntroView *introView = [[IntroView alloc]initWithFrame:self.view.bounds introElements:viewElements];
[self.view addSubview:introView];
[introView showIntro];
}
Note: i made a highlight to the UIControl frame, if you need a circle highlight, update the changes in the CGRect.
I created a custom UIView to draw a "hole" ontop of a background
#implementation MaskWithHole
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.hole = CGRectZero;
self.holeColor = [UIColor clearColor];
}
return self;
}
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return nil;
}
-(void)setHole:(CGRect)hole
{
_hole = hole;
[self setNeedsDisplay];
}
-(void)setHoleColor:(UIColor *)holeColor
{
_holeColor = holeColor;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[self.backgroundColor setFill];
UIRectFill(rect);
// clear the background in the given rectangles
CGRect holeRect = self.hole;
CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );
[self.holeColor setFill];
UIRectFill(holeRectIntersection);
}
#end
But the custom drawing is always the background color - disregarding the drawRect code
CGRect holeRect = self.hole;
CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );
[self.holeColor setFill];
UIRectFill(holeRectIntersection);
when I change the color of the hole from clear to green i can see the green- so apperantly the method is drawing teh background on everything
The easy way to have a transparent hole in the middle of a solid background is using a png image of that size with transparency in the middle, and the solid color outside.
This seems to work
#interface MaskWithHole : UIView
// default cgrectzero
#property (nonatomic,assign) CGRect hole;
// default [uicolor clearcolor]
#property (nonatomic,strong) UIColor *holeColor;
#end
#interface MaskWithHole ()
#property (nonatomic,strong) UIColor *backGroundColorForMask;
#end
#implementation MaskWithHole
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.hole = CGRectZero;
self.holeColor = [UIColor clearColor];
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
}
return self;
}
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return nil;
}
-(void)setHole:(CGRect)hole
{
_hole = hole;
[self setNeedsDisplay];
}
-(void)setHoleColor:(UIColor *)holeColor
{
_holeColor = holeColor;
[self setNeedsDisplay];
}
-(void)setBackgroundColor:(UIColor *)backgroundColor
{
[super setBackgroundColor:[UIColor clearColor]];
self.backGroundColorForMask = backgroundColor;
}
- (void)drawRect:(CGRect)rect
{
[self.backGroundColorForMask setFill];
UIRectFill(rect);
CGRect holeRectIntersection = CGRectIntersection( self.hole, rect );
[[UIColor clearColor] setFill];
UIRectFill(holeRectIntersection);
}
#end
I'm wanting to generate a UIImage that's a RGB gradient that also has a portion of black on the left side. I would like to do this programmatically, instead of having to make the image in Photoshop and using it as an asset in the app.
I've searched on here and have seen the generation of iOS color wheels before, but nothing like a rectangle below which I mocked in Photoshop:
This will be for letting users change text color as they touch inside of the UIImage.
I'm not really sure where to begin however, and would appreciate some pointers.
The below code creates a gradient [from red,yellow,green,blue,red] and returns RGB values if touched on it.The below code can be used for any number of colors, just set their value in colors Array.The selected color is set as backgroundColor for selectedColorView(UIView).
ViewController.h file
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#end
ViewController.m file
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#end
#implementation ViewController{
UIView *selectedColorView;
CAGradientLayer *layer;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
layer =[CAGradientLayer layer];
[layer setFrame:CGRectMake(20, 20, 280, 50)];
layer.colors =#[(id)[UIColor redColor].CGColor,(id)[UIColor yellowColor].CGColor,(id)[UIColor greenColor].CGColor,(id)[UIColor blueColor].CGColor,(id)[UIColor redColor].CGColor];
layer.startPoint =CGPointMake(0, .5);
layer.endPoint =CGPointMake(1, .5);
[self.view.layer addSublayer:layer];
selectedColorView =[[UIView alloc] initWithFrame:CGRectMake(20, 20+50, 280, 50)];
[self.view addSubview:selectedColorView];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint p = [[touches anyObject] locationInView:self.view];
if(CGRectContainsPoint(layer.frame, p)){
CGFloat xOffset = (p.x - layer.frame.origin.x);
CGFloat gap=(layer.frame.size.width/(layer.colors.count-1));
NSInteger index = xOffset/gap;
xOffset =xOffset -index*gap;
UIColor *color1=[UIColor colorWithCGColor:(CGColorRef)layer.colors[index]];
UIColor *color2=[UIColor colorWithCGColor:(CGColorRef)layer.colors[index+1]];
CGFloat r1,g1,b1,a1,r2,g2,b2,a2;
[color1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
[color2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
selectedColorView.backgroundColor =[UIColor colorWithRed:(1-(xOffset/gap))*r1 +(xOffset/gap)*r2 green:(1-(xOffset/gap))*g1 +(xOffset/gap)*g2 blue:(1-(xOffset/gap))*b1 +(xOffset/gap)*b2 alpha:1.0];
}
}
#end
i think you could draw a gradient color on a context and then create a image of this context.
or maybe you can also do it like this.
http://belencruz.com/2012/12/gradient-background-in-ios-without-images/
I'm trying to animate a UIBezierPath and I've installed a CAShapeLayer to try to do it. Unfortunately the animation isn't working and I'm not sure any of the layers are having any affect (as the code is doing the same thing it was doing before I had the layers).
Here is the actual code - would love any help. Draw2D is an implementation of UIView that is embedded in a UIViewController. All the drawing is happening inside the Draw2D class. The call to [_helper createDrawing... ] simply populates the _uipath variable with points.
Draw2D.h defines the following properties:
#define defaultPointCount ((int) 25)
#property Draw2DHelper *helper;
#property drawingTypes drawingType;
#property int graphPoints;
#property UIBezierPath *uipath;
#property CALayer *animationLayer;
#property CAShapeLayer *pathLayer;
- (void)refreshRect:(CGRect)rect;
below is the actual implementation :
//
// Draw2D.m
// Draw2D
//
// Created by Marina on 2/19/13.
// Copyright (c) 2013 Marina. All rights reserved.
//
#import "Draw2D.h"
#import"Draw2DHelper.h"
#include <stdlib.h>
#import "Foundation/Foundation.h"
#import <QuartzCore/QuartzCore.h>
int MAX_WIDTH;
int MAX_HEIGHT;
#implementation Draw2D
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
if (self.pathLayer != nil) {
[self.pathLayer removeFromSuperlayer];
self.pathLayer = nil;
}
self.animationLayer = [CALayer layer];
self.animationLayer.frame = self.bounds;
[self.layer addSublayer:self.animationLayer];
CAShapeLayer *l_pathLayer = [CAShapeLayer layer];
l_pathLayer.frame = self.frame;
l_pathLayer.bounds = self.bounds;
l_pathLayer.geometryFlipped = YES;
l_pathLayer.path = _uipath.CGPath;
l_pathLayer.strokeColor = [[UIColor grayColor] CGColor];
l_pathLayer.fillColor = nil;
l_pathLayer.lineWidth = 1.5f;
l_pathLayer.lineJoin = kCALineJoinBevel;
[self.animationLayer addSublayer:l_pathLayer];
self.pathLayer = l_pathLayer;
[self.layer addSublayer:l_pathLayer];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect :(int) points :(drawingTypes) type //:(Boolean) initial
{
//CGRect bounds = [[UIScreen mainScreen] bounds];
CGRect appframe= [[UIScreen mainScreen] applicationFrame];
CGContextRef context = UIGraphicsGetCurrentContext();
_helper = [[Draw2DHelper alloc ] initWithBounds :appframe.size.width :appframe.size.height :type];
CGPoint startPoint = [_helper generatePoint] ;
[_uipath moveToPoint:startPoint];
[_uipath setLineWidth: 1.5];
CGContextSetStrokeColorWithColor(context, [UIColor lightGrayColor].CGColor);
CGPoint center = CGPointMake(self.center.y, self.center.x) ;
[_helper createDrawing :type :_uipath :( (points>0) ? points : defaultPointCount) :center];
self.pathLayer.path = (__bridge CGPathRef)(_uipath);
[_uipath stroke];
[self startAnimation];
}
- (void) startAnimation {
[self.pathLayer removeAllAnimations];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 3.0;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[self.pathLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
}
- (void)drawRect:(CGRect)rect {
if (_uipath == NULL)
_uipath = [[UIBezierPath alloc] init];
else
[_uipath removeAllPoints];
[self drawRect:rect :self.graphPoints :self.drawingType ];
}
- (void)refreshRect:(CGRect)rect {
[self setNeedsDisplay];
}
#end
I know there's probably an obvious reason for why the path isn't animating as it's being drawing (as opposed to being shown immediately which is what happens now) but I've been staring at the thing for so long that I just don't see it.
Also, if anyone can recommend a basic primer on CAShapeLayers and animation in general I would appreciate it. Haven't come up across any that are good enough.
thanks in advance.
It looks like you're trying to animate within drawRect (indirectly, at least). That doesn't quite make sense. You don't animate within drawRect. The drawRect is used for drawing a single frame. Some animation is done with timers or CADisplayLink that repeatedly calls setNeedsDisplay (which will cause iOS to call your drawRect) during which you might draw the single frame that shows the progress of the animation at that point. But you simply don't have drawRect initiating any animation on its own.
But, since you're using Core Animation's CAShapeLayer and CABasicAnimation, you don't need a custom drawRect at all. Quartz's Core Animation just takes care of everything for you. For example, here is my code for animating the drawing of a UIBezierPath:
#import <QuartzCore/QuartzCore.h>
#interface View ()
#property (nonatomic, weak) CAShapeLayer *pathLayer;
#end
#implementation View
/*
// I'm not doing anything here, so I can comment this out
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
*/
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
// It doesn't matter what my path is. I could make it anything I wanted.
- (UIBezierPath *)samplePath
{
UIBezierPath *path = [UIBezierPath bezierPath];
// build the path here
return path;
}
- (void)startAnimation
{
if (self.pathLayer == nil)
{
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [[self samplePath] CGPath];
shapeLayer.strokeColor = [[UIColor grayColor] CGColor];
shapeLayer.fillColor = nil;
shapeLayer.lineWidth = 1.5f;
shapeLayer.lineJoin = kCALineJoinBevel;
[self.layer addSublayer:shapeLayer];
self.pathLayer = shapeLayer;
}
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 3.0;
pathAnimation.fromValue = #(0.0f);
pathAnimation.toValue = #(1.0f);
[self.pathLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
}
#end
Then, when I want to start drawing the animation, I just call my startAnimation method. I probably don't even need a UIView subclass at all for something as simple as this, since I'm not actually changing any UIView behavior. There are definitely times that you subclass UIView with a custom drawRect implementation, but it's not needed here.
You asked for some references:
I would probably start with a review of Apple's Core Animation Programming Guide, if you haven't seen that.
For me, it all fell into place when I went through Mike Nachbaur's Core Animation Tutorial Part 4, actually reproducing his demo from scratch. Clearly, you can check out parts 1 through 3, too.
I'm trying to learn Core Animation to develop a certain app but I need to subclass the CALayer class, however I'm struggling to get the layer to draw itself.
I need the custom CALayer to have some additional properties and handle custom events (touching and such) but from the start the basic CALayer I'm implementing is not drawing itself, can anyone tell me what I'm doing wrong?
I'm have a
MagicSquare
#import "MagicSquare.h"
#implementation MagicSquare
-(id) initWithLayer:(id)layer {
self = [super initWithLayer:layer];
self.bounds = CGRectMake(0, 0, 200, 200);
self.position = CGPointMake(10,10);
self.cornerRadius = 100;
self.borderColor = [UIColor redColor].CGColor;
self.borderWidth = 1.5;
return self;
}
- (void)drawInContext:(CGContextRef)theContext
{
NSLog(#"Drawing");
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
CGPathAddCurveToPoint(thePath,
NULL,
15.f,250.0f,
295.0f,250.0f,
295.0f,15.0f);
CGContextBeginPath(theContext);
CGContextAddPath(theContext, thePath );
CGContextSetLineWidth(theContext,
1.0);
CGContextSetStrokeColorWithColor(theContext,
[UIColor redColor].CGColor);
CGContextStrokePath(theContext);
CFRelease(thePath);
}
and here's how I'm trying to have it draw on the main controller
#implementation BIDViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
MagicSquare *layer = [[MagicSquare alloc] initWithLayer:[CALayer layer]];
[self.view.layer addSublayer:layer];
}
Found the problem.
I needed to call to setNeedsDisplay on the layer, because it doesn't automatically draws itself:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
MagicSquare *layer = [[MagicSquare alloc] initWithLayer:[CALayer layer]];
[self.view.layer addSublayer:layer];
[layer setNeedsDisplay];
}