Swift: add custom view inside CALayer - ios

I'm trying to add a custom view inside a CALayer.
http://i.imgur.com/sYzQ4kX.png
And I want to put inside some buttons and labels, but I'm afraid I'm not able to do it. I make the CALabel like this:
func addRectangleToLayer(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat, layer: CALayer, index: UInt32 ) {
var sublayer = CALayer()
sublayer.backgroundColor = UIColor.whiteColor().CGColor
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = UIColor.blackColor().CGColor
sublayer.shadowOpacity = 0.8;
sublayer.cornerRadius = 12.0;
sublayer.frame = CGRectMake(x, y, width, height);
sublayer.borderColor = UIColor.blackColor().CGColor;
sublayer.borderWidth = 0.5;
//An example
let label = UILabel()
label.text = "LOREN"
sublayer.contents = label
layer.insertSublayer(sublayer, atIndex: index)
}
Is it possible to do what I want to?
Thanks a lot and excuse me for my english level

If you create a UIView, you'll have access to its layer property for this purpose:
let label = UILabel()
label.text = "LOREN"
var sublayer = label.layer;
// .. the rest of your layer initialization
sublayer.backgroundColor = UIColor.whiteColor().CGColor
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = UIColor.blackColor().CGColor
sublayer.shadowOpacity = 0.8;
sublayer.cornerRadius = 12.0;
sublayer.frame = CGRectMake(x, y, width, height);
sublayer.borderColor = UIColor.blackColor().CGColor;
sublayer.borderWidth = 0.5;
// .. ended original source initialization
layer.insertSublayer(sublayer, atIndex: index)

Related

How To Draw the Oval Shape Layer to the UITextField

I need to draw the Oval shape to my textfield , I don't know how to draw using layer, if anyone help me for this design shadow,
Here's the Objective-C translation of Anton's answer since question is tagged for Objective-C and not Swift:
CAShapeLayer *shadowLayer = [[CAShapeLayer alloc] init];
shadowLayer.fillColor = [UIColor lightGrayColor].CGColor;
shadowLayer.lineWidth = 0.0f;
CGSize shadowSize = CGSizeMake(textField.bounds.size.width + 40, 40);
CGRect shawdowBounds = CGRectMake(0, 0, shadowSize.width, shadowSize.height);
shadowLayer.path = [UIBezierPath bezierPathWithOvalInRect:shawdowBounds].CGPath;
shadowLayer.bounds = shawdowBounds;
shadowLayer.position = CGPointMake(CGRectGetMidX(textField.bounds), CGRectGetMaxY(textField.bounds));
[containerView.layer insertSublayer:shadowLayer atIndex0];
You can use this code as an example:
let shadowLayer = CAShapeLayer()
// set your shadow color here
shadowLayer.fillColor = UIColor.lightGray.cgColor
shadowLayer.lineWidth = 0
// calc size of your shadow according to your design
let shadowSize = CGSize(width: textField.bounds.width + 40, height: 20)
let shadowBounds = CGRect(origin: .zero, size: shadowSize)
shadowLayer.path = UIBezierPath(ovalIn: shadowBounds).cgPath
shadowLayer.bounds = shadowBounds
shadowLayer.position = CGPoint(x: textField.bounds.midX, y: textField.bounds.maxY)
containerView.layer.insertSublayer(shadowLayer, at: 0)
Here I assume that you have some containerView, containing your text field as a subview and keep reference to the text field in variable called textField.

Swift create extension for textfield shadow code

I have multiple textfield but all the textfield have same shadow effects, I need to use extension for that shadow code and use it shortly in viewdid load.
below code I am using
//MARK - Email TextField
email_textfield.borderStyle = .none
email_textfield.backgroundColor = UIColor.groupTableViewBackground // Use anycolor that give you a 2d look.
//To apply corner radius
email_textfield.layer.cornerRadius = email_textfield.frame.size.height / 2
//To apply border
email_textfield.layer.borderWidth = 0.25
email_textfield.layer.borderColor = UIColor.white.cgColor
//To apply Shadow
email_textfield.layer.shadowOpacity = 1
email_textfield.layer.shadowRadius = 1.0
email_textfield.layer.shadowOffset = CGSize.zero // Use any CGSize
email_textfield.layer.shadowColor = UIColor.lightGray.cgColor
//To apply padding
let paddingView : UIView = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: email_textfield.frame.height))
email_textfield.leftView = paddingView
email_textfield.leftViewMode = UITextFieldViewMode.always
you can create extension like this
extension UITextField {
func applyCustomEffect() {
self.borderStyle = .none
self.backgroundColor = UIColor.groupTableViewBackground // Use anycolor that give you a 2d look.
//To apply corner radius
self.layer.cornerRadius = self.frame.size.height / 2
//To apply border
self.layer.borderWidth = 0.25
self.layer.borderColor = UIColor.white.cgColor
//To apply Shadow
self.layer.shadowOpacity = 1
self.layer.shadowRadius = 1.0
self.layer.shadowOffset = CGSize.zero // Use any CGSize
self.layer.shadowColor =
UIColor.lightGray.cgColor
self.layer.sublayerTransform = CATransform3DMakeTranslation(20, 0, 0)
}
}
And apply this effect like below
email_textfield.applyCustomEffect()

