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

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
}
}

Related

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()

Custom View Drawing - Hole inside a View

How to draw View like this.
After research I got context.fillRects method can be used. But how to find the exact rects for this.
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(UIColor.red.cgColor)
context?.setAlpha(0.5)
context?.fill([<#T##rects: [CGRect]##[CGRect]#>])
How to achieve this result?
Background: Blue.
Overlay(Purple): 50% opacity that contains square hole in the center
First create your view and then draw everything with two UIBezierPaths: one is describing the inside rect (the hole) and the other one runs along the borders on your screen (externalPath). This way of drawing ensures that the blue rect in the middle is a true hole and not drawn on top of the purple view.
let holeWidth: CGFloat = 200
let hollowedView = UIView(frame: view.frame)
hollowedView.backgroundColor = UIColor.clear
//Initialise the layer
let hollowedLayer = CAShapeLayer()
//Draw your two paths and append one to the other
let holePath = UIBezierPath(rect: CGRect(origin: CGPoint(x: (view.frame.width - holeWidth) / 2, y: (view.frame.height - holeWidth) / 2), size: CGSize(width: holeWidth, height: holeWidth)))
let externalPath = UIBezierPath(rect: hollowedView.frame).reversing()
holePath.append(externalPath)
holePath.usesEvenOddFillRule = true
//Assign your path to the path property of your layer
hollowedLayer.path = holePath.cgPath
hollowedLayer.fillColor = UIColor.purple.cgColor
hollowedLayer.opacity = 0.5
//Add your hollowedLayer to the layer of your hollowedView
hollowedView.layer.addSublayer(hollowedLayer)
view.addSubview(hollowedView)
The result looks like this :
Create a custom UIView with background color blue.
class CustomView: UIView {
// Try adding a rect and fill color.
override func draw(_ rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
ctx!.beginPath()
//Choose the size based on the size required.
ctx?.addRect(CGRect(x: 20, y: 20, width: rect.maxX - 40, height: rect.maxY - 40))
ctx!.closePath()
ctx?.setFillColor(UIColor.red.cgColor)
ctx!.fillPath()
}
}
I just ended up with this.
Code:
createHoleOnView()
let blurView = createBlurEffect(style: style)
self.addSubview(blurView)
Method Create Hole:
private func createHoleOnView() {
let maskView = UIView(frame: self.frame)
maskView.clipsToBounds = true;
maskView.backgroundColor = UIColor.clear
func holeRect() -> CGRect {
var holeRect = CGRect(x: 0, y: 0, width: scanViewSize.rawValue.width, height: scanViewSize.rawValue.height)
let midX = holeRect.midX
let midY = holeRect.midY
holeRect.origin.x = maskView.frame.midX - midX
holeRect.origin.y = maskView.frame.midY - midY
self.holeRect = holeRect
return holeRect
}
let outerbezierPath = UIBezierPath.init(roundedRect: self.bounds, cornerRadius: 0)
let holePath = UIBezierPath(roundedRect: holeRect(), cornerRadius: holeCornerRadius)
outerbezierPath.append(holePath)
outerbezierPath.usesEvenOddFillRule = true
let hollowedLayer = CAShapeLayer()
hollowedLayer.fillRule = kCAFillRuleEvenOdd
hollowedLayer.fillColor = outerColor.cgColor
hollowedLayer.path = outerbezierPath.cgPath
if self.holeStyle == .none {
hollowedLayer.opacity = 0.8
}
maskView.layer.addSublayer(hollowedLayer)
switch self.holeStyle {
case .none:
self.addSubview(maskView)
break
case .blur(_):
self.mask = maskView;
break
}
}
UIView's Extension function for Create Blur:
internal func createBlurEffect(style: UIBlurEffectStyle = .extraLight) -> UIView {
let blurEffect = UIBlurEffect(style: style)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = self.bounds
return blurEffectView
}

Inner shadow in UITextField with rounded corners

I want to implement this UITextField design:
In Zeplin here is the properties of the shadow:
What I have tried ?
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = self.frame.size.height/2
self.addInnerShadow()
}
private func addInnerShadow() {
let innerShadow = CALayer()
innerShadow.frame = bounds
// Shadow path (1pt ring around bounds)
let path = UIBezierPath(rect: innerShadow.bounds.insetBy(dx: -1, dy: -1))
let cutout = UIBezierPath(rect: innerShadow.bounds).reversing()
path.append(cutout)
innerShadow.shadowPath = path.cgPath
innerShadow.masksToBounds = true
// Shadow properties
innerShadow.shadowColor = UIColor.black.cgColor
innerShadow.shadowOffset = CGSize(width: 0, height: 3)
innerShadow.shadowOpacity = 0.05
innerShadow.shadowRadius = 3
innerShadow.cornerRadius = self.frame.size.height/2
layer.addSublayer(innerShadow)
}
result:
Update:
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = self.frame.size.height/2
self.addInnerShadow()
}
private func addInnerShadow() {
let innerShadow = CALayer()
innerShadow.frame = bounds
// Shadow path (1pt ring around bounds)
let path = UIBezierPath(roundedRect: innerShadow.bounds.insetBy(dx: -1, dy: -1), cornerRadius: self.frame.size.height/2)
let cutout = UIBezierPath(rect: innerShadow.bounds).reversing()
path.append(cutout)
innerShadow.shadowPath = path.cgPath
innerShadow.masksToBounds = true
// Shadow properties
innerShadow.shadowColor = UIColor.black.cgColor
innerShadow.shadowOffset = CGSize(width: 0, height: 3)
innerShadow.shadowOpacity = 0.05
innerShadow.shadowRadius = 3
//innerShadow.cornerRadius = self.frame.size.height/2
layer.addSublayer(innerShadow)
}
result:
the corner radius is causing a problem because the path is still rectangular and the shadow looks different
Just use a rounded rect path:
private func addInnerShadow() {
let innerShadow = CALayer()
innerShadow.frame = bounds
// Shadow path (1pt ring around bounds)
let radius = self.frame.size.height/2
let path = UIBezierPath(roundedRect: innerShadow.bounds.insetBy(dx: -1, dy:-1), cornerRadius:radius)
let cutout = UIBezierPath(roundedRect: innerShadow.bounds, cornerRadius:radius).reversing()
path.append(cutout)
innerShadow.shadowPath = path.cgPath
innerShadow.masksToBounds = true
// Shadow properties
innerShadow.shadowColor = UIColor.black.cgColor
innerShadow.shadowOffset = CGSize(width: 0, height: 3)
innerShadow.shadowOpacity = 0.15
innerShadow.shadowRadius = 3
innerShadow.cornerRadius = self.frame.size.height/2
layer.addSublayer(innerShadow)
}
To attain this - I added a uiimageview and input the image as the shadow text cell, then placed a label on top of the uiimageview with a clear background so you are able to see the shadow image through the text. This was all done with storyboard so I have no code to show and am not allowed to upload an image yet. Hope this helps.

