I have a view with bezier path mask layer. Now I need to add to my view a subview, that has to be placed outside of the superView
class MiddleSegmentView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
createMaskView()
}
func createPath() -> UIBezierPath {
let width = self.frame.size.width
let height = self.frame.size.height
let lineWidth = -height/tan(angle)
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 0.0, y: 0.0))
path.addLineToPoint(CGPoint(x: width, y: 0))
path.addLineToPoint(CGPoint(x: width - lineWidth, y: height))
path.addLineToPoint(CGPoint(x: lineWidth, y: height))
path.addLineToPoint(CGPoint(x: 0.0, y: 0.0))
path.closePath()
return path
}
func createMaskView() {
let mask = CAShapeLayer()
mask.path = createPath().CGPath
self.layer.mask = mask
}
//Creating shadow
override func drawRect(rect: CGRect) {
super.drawRect(rect)
//createShadow()
let shadowView = SegmentShadow(frame: CGRect(x: 0, y: 0, width: 10, height: 40))
self.clipsToBounds = false
self.addSubview(shadowView)
shadowView.bringToFront()
}
}
Although my view is generating correctly my shadowView is being cropped by superView bounds. How can I avoid such behaviour?
There are a couple of ways to achieve what you want:
METHOD ONE: INSERT SHADOW SHAPE INTO EACH TAB
Create the tabs you want and then add a custom CAShapeLayer to each one:
//insert a CAShapeLayer into each 'tab'
CAShapeLayer * shadowLayer = [self trapezium];
shadowLayer.fillColor = grey.CGColor;
shadowLayer.shadowOpacity = 1.0f;
shadowLayer.shadowRadius = 1.0f;
shadowLayer.shadowOffset = CGSizeZero;
shadowLayer.shadowColor = [UIColor blackColor].CGColor;
[tab.layer insertSublayer:shadowLayer atIndex:0];
You can access the tabs and shadowLayer like this (given an array 'tabs'):
//then access like this
UIView * tab = tabs[2];
[self.view bringSubviewToFront:tab];
CAShapeLayer * layer = (CAShapeLayer*)tab.layer.sublayers[0];
layer.fillColor = orange.CGColor;
METHOD TWO: MOVE SHADOW VIEW
Another way is to create a single custom 'tabShadowView' and simply move that to the location of the selected tab. Here's the code for laying out the tabs in a custom view:
grey = [_peacock colourForHex:#"#ACA499" andAlpha:1.0f];
orange = [_peacock colourForHex:#"#CB9652" andAlpha:1.0f];
tabShadowView = [UIView new];
tabShadowView.frame = CGRectMake(0, 20, 100, 40);
tabShadowView.layer.shadowPath = [self trapezium].path;
tabShadowView.layer.shadowColor = [UIColor blackColor].CGColor;
tabShadowView.layer.shadowOffset = CGSizeZero;
tabShadowView.layer.shadowRadius = 1.0f;
tabShadowView.layer.shadowOpacity = 0.5f;
[self.view addSubview:tabShadowView];
customTabView = [UIView new];
customTabView.frame = CGRectMake(0, 0, w, 60);
[self.view addSubview:customTabView];
NSArray * titles = #[#"Button A", #"Button B", #"Button C",#"Button D"];
tabs = [NSMutableArray new];
float xOff = 0.0f;
for (NSString * title in titles){
UIView * tab = [UIView new];
tab.frame = CGRectMake(xOff, 20, 100, 40);
tab.backgroundColor = grey;
tab.layer.mask = [self trapezium];
[customTabView addSubview:tab];
[tabs addObject:tab];
UIButton * button = [UIButton new];
button.frame = tab.bounds;
[button setTitle:title forState:UIControlStateNormal];
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
button.titleLabel.textAlignment = NSTextAlignmentCenter;
button.titleLabel.font = [UIFont systemFontOfSize:14.0f weight:UIFontWeightRegular];
[button addTarget:self action:#selector(tabSelected:) forControlEvents:UIControlEventTouchUpInside];
[button setTag:[titles indexOfObject:title]];
[tab addSubview:button];
xOff += 70.0f;
}
[self updateTabsForSelected:tabs[1]];
Put the above code in the place you layout your views. The tabShadowView is the view that we move about, it's right at the bottom of the view hierarchy. Then the for loop just adds tabs in on top of it. Here are the methods that it uses:
-(void)tabSelected:(UIButton *)button {
UIView * tab = tabs[(int)button.tag];
[self updateTabsForSelected:tab];
}
-(void)updateTabsForSelected:(UIView *)tab{
for (UIView * view in customTabView.subviews){
[customTabView sendSubviewToBack:view];
view.backgroundColor = grey;
}
[customTabView bringSubviewToFront:tab];
tab.backgroundColor = orange;
tabShadowView.transform = CGAffineTransformMakeTranslation([tabs indexOfObject:tab]*70, 0);
}
-(CAShapeLayer *)trapezium {
UIBezierPath * path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointZero];
[path addLineToPoint:CGPointMake(20, 40)];
[path addLineToPoint:CGPointMake(80, 40)];
[path addLineToPoint:CGPointMake(100, 0)];
[path closePath];
CAShapeLayer * layer = [CAShapeLayer layer];
layer.path = path.CGPath;
return layer;
}
Tapping on a button then moves the shadow under the selected tab. The code is quick and dirty, you'll need to add more logic, clean up etc, but you get the point, method one inserts a shadow view into each, method two moves a single shadow view about underneath the tabs.
Related
I was tinkering around with BezierPath and noticed something that I can't seem to figure out. This is the code-
UIBezierPath *path = [UIBezierPath new];
CGSize screenDimensions = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
CGPoint firstPoint = CGPointMake(screenDimensions.width/2, screenDimensions.height/2);
CGPoint secondPoint = CGPointMake(screenDimensions.width/2 + 10, screenDimensions.height/2 + 10);
CGPoint thirdPoint = CGPointMake(screenDimensions.width/2 - 10, screenDimensions.height/2 + 10);
[path moveToPoint:firstPoint];
[path addLineToPoint:secondPoint];
[path addLineToPoint:thirdPoint];
[path addLineToPoint:firstPoint];
[path closePath];
CAShapeLayer *tempLayer = [CAShapeLayer new];
[tempLayer setPath:path.CGPath];
UIView *tempView = [[UIView alloc] initWithFrame:CGRectMake(screenDimensions.width/2 - 30, screenDimensions.height/2 - 30, 100, 100)];
// UIView *tempView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[tempView setBackgroundColor:[UIColor blueColor]];
tempView.layer.mask = tempLayer;
[self.view addSubview:tempView];
If i run the above code block, nothing is drawn on the UIView that is added on the screen. But if i were to comment the current "tempView" and uncomment the currently commented allocation, it would draw on screen perfectly. Can anyone please point out what am I doing wrong here when setting the frame or is it something else?
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint point1 = CGPointMake(100, 100);
CGPoint point2 = CGPointMake(200, 200);
CGPoint point3 = CGPointMake(300, 300);
[path moveToPoint:point1];
[path addQuadCurveToPoint:point3 controlPoint:point2];
CAShapeLayer *shape = [CAShapeLayer layer];
shape.path = path.CGPath;
shape.lineWidth = 5;
shape.strokeColor = [UIColor blueColor].CGColor;
shape.fillColor = [UIColor clearColor].CGColor;
shape.frame = self.view.bounds;
[self.view.layer addSublayer:shape];
You can see below output :
It's a simple problem.
Let's say the screen dimensions are 320x480 (and old iPhone). This means that firstPoint will be 160, 240.
In the current code, your view's frame is 130, 210, 100, 100. It's width and height are both 100.
The shape layer will be drawn relative to the view's bounds, not its frame. So as far as the layer and bezier path are concerned, the view's bounds are 0, 0, 100, 100. Since firstPoint is outside those bounds, it doesn't appear. It's actually drawn but outside the visible bounds of the view.
When you switch the two lines that create the view, the view's frame becomes 0, 0, 320, 480. This means its bounds is also 0, 0, 320, 480. The view is much larger now and the layer and bezier path fit and can be seen.
The proper solution is to create the coordinates of the bezier path based on the size of the view it will be applied to, not the size of the screen. This way, the bezier path will fit inside its view no matter how big it is.
More like this:
CGSize screenDimensions = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
UIView *tempView = [[UIView alloc] initWithFrame:CGRectMake(screenDimensions.width/2 - 30, screenDimensions.height/2 - 30, 100, 100)];
// UIView *tempView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIBezierPath *path = [UIBezierPath new];
CGPoint firstPoint = CGPointMake(tempView.bounds.size.width/2, tempView.bounds.size.height/2);
CGPoint secondPoint = CGPointMake(tempView.bounds.size.width/2 + 10, tempView.bounds.size.height/2 + 10);
CGPoint thirdPoint = CGPointMake(tempView.bounds.size.width/2 - 10, tempView.bounds.size.height/2 + 10);
[path moveToPoint:firstPoint];
[path addLineToPoint:secondPoint];
[path addLineToPoint:thirdPoint];
[path addLineToPoint:firstPoint];
[path closePath];
CAShapeLayer *tempLayer = [CAShapeLayer new];
[tempLayer setPath:path.CGPath];
[tempView setBackgroundColor:[UIColor blueColor]];
tempView.layer.mask = tempLayer;
[self.view addSubview:tempView];
I'm trying to create a bezier path like the instagram triangle in the picture below, however I must be doing something wrong. The Bezier path does not show!
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
[self drawTriangle];
}
- (IBAction)closeButton:(UIButton *)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)drawTriangle{
UIBezierPath* trianglePath = [UIBezierPath bezierPath];
[trianglePath moveToPoint:CGPointMake(self.signUpButton.center.x, self.signUpButton.frame.origin.y + 30)];
[trianglePath addLineToPoint:CGPointMake(self.signUpButton.center.x - 10, self.imageView.frame.size.height)];
[trianglePath addLineToPoint:CGPointMake(self.signUpButton.center.x + 10, self.imageView.frame.size.height)];
UIColor *fillColor = [UIColor whiteColor];
[fillColor setFill];
UIColor *strokeColor = [UIColor whiteColor];
[strokeColor setStroke];
[trianglePath fill];
[trianglePath stroke];
[trianglePath closePath];
}
Xcode 10 • Swift 4.2
func drawTriangle(size: CGFloat, x: CGFloat, y: CGFloat, up:Bool) {
let triangleLayer = CAShapeLayer()
let trianglePath = UIBezierPath()
trianglePath.move(to: .zero)
trianglePath.addLine(to: CGPoint(x: -size, y: up ? size : -size))
trianglePath.addLine(to: CGPoint(x: size, y: up ? size : -size))
trianglePath.close()
triangleLayer.path = trianglePath.cgPath
triangleLayer.fillColor = UIColor.white.cgColor
triangleLayer.anchorPoint = .zero
triangleLayer.position = CGPoint(x: x, y: y)
triangleLayer.name = "triangle"
view.layer.addSublayer(triangleLayer)
}
drawTriangle(size: 12, x: view.frame.midX/2, y: view.frame.midY, up: true)
drawTriangle(size: 12, x: view.frame.midX, y: view.frame.midY, up: false)
Here is some sample code that I have drawn a triangle for UIButton in my project and working great.
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setBackgroundColor:[UIColor redColor]];
btn.frame = CGRectMake(100, 100, 80, 53);
[self.view addSubview:btn];
UIBezierPath* bezierPath = UIBezierPath.bezierPath;
[bezierPath moveToPoint:CGPointMake(40, 43)];
[bezierPath addLineToPoint:CGPointMake(25, 53)];
[bezierPath addLineToPoint:CGPointMake(55, 53)];
[bezierPath closePath];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.frame = btn.bounds;
shapeLayer.path = bezierPath.CGPath;
shapeLayer.fillColor = [UIColor whiteColor].CGColor;
[btn.layer addSublayer:shapeLayer];
and if you want to remove triangle of previous button when you click on next button then use following code snippet:
NSArray *arr = btn.layer.sublayers;
for (id class in arr) {
if ([class isKindOfClass:[CAShapeLayer class]] ) {
[class removeFromSuperlayer];
}
}
Is it possible to add a border just on top of a UIView, if so, how please?
I just Testing Bellow few line of Code and it works very nice, just test it in to your Project. hope you'll get your solution easily.
Why to create new View and adding it into your existing view..? For this task simply create one CALayer and add it into your existing UIView's Layer do as following:-
#import <QuartzCore/QuartzCore.h>
- (void)viewDidLoad
{
CALayer *TopBorder = [CALayer layer];
TopBorder.frame = CGRectMake(0.0f, 0.0f, myview.frame.size.width, 3.0f);
TopBorder.backgroundColor = [UIColor redColor].CGColor;
[myview.layer addSublayer:TopBorder];
[super viewDidLoad];
}
and It's Output is:-
i've find solution for me, here's the tricks :
CGSize mainViewSize = self.view.bounds.size;
CGFloat borderWidth = 1;
UIColor *borderColor = [UIColor colorWithRed:37.0/255 green:38.0/255 blue:39.0/255 alpha:1.0];
UIView *topView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, mainViewSize.width, borderWidth)];
topView.opaque = YES;
topView.backgroundColor = borderColor;
topView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin;
[self.view addSubview:topView];
GilbertOOI's answer in Swift 2:
let topBorder: CALayer = CALayer()
topBorder.frame = CGRectMake(0.0, 0.0, myView.frame.size.width, 3.0)
topBorder.backgroundColor = UIColor.redColor().CGColor
myView.layer.addSublayer(topBorder)
Here's a UIView category that lets you add a layer-back or view-backed border on any side of the UIView: UIView+Borders
GilbertOOI's answer in Swift 4:
let topBorder: CALayer = CALayer()
topBorder.frame = CGRect(x: 0, y: 0, width: myView.frame.size.width, height: 1)
topBorder.backgroundColor = UIColor.purple.cgColor
myView.layer.addSublayer(topBorder)
I created this simple UIView subclass so that it works in Interface Builder and works with constraints:
https://github.com/natrosoft/NAUIViewWithBorders
Here's my blog post about it:
http://natrosoft.com/?p=55
-- Basically just drop in a UIView in Interface Builder and change its class type to NAUIViewWithBorders.
-- Then in your VC's viewDidLoad do something like:
/* For a top border only ———————————————- */
self.myBorderView.borderColorTop = [UIColor redColor];
self.myBorderView..borderWidthsAll = 1.0f;
/* For borders with different colors and widths ————————— */
self.myBorderView.borderWidths = UIEdgeInsetsMake(2.0, 4.0, 6.0, 8.0);
self.myBorderView.borderColorTop = [UIColor blueColor];
self.myBorderView.borderColorRight = [UIColor redColor];
self.myBorderView.borderColorBottom = [UIColor greenColor];
self.myBorderView.borderColorLeft = [UIColor darkGrayColor];
Here's a direct link to the .m file so you can see the implementation: NAUIViewWithBorders.m
There is a demo project as well.
remus' answer in Obj-C:
CALayer *topBorder = [CALayer new];
topBorder.frame = CGRectMake(0.0, 0.0, self.frame.size.width, 3.0);
topBorder.backgroundColor = [UIColor redColor].CGColor;
[myView.layer addSublayer:topBorder];
Swift5:
We will write a separate method to add borders to this view. To add borders to this view we will create two layers with the desired thickness. We will set the frame of these two layers to the top and bottom of the view. We will set the desired background color of the borders on these layers and add these layers as subLayers to the view.
func addTopBorders() {
let thickness: CGFloat = 1.0
let topBorder = CALayer()
topBorder.frame = CGRect(x: 0.0, y: 0.0, width:
self.down_view_outlet.frame.size.width, height: thickness)
topBorder.backgroundColor = UIColor.white.cgColor
down_view_outlet.layer.addSublayer(topBorder)
}
I am trying to set cornerRadius of UIButton but I dont know how to do it.
If I do like this:
button.layer.cornerRadius = 5;
works well, if I do like this :
button.layer.cornerRadius = 5;
[button setBackgroundColor:[UIColor colorWithPatternImage:radialGradient]];
the corners are not rounded.
I know I could solve this whit
[button.layer setMasksToBounds:YES];
but I specifically looking for different solution, because I add some arrows to the button and if I set mask to bounds the arrow are masked.
EDIT :
radialGradient is made whit func
+ (UIImage *)getRadialGradientImage:(CGSize)size centre:(CGPoint)centre radius:(float)radius startColor:(UIColor *)startColor endColor:(UIColor *)endColor{
// Initialise
UIGraphicsBeginImageContextWithOptions(size, YES, 1);
// Create the gradient's colours
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
const CGFloat *component_first = CGColorGetComponents([startColor CGColor]);
CGFloat red1 = component_first[0];
CGFloat green1 = component_first[1];
CGFloat blue1 = component_first[2];
const CGFloat *component_second = CGColorGetComponents([endColor CGColor]);
CGFloat red2 = component_second[0];
CGFloat green2 = component_second[1];
CGFloat blue2 = component_second[2];
const CGFloat components[8] = { red1,green1,blue1,1,red2,green2,blue2,1}; // End color
CGColorSpaceRef myColorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef myGradient = CGGradientCreateWithColorComponents (myColorspace, components, locations, num_locations);
// Normalise the 0-1 ranged inputs to the width of the image
CGPoint myCentrePoint = CGPointMake(centre.x * size.width, centre.y * size.height);
float myRadius = MIN(size.width, size.height) * radius;
// Draw it!
CGContextDrawRadialGradient (UIGraphicsGetCurrentContext(), myGradient, myCentrePoint,
0, myCentrePoint, myRadius,
kCGGradientDrawsAfterEndLocation);
// Grab it as an autoreleased image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// Clean up
CGColorSpaceRelease(myColorspace); // Necessary?
CGGradientRelease(myGradient); // Necessary?
UIGraphicsEndImageContext(); // Clean up
return image;
}
Here is how I would create a button through code and set its background color.
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(100, 100, 100,50);
[btn setTitle:#"Hello" forState:UIControlStateNormal];
[btn setBackgroundColor:[UIColor colorWithRed:128.0/255.0f green:0.0/255.0f blue:0.0/255.0f alpha:0.7]];
btn.frame = CGRectMake(100.0, 100.0, 120.0, 50.0);//width and height should be same value
btn.clipsToBounds = YES;
btn.layer.cornerRadius = 20;//half of the width
btn.layer.borderColor=[UIColor redColor].CGColor;
btn.layer.borderWidth=2.0f;
[self.view addSubview:btn];
Below is the image of the button that is related with the above code
You can always play around with code and create the colors that you need for background and border. Hope this would help you out.
btn.clipsToBounds = YES;
i Just added this and it worked for me. I was actually able to set corner radius to UIButton with setting an image to that particular UIButton.
If you are using the image in your button background then you need to set clipsTobounds to true.
button.layer.cornerRadius = 10
button.clipsToBounds = true
You can also have:
btn.clipsToBounds = true;
//create button like this
UIButton *cancel=[[UIButton alloc]initWithFrame:CGRectMake(9, 9,35,35)];
cancel.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"BackX.png"]];
[cancel setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[cancel.layer setBorderColor: [[UIColor blackColor] CGColor]];
[cancel.layer setBorderWidth: 1.0];
cancel.contentMode=UIViewContentModeScaleAspectFill;
cancel.clipsToBounds=YES;
cancel.layer.cornerRadius=8.0;
[cancel addTarget:self action:#selector(cancelbtnclk1:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:cancel];
Before that add QuartzCore Framework and import QuartzCore/CoreAnimation.h in your .h file.
hope it will helps you..
Using UIGraphicsImageRenderer, you can use the cgContext property of UIGraphicsImageRendererContext to access the CGContext and add a rounded path:
UIGraphicsImageRenderer(size: size).image { context in
let rect = CGRect(origin: .zero, size: size)
let clipPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
context.cgContext.addPath(clipPath)
context.cgContext.setFillColor(self.cgColor)
context.cgContext.fillPath()
}
Added as an extension to UIColor:
extension UIColor {
public func image(_ size: CGSize = CGSize(width: 10, height: 10), cornerRadius: CGFloat = 4) -> UIImage {
return UIGraphicsImageRenderer(size: size).image { context in
let rect = CGRect(origin: .zero, size: size)
let clipPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
context.cgContext.addPath(clipPath)
context.cgContext.setFillColor(self.cgColor)
context.cgContext.fillPath()
}
}
}
This approach will also allow you to add a shadow to the button unlike using clipsToBounds.
Set corner Radius in Identity Inspector for Button. See Pic
Set Background image in Attribute Inspector for Button. See pic
May i know how do i add bottom border to UILable only with shadow?
No top, left, right border.
this is not working. Here is the code I've tried but it's not working.
CALayer* layer = [titleLabel layer];
layer.frame = CGRectMake(-1, -1, titleLabel.frame.size.width, 1.0f);
layer.borderWidth=1;
[layer setBorderWidth:2.0f];
[layer setBorderColor:[UIColor blackColor].CGColor];
[layer setShadowOffset:CGSizeMake(-3.0, 3.0)];
[layer setShadowRadius:5.0];
[layer setShadowOpacity:5.0];
Test in this way:
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 50)];
[lbl setText:#"Testo di prova..."];
[lbl setBackgroundColor:[UIColor clearColor]];
[[self view] addSubview:lbl];
[lbl sizeToFit];
CALayer* layer = [lbl layer];
CALayer *bottomBorder = [CALayer layer];
bottomBorder.borderColor = [UIColor darkGrayColor].CGColor;
bottomBorder.borderWidth = 1;
bottomBorder.frame = CGRectMake(-1, layer.frame.size.height-1, layer.frame.size.width, 1);
[bottomBorder setBorderColor:[UIColor blackColor].CGColor];
[layer addSublayer:bottomBorder];
I hope this helps you
Just posting this answers for reference, since the user already got the answer,
Try this code in Swift and let me know how it goes..
func buttomBorder(label: UILabel) -> UILabel {
// For Buttom Border
let frame = label.frame
let bottomLayer = CALayer()
bottomLayer.frame = CGRect(x: 0, y: frame.height - 1, width: frame.width - 2, height: 1)
bottomLayer.backgroundColor = UIColor.lightGray.cgColor
label.layer.addSublayer(bottomLayer)
//For Shadow
label.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).CGColor
label.layer.shadowOffset = CGSizeMake(0.0, 2.0)
label.layer.shadowOpacity = 1.0
label.layer.shadowRadius = 0.0
label.layer.masksToBounds = false
label.layer.cornerRadius = 4.0
return label
}
Function Call:
labelName = buttomBoder(label: labelName)