CAShapeLayer shadow

Given the following CAShapeLayer is it possible to add a drop shadow like the following image at the tip?
I'm using a UIBezierPath to draw the bars.
- (CAShapeLayer *)gaugeCircleLayer {
if (_gaugeCircleLayer == nil) {
_gaugeCircleLayer = [CAShapeLayer layer];
_gaugeCircleLayer.lineWidth = self.gaugeWidth;
_gaugeCircleLayer.fillColor = [UIColor clearColor].CGColor;
_gaugeCircleLayer.strokeColor = self.gaugeTintColor.CGColor;
_gaugeCircleLayer.strokeStart = 0.0f;
_gaugeCircleLayer.strokeEnd = self.value;
_gaugeCircleLayer.lineCap = kCALineCapRound;
_gaugeCircleLayer.masksToBounds = NO;
_gaugeCircleLayer.cornerRadius = 8.0;
_gaugeCircleLayer.shadowRadius = 8.0;
_gaugeCircleLayer.shadowColor = [UIColor blackColor].CGColor;
_gaugeCircleLayer.shadowOpacity = 0.5;
_gaugeCircleLayer.shadowOffset = CGSizeMake(0.0, 0.0);
_gaugeCircleLayer.path = [self circlPathForCurrentGaugeStyle].CGPath;
}
return _gaugeCircleLayer;
}
It will need to be applied to this UIBezierPath:
- (UIBezierPath *)insideCirclePath {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:CGRectGetWidth(self.bounds) / 2.0f
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
_titleTextLabel.textColor = self.gaugeTintColor;
return path;
}
Very roughly:
let arc1 = CAShapeLayer()
arc1.lineWidth = 20.0
arc1.path = UIBezierPath(ovalInRect: CGRectMake(10, 10, 80, 80)).CGPath
arc1.strokeStart = 0
arc1.strokeEnd = 0.5
arc1.strokeColor = UIColor.grayColor().CGColor
arc1.fillColor = UIColor.clearColor().CGColor
layer.addSublayer(arc1)
let cap = CAShapeLayer()
cap.shadowColor = UIColor.blackColor().CGColor
cap.shadowRadius = 8.0
cap.shadowOpacity = 0.9
cap.shadowOffset = CGSize(width: 0, height: 0)
cap.path = UIBezierPath(ovalInRect: CGRectMake(0, 40, 20, 20)).CGPath
cap.fillColor = UIColor.grayColor().CGColor
layer.addSublayer(cap)
let arc2 = CAShapeLayer()
arc2.lineWidth = 20.0
arc2.path = UIBezierPath(ovalInRect: CGRectMake(10, 10, 80, 80)).CGPath
arc2.strokeStart = 0.5
arc2.strokeEnd = 1.0
arc2.strokeColor = UIColor.grayColor().CGColor
arc2.fillColor = UIColor.clearColor().CGColor
layer.addSublayer(arc2)
I think you need two arcs, and one circle for cap with shadow.

Can I add a border to an SKSpriteNode, similar to UIView?