Swift: UIButton Customization

I would like to move the code to customize UIButtons out of my viewcontroller classes as a best practice. The code I have below is to add a white border to UIButtons and I would like to easily call it on buttons throughout my project.
//White Border
let passwordBorder = CALayer()
let width = CGFloat(5.0)
passwordBorder.borderColor = UIColor.whiteColor().CGColor
passwordBorder.frame = CGRect(x: 0, y: 0, width: passwordField.frame.size.width, height: passwordField.frame.size.height)
passwordBorder.borderWidth = width
passwordField.layer.addSublayer(passwordBorder)
passwordField.layer.masksToBounds = true
How would I put this code into a helper function so I could call it easily?
I am new to coding and am having trouble with helper functions on anything UI. Thanks!
Take a look at Swift's Extensions. You could pretty easily do something like
extension UIButton {
func setPasswordBorderColor(borderColor: UIColor) {
//White Border
let passwordBorder = CALayer()
let width = CGFloat(5.0)
passwordBorder.borderColor = borderColor.CGColor
passwordBorder.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
passwordBorder.borderWidth = width
self.layer.addSublayer(passwordBorder)
self.layer.masksToBounds = true
}
}
import UIKit
//
#IBDesignable
class CustomBorderButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: UIColor? {
didSet {
layer.borderColor = borderColor?.CGColor
}
}
}
Without Code you can configure your button
Select Your button then Select Identity Inspector then add
"User Defined Runtime Attribute".See screen Shot for more details
Add CALayer+XibConfiguration.h & CALayer+XibConfiguration.m in your Project
for CALayer+XibConfiguration open this link & download CALayer+XibConfiguration
There are several ways by which you can achieve this like using category, subclassing UIButton, or create a function in a class (may be base class inherited by all other classes.), etc.
By using function you ca do
func cutomizeButton(frame : CGRect, title : String) -> UIButton {
let button : UIButton = UIButton(type: .Custom)
button.frame = frame
button.layer.borderWidth = 5.0
button.layer.borderColor = UIColor.whiteColor().CGColor;
button.layer.masksToBounds = true
button.titleLabel?.text = title;
//do other stuff
return button;
}
In helper class make method like
func customiseButton(button:UIButton){
//White Border
let passwordBorder = CALayer()
let width = CGFloat(5.0)
passwordBorder.borderColor = UIColor.whiteColor().CGColor
passwordBorder.frame = CGRect(x: 0, y: 0, width: button.frame.size.width, height: button.frame.size.height)
passwordBorder.borderWidth = width
button.layer.addSublayer(passwordBorder)
button.layer.masksToBounds = true
}
and in ViewController call this method as
HelperClass().customiseButton(passwordField)
You can try with this Extension for round button:
extension UIButton{
func roundCorners(corners:UIRectCorner, radius: CGFloat){
let borderLayer = CAShapeLayer()
borderLayer.frame = self.layer.bounds
borderLayer.strokeColor = UIColor.green.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.lineWidth = 10.5
let path = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius))
borderLayer.path = path.cgPath
self.layer.addSublayer(borderLayer)
}
}

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