I'm interested if an SKSpriteNode can be made to imitate the behavior of a UIView where I can specify border and corner radius?
self.view.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.view.layer.borderWidth = 2;
self.view.layer.cornerRadius = 2;
self.view.layer.masksToBounds = YES;
The following is a simple, dropin extension for SKSpriteNode that will allow you to draw a border with a given color around your node.
import SpriteKit
extension SKSpriteNode {
func drawBorder(color: UIColor, width: CGFloat) {
let shapeNode = SKShapeNode(rect: frame)
shapeNode.fillColor = .clear
shapeNode.strokeColor = color
shapeNode.lineWidth = width
addChild(shapeNode)
}
}
Not natively. Though you can just add a SKShapeNode as child whose path you create with CGPathCreateWithRoundedRect.
extension SKSpriteNode {
func drawBorder(color: UIColor, width: CGFloat) {
for layer in self.children {
if layer.name == "border" {
layer.removeFromParent()
}
}
let imageSize = self.texture?.size()
let lineWidth = (imageSize!.width / self.size.width) * width
let shapeNode = SKShapeNode(rect: CGRect(x: -imageSize!.width/2, y: -imageSize!.height/2, width: imageSize!.width, height: imageSize!.height))
shapeNode.fillColor = .clear
shapeNode.strokeColor = color
shapeNode.lineWidth = lineWidth
shapeNode.name = "border"
shapeNode.zPosition = 1001
self.addChild(shapeNode)
self.zPosition = 1000
}
}

Add a border outside of a UIView (instead of inside)

If a add a border of a view using code in a view like
self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = 2.0f;
the border is added inside the view like the following:
the right view is the original view, as you can see, the black area of bordered view is less than the original one. but what I want to get is a border outside of original view, like this:. the black area is equal to original one, how can I implement it?
Unfortunately, there isn't simply a little property you can set to align the border to the outside. It draws aligned to the inside because the UIViews default drawing operations draw within its bounds.
The simplest solution that comes to mind would be to expand the UIView by the size of the border width when applying the border:
CGFloat borderWidth = 2.0f;
self.frame = CGRectInset(self.frame, -borderWidth, -borderWidth);
self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = borderWidth;
With the above accepted best answer i made experiences with such not nice results and unsightly edges:
So i will share my UIView Swift extension with you, that uses a UIBezierPath instead as border outline – without unsightly edges (inspired by #Fattie):
// UIView+BezierPathBorder.swift
import UIKit
extension UIView {
fileprivate var bezierPathIdentifier:String { return "bezierPathBorderLayer" }
fileprivate var bezierPathBorder:CAShapeLayer? {
return (self.layer.sublayers?.filter({ (layer) -> Bool in
return layer.name == self.bezierPathIdentifier && (layer as? CAShapeLayer) != nil
}) as? [CAShapeLayer])?.first
}
func bezierPathBorder(_ color:UIColor = .white, width:CGFloat = 1) {
var border = self.bezierPathBorder
let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:self.layer.cornerRadius)
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
if (border == nil) {
border = CAShapeLayer()
border!.name = self.bezierPathIdentifier
self.layer.addSublayer(border!)
}
border!.frame = self.bounds
let pathUsingCorrectInsetIfAny =
UIBezierPath(roundedRect: border!.bounds, cornerRadius:self.layer.cornerRadius)
border!.path = pathUsingCorrectInsetIfAny.cgPath
border!.fillColor = UIColor.clear.cgColor
border!.strokeColor = color.cgColor
border!.lineWidth = width * 2
}
func removeBezierPathBorder() {
self.layer.mask = nil
self.bezierPathBorder?.removeFromSuperlayer()
}
}
Example:
let view = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 100))
view.layer.cornerRadius = view.frame.width / 2
view.backgroundColor = .red
//add white 2 pixel border outline
view.bezierPathBorder(.white, width: 2)
//remove border outline (optional)
view.removeBezierPathBorder()
For a Swift implementation, you can add this as a UIView extension.
extension UIView {
struct Constants {
static let ExternalBorderName = "externalBorder"
}
func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.whiteColor()) -> CALayer {
let externalBorder = CALayer()
externalBorder.frame = CGRectMake(-borderWidth, -borderWidth, frame.size.width + 2 * borderWidth, frame.size.height + 2 * borderWidth)
externalBorder.borderColor = borderColor.CGColor
externalBorder.borderWidth = borderWidth
externalBorder.name = Constants.ExternalBorderName
layer.insertSublayer(externalBorder, atIndex: 0)
layer.masksToBounds = false
return externalBorder
}
func removeExternalBorders() {
layer.sublayers?.filter() { $0.name == Constants.ExternalBorderName }.forEach() {
$0.removeFromSuperlayer()
}
}
func removeExternalBorder(externalBorder: CALayer) {
guard externalBorder.name == Constants.ExternalBorderName else { return }
externalBorder.removeFromSuperlayer()
}
}
Ok, there already is an accepted answer but I think there is a better way to do it, you just have to had a new layer a bit larger than your view and do not mask it to the bounds of the view's layer (which actually is the default behaviour). Here is the sample code :
CALayer * externalBorder = [CALayer layer];
externalBorder.frame = CGRectMake(-1, -1, myView.frame.size.width+2, myView.frame.size.height+2);
externalBorder.borderColor = [UIColor blackColor].CGColor;
externalBorder.borderWidth = 1.0;
[myView.layer addSublayer:externalBorder];
myView.layer.masksToBounds = NO;
Of course this is if you want your border to be 1 unity large, if you want more you adapt the borderWidth and the frame of the layer accordingly.
This is better than using a second view a bit larger as a CALayer is lighter than a UIView and you don't have do modify the frame of myView, which is good for instance if myView is aUIImageView
N.B : For me the result was not perfect on simulator (the layer was not exactly at the right position so the layer was thicker on one side sometimes) but was exactly what is asked for on real device.
EDIT
Actually the problem I talk about in the N.B was just because I had reduced the screen of the simulator, on normal size there is absolutely no issue
Hope it helps
Well there is no direct method to do it
You can consider some workarounds.
Change and increase the frame and add bordercolor as you did
Add a view behind the current view with the larger size so that it appears as border.Can be worked as a custom class of view
If you dont need a definite border (clearcut border) then you can depend on shadow for the purpose
[view1 setBackgroundColor:[UIColor blackColor]];
UIColor *color = [UIColor yellowColor];
view1.layer.shadowColor = [color CGColor];
view1.layer.shadowRadius = 10.0f;
view1.layer.shadowOpacity = 1;
view1.layer.shadowOffset = CGSizeZero;
view1.layer.masksToBounds = NO;
Swift 5
extension UIView {
fileprivate struct Constants {
static let externalBorderName = "externalBorder"
}
func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) -> CALayer {
let externalBorder = CALayer()
externalBorder.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + 2 * borderWidth, height: frame.size.height + 2 * borderWidth)
externalBorder.borderColor = borderColor.cgColor
externalBorder.borderWidth = borderWidth
externalBorder.name = Constants.ExternalBorderName
layer.insertSublayer(externalBorder, at: 0)
layer.masksToBounds = false
return externalBorder
}
func removeExternalBorders() {
layer.sublayers?.filter() { $0.name == Constants.externalBorderName }.forEach() {
$0.removeFromSuperlayer()
}
}
func removeExternalBorder(externalBorder: CALayer) {
guard externalBorder.name == Constants.externalBorderName else { return }
externalBorder.removeFromSuperlayer()
}
}
Increase the width and height of view's frame with border width before adding the border:
float borderWidth = 2.0f
CGRect frame = self.frame;
frame.width += borderWidth;
frame.height += borderWidth;
self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = 2.0f;
I liked solution of #picciano
If you want exploding circle instead of square replace addExternalBorder function with:
func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) {
let externalBorder = CALayer()
externalBorder.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + 2 * borderWidth, height: frame.size.height + 2 * borderWidth)
externalBorder.borderColor = borderColor.cgColor
externalBorder.borderWidth = borderWidth
externalBorder.cornerRadius = (frame.size.width + 2 * borderWidth) / 2
externalBorder.name = Constants.ExternalBorderName
layer.insertSublayer(externalBorder, at: 0)
layer.masksToBounds = false
}
There is actually a very simple solution. Just set them both like this:
view.layer.borderWidth = 5
view.layer.borderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5).cgColor
view.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.25).cgColor
How I placed a border around my UI view (main - SubscriptionAd) in Storyboard is to place it inside another UI view (background - BackgroundAd). The Background UIView has a background colour that matches the border colour i want, and the Main UIView has constraints value 2 from each side.
I will link the background view to my ViewController and then turn the border on and off by changing the background colour.
I liked solution of #picciano & #Maksim Kniazev. We can also create annular border with following:
func addExternalAnnularBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) {
let externalBorder = CALayer()
externalBorder.frame = CGRect(x: -borderWidth*2, y: -borderWidth*2, width: frame.size.width + 4 * borderWidth, height: frame.size.height + 4 * borderWidth)
externalBorder.borderColor = borderColor.cgColor
externalBorder.borderWidth = borderWidth
externalBorder.cornerRadius = (frame.size.width + 4 * borderWidth) / 2
externalBorder.name = Constants.ExternalBorderName
layer.insertSublayer(externalBorder, at: 0)
layer.masksToBounds = false
}

Resources