Why sometimes the View pass through the boundary? - swift

I have a square view , when I drop it sometimes pass through a boundary of a line , if the line is horizontal there is no problem , but some position of it cause the problem.
Here is my codes:
square = UIView(frame: CGRect(x: 100, y: 30, width: 100, height: 100))
square.backgroundColor = UIColor.gray
view.addSubview(square)
animator = UIDynamicAnimator(referenceView: view)
gravity = UIGravityBehavior(items: [square])
gravity.gravityDirection = CGVector(dx: 0, dy: 0.1)
animator.addBehavior(gravity)
collision = UICollisionBehavior(items: [square])
collision.collisionDelegate = self
collision.addBoundary(withIdentifier: "line" as NSCopying, for: line.collisionBoundingPath)
collision.translatesReferenceBoundsIntoBoundary = true
animator.addBehavior(collision)
let itemBehaviour = UIDynamicItemBehavior(items: [square])
itemBehaviour.elasticity = 0.6
animator.addBehavior(itemBehaviour)
Line Shape:
let startingPoint = CGPoint(x: 0, y: 150)
path.move(to: startingPoint)
path.addLine(to: CGPoint(x: 250, y: 250))
UserPath = path
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 3.0
layer.addSublayer(shapeLayer)
Gif describe the prob: **
**Another Gif describe a problem with the boundary:

Related

Swift how to mask shape layer to blur layer

I was making a progress circle, and I want its track path to have a blur effect, is there any way to achieve that?
This is what the original track looks like(the track path is transparent, I want it to be blurred)
And this is my attempt
let outPutViewFrame = CGRect(x: 0, y: 0, width: 500, height: 500)
let circleRadius: CGFloat = 60
let circleViewCenter = CGPoint(x: outPutViewFrame.width / 2 , y: outPutViewFrame.height / 2)
let circleView = UIView()
let progressWidth: CGFloat = 8
circleView.frame.size = CGSize(width: (circleRadius + progressWidth) * 2, height: (circleRadius + progressWidth) * 2)
circleView.center = circleViewCenter
let circleTrackPath = UIBezierPath(arcCenter: CGPoint(x: circleView.frame.width / 2, y: circleView.frame.height / 2), radius: circleRadius, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
let blur = UIBlurEffect(style: .light)
let blurEffect = UIVisualEffectView(effect: blur)
blurEffect.frame = circleView.bounds
blurEffect.mask(withPath: circleTrackPath, inverse: false)
extension UIView {
func mask(withPath path: UIBezierPath, inverse: Bool = false) {
let maskLayer = CAShapeLayer()
if inverse {
path.append(UIBezierPath(rect: self.bounds))
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
}
maskLayer.path = path.cgPath
maskLayer.lineWidth = 5
maskLayer.lineCap = CAShapeLayerLineCap.round
self.layer.mask = maskLayer
}
}
Set maskLayer.fillRule to evenOdd, even when not inversed.
if inverse {
path.append(UIBezierPath(rect: self.bounds))
}
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
create the circleTrackPath by using a big circle and a smaller circle.
let circleCenter = CGPoint(x: circleView.frame.width / 2, y: circleView.frame.height / 2)
let circleTrackPath = UIBezierPath(ovalIn:
CGRect(origin: circleCenter, size: .zero)
.insetBy(dx: circleRadius, dy: circleRadius))
// smaller circle
circleTrackPath.append(CGRect(origin: circleCenter, size: .zero)
.insetBy(dx: circleRadius * 0.8, dy: circleRadius * 0.8))
Set circleTrackPath.usesEvenOddFillRule to true:
circleTrackPath.usesEvenOddFillRule = true
Now you have a blurred full circle. The non-blurred arc part can be implemented as another sublayer.
Here is a MCVE that you can paste into a playground:
let container = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
// change this to a view of your choice
let image = UIImageView(image: UIImage(named: "my_image"))
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .light))
container.addSubview(image)
blur.frame = image.frame
container.addSubview(blur)
let outer = image.bounds.insetBy(dx: 30, dy: 30)
let path = UIBezierPath(ovalIn: outer)
path.usesEvenOddFillRule = true
path.append(UIBezierPath(ovalIn: outer.insetBy(dx: 10, dy: 10)))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
maskLayer.fillRule = .evenOdd
blur.layer.mask = maskLayer
container // <--- playground quick look this
Using my profile pic as the background, this produces:

How to fill out an SKLabelNode with a specific color through animation?

I've been searching the internet for a while now and I couldn't come up with any solution.
I've created a SKLabelNode in my SpriteKit project which displays level numbers.
SKLabelNode:
let levelLbl = SKLabelNode(fontNamed: "LEMON-Bold")
levelLbl.text = "1"
levelLbl.fontSize = 100
levelLbl.fontColor = .grey
levelLbl.zPosition = 2
levelLbl.horizontalAlignmentMode = .center
levelLbl.verticalAlignmentMode = .center
levelLbl.position = CGPoint(x: frame.midX, y: frame.height * 0.75)
addChild(levelLbl)
Screenshot:
Screenshot of my label
I want to fill up the number of my label smoothly with white color from the bottom to the top with an animation so that after x seconds the label is completely white.
I don't want the color to fade in but rather to be filled up similar to a cup of water.
It would be amazing if someone could help me out here!
This is how far I came by using a SKCropNode. My problem is I don't know why I don't see an animation:
func labelAnimation() {
let levelLbl = SKLabelNode(fontNamed: "LEMON-Bold")
let blackRect = SKShapeNode(rectOf: CGSize(width: levelLbl.frame.width, height: levelLbl.frame.height))
blackRect.fillColor = .grey
blackRect.strokeColor = .grey
blackRect.position = CGPoint(x: 0, y: levelLbl.frame.height / 2)
let whiteRect = SKShapeNode(rectOf: CGSize(width: levelLbl.frame.width, height: levelLbl.frame.height))
whiteRect.fillColor = .white
whiteRect.strokeColor = .white
whiteRect.position = CGPoint(x: 0, y: -levelLbl.frame.height / 2)
//Level Node
levelNode.position = CGPoint(x: 0, y: 0)
levelNode.addChild(blackRect)
levelNode.addChild(whiteRect)
//Mask Node
levelLbl.text = "\(levelNr)"
levelLbl.fontSize = 100
levelLbl.horizontalAlignmentMode = .center
levelLbl.verticalAlignmentMode = .center
//Crop Node
cropNode.addChild(levelNode)
cropNode.maskNode = levelLbl
cropNode.position = CGPoint(x: frame.midX, y: frame.midY)
cropNode.zPosition = 5
addChild(cropNode)
//action
let movelevelNode = SKAction.move(to: CGPoint(x: 0, y: frame.height / 3), duration: 8)
levelNode.run(movelevelNode)
}

Create a UIView with rounded bottom edge

I want make view this Bottom roundedenter image description here
I think this is really close to what you want:
The rectView is your initial view;
You can play with the + 50 of quad curve height to adjust the curve
let rectView = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 200))
rectView.backgroundColor = .red
view.addSubview(rectView)
let arcBezierPath = UIBezierPath()
arcBezierPath.move(to: CGPoint(x: 0, y: rectView.frame.height))
arcBezierPath.addQuadCurve(to: CGPoint(x: rectView.frame.width, y: rectView.frame.height), controlPoint: CGPoint(x: rectView.frame.width / 2 , y: rectView.frame.height + 50 ))
arcBezierPath.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = arcBezierPath.cgPath
shapeLayer.fillColor = UIColor.red.cgColor
rectView.layer.insertSublayer(shapeLayer, at: 0)
rectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
rectView.layer.cornerRadius = 10
rectView.layer.shadowRadius = 3
rectView.layer.shadowColor = UIColor.black.cgColor
rectView.layer.shadowOffset = CGSize(width: 0, height: 0)
rectView.layer.shadowOpacity = 0.25
Maybe, even better:
You can create your view:
let rectView = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 200))
view.addSubview(rectView)
Extend UIView
extension UIView {
func addBottomArc(ofHeight height: CGFloat, topCornerRadius: CGFloat) {
let arcBezierPath = UIBezierPath()
arcBezierPath.move(to: CGPoint(x: 0, y: frame.height))
arcBezierPath.addQuadCurve(to: CGPoint(x: frame.width, y: frame.height), controlPoint: CGPoint(x: frame.width / 2 , y: frame.height + height ))
arcBezierPath.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = arcBezierPath.cgPath
shapeLayer.fillColor = UIColor.red.cgColor
layer.insertSublayer(shapeLayer, at: 0)
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
layer.cornerRadius = topCornerRadius
}
}
So you can set the height and the corner radius like this:
rectView.addBottomArc(ofHeight: 50, topCornerRadius: 15)
and than add the shadow you need to your view:
rectView.layer.shadowRadius = 3
rectView.layer.shadowColor = UIColor.black.cgColor
rectView.layer.shadowOffset = CGSize(width: 0, height: 0)
rectView.layer.shadowOpacity = 0.25

How to add rounded corners to a UIView with gradient applied?

I've written custom subclass of UIView that draws a gradient inside of it:
import UIKit
#IBDesignable
class PlayerCellView: UIView {
var startColor: UIColor = UIColor(red:0.20, green:0.75, blue:1.00, alpha:1.00)
var endColor: UIColor = UIColor(red:0.07, green:0.42, blue:1.00, alpha:1.00)
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
let colors = [startColor.cgColor, endColor.cgColor]
let locations : [CGFloat] = [0.0, 1.0]
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: locations)
context?.drawLinearGradient(gradient!, start: CGPoint.zero, end: CGPoint(x: 0, y: self.bounds.height), options: .drawsAfterEndLocation)
}
}
How would I now apply rounded edges to this view?
use this extension method:
extension UIView {
func addGradientLayer(with colors: [CGColor], startPoint: CGPoint, endPoint: CGPoint, locations: [NSNumber] = [0.0, 1.0], frame: CGRect = CGRect.zero) {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = colors
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
gradientLayer.locations = locations
gradientLayer.frame = frame
gradientLayer.cornerRadius = self.layer.cornerRadius
self.layer.insertSublayer(gradientLayer, at: 0)
}
}
You can add a rounded corner UIView with gradient using the following method:
func makeCircularGradient(){
let circularView = UIView()
self.view.addSubview(circularView)
circularView.translatesAutoresizingMaskIntoConstraints = false
circularView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
circularView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
circularView.widthAnchor.constraint(equalToConstant: 200).isActive = true
circularView.heightAnchor.constraint(equalToConstant: 200).isActive = true
self.view.layoutIfNeeded()
let gradient = CAGradientLayer()
gradient.frame = circularView.bounds
gradient.colors = [UIColor.blue.cgColor,
UIColor.red.cgColor]
gradient.startPoint = CGPoint(x: 0, y: 1)
gradient.endPoint = CGPoint(x: 1, y: 0)
circularView.layer.addSublayer(gradient)
let circularPath = CGMutablePath()
circularPath.addArc(center: CGPoint.init(x: circularView.bounds.width / 2, y: circularView.bounds.height / 2), radius: circularView.bounds.width / 2, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true, transform: .identity)
let maskLayer = CAShapeLayer()
maskLayer.path = circularPath
maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.fillColor = UIColor.red.cgColor
circularView.layer.mask = maskLayer
}
Make a UIView and set the constraints as you see fit
Make a CAGradientLayer with colours of your choice
Make a circular mask layer and apply to the UIView.
The result will be something like below:
Making a curved corner radius View with gradient is a little bit more difficult than the circular one. And can be done like :
func makeCurvedCornerGradient(){
let circularView = UIView()
self.view.addSubview(circularView)
circularView.translatesAutoresizingMaskIntoConstraints = false
circularView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
circularView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
circularView.widthAnchor.constraint(equalToConstant: 200).isActive = true
circularView.heightAnchor.constraint(equalToConstant: 200).isActive = true
self.view.layoutIfNeeded()
let gradient = CAGradientLayer()
gradient.frame = circularView.bounds
gradient.colors = [UIColor.blue.cgColor,
UIColor.red.cgColor]
gradient.startPoint = CGPoint(x: 0, y: 1)
gradient.endPoint = CGPoint(x: 1, y: 0)
circularView.layer.addSublayer(gradient)
let circularPath = CGMutablePath()
circularPath.move(to: CGPoint.init(x: 20, y: 0))
circularPath.addLine(to: CGPoint.init(x: circularView.bounds.width - 20, y: 0))
circularPath.addQuadCurve(to: CGPoint.init(x: circularView.bounds.width, y: 20), control: CGPoint.init(x: circularView.bounds.width, y: 0))
circularPath.addLine(to: CGPoint.init(x: circularView.bounds.width, y: circularView.bounds.height - 20))
circularPath.addQuadCurve(to: CGPoint.init(x: circularView.bounds.width - 20, y: circularView.bounds.height), control: CGPoint.init(x: circularView.bounds.width, y: circularView.bounds.height))
circularPath.addLine(to: CGPoint.init(x: 20, y: circularView.bounds.height))
circularPath.addQuadCurve(to: CGPoint.init(x: 0, y: circularView.bounds.height - 20), control: CGPoint.init(x: 0, y: circularView.bounds.height))
circularPath.addLine(to: CGPoint.init(x: 0, y: 20))
circularPath.addQuadCurve(to: CGPoint.init(x: 20, y: 0), control: CGPoint.init(x: 0, y: 0))
let maskLayer = CAShapeLayer()
maskLayer.path = circularPath
maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.fillColor = UIColor.red.cgColor
circularView.layer.mask = maskLayer
}
Change the values according to your need.
Output:
You can possibly use the Following Code:
func setGradientBorderWidthColor(view: UIView)
{
let gradientColor = CAGradientLayer()
gradientColor.frame = CGRect(origin: CGPoint.zero, size: view.frame.size)
gradientColor.colors = [UIColor.blue.cgColor, UIColor.green.cgColor]
let pathWithRadius = UIBezierPath(roundedRect:view.bounds, byRoundingCorners:[.topRight, .topLeft, .bottomLeft , .bottomRight], cornerRadii: CGSize(width: 20.0, height: 20.0))
let shape = CAShapeLayer()
shape.lineWidth = 3
shape.path = pathWithRadius.cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradientColor.mask = shape
view.layer.mask = shape
view.layer.addSublayer(gradientColor)
}
You can use this as:
let myView = UIView()
myView.backgroundColor = UIColor.gray
myView.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
setGradientBorderWidthColor(view: myView)
self.view.addSubview(myView)
Output is like this:
Hope this Helps!

Swift draw shadow to a uibezier path

I have a strange question. Even though I did read a lot of tutorials on how to do this, the final result only shows the bezier line, not any shadow whatsoever. My code is pretty simple :
let borderLine = UIBezierPath()
borderLine.moveToPoint(CGPoint(x:0, y: y! - 1))
borderLine.addLineToPoint(CGPoint(x: x!, y: y! - 1))
borderLine.lineWidth = 2
UIColor.blackColor().setStroke()
borderLine.stroke()
let shadowLayer = CAShapeLayer()
shadowLayer.shadowOpacity = 1
shadowLayer.shadowOffset = CGSize(width: 0,height: 1)
shadowLayer.shadowColor = UIColor.redColor().CGColor
shadowLayer.shadowRadius = 1
shadowLayer.masksToBounds = false
shadowLayer.shadowPath = borderLine.CGPath
self.layer.addSublayer(shadowLayer)
What am I doing wrong as I dont seem to see anything wrong but of course I am wrong since no shadow appears. The function is drawRect, basic UIVIew no extra anything in there, x and y are the width and height of the frame. Many thanks in advance!
I take this example straight from my PaintCode-app. Hope this helps.
//// General Declarations
let context = UIGraphicsGetCurrentContext()
//// Shadow Declarations
let shadow = UIColor.blackColor()
let shadowOffset = CGSizeMake(3.1, 3.1)
let shadowBlurRadius: CGFloat = 5
//// Bezier 2 Drawing
var bezier2Path = UIBezierPath()
bezier2Path.moveToPoint(CGPointMake(30.5, 90.5))
bezier2Path.addLineToPoint(CGPointMake(115.5, 90.5))
CGContextSaveGState(context)
CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, (shadow as UIColor).CGColor)
UIColor.blackColor().setStroke()
bezier2Path.lineWidth = 1
bezier2Path.stroke()
CGContextRestoreGState(context)
I prefer the way to add a shadow-sublayer. You can easily use the following function (Swift 3.0):
func createShadowLayer() -> CALayer {
let shadowLayer = CALayer()
shadowLayer.shadowColor = UIColor.red.cgColor
shadowLayer.shadowOffset = CGSize.zero
shadowLayer.shadowRadius = 5.0
shadowLayer.shadowOpacity = 0.8
shadowLayer.backgroundColor = UIColor.clear.cgColor
return shadowLayer
}
And finally, you just add it to your line path (CAShapeLayer):
let line = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 50, y: 100))
path.addLine(to: CGPoint(x: 100, y: 50))
line.path = path.cgPath
line.strokeColor = UIColor.blue.cgColor
line.fillColor = UIColor.clear.cgColor
line.lineWidth = 2.0
view.layer.addSublayer(line)
let shadowSubLayer = createShadowLayer()
shadowSubLayer.insertSublayer(line, at: 0)
view.layer.addSublayer(shadowSubLayer)
I am using the shadow properties of my shape layer to add shadow to it. The best part of this approach is that I don't have to provide a path explicitly. The shadow follows the path of the layer. I am also animating the layer by changing path. In that case too the shadow animates seamlessly without a single line of code.
Here is what I am doing (Swift 4.2)
shapeLayer.path = curveShapePath(postion: initialPosition)
shapeLayer.strokeColor = UIColor.clear.cgColor
shapeLayer.fillColor = shapeBackgroundColor
self.layer.addSublayer(shapeLayer)
if shadow {
shapeLayer.shadowRadius = 5.0
shapeLayer.shadowColor = UIColor.gray.cgColor
shapeLayer.shadowOpacity = 0.8
}
The curveShapePath method is the one that returns the path and is defined as follows:
func curveShapePath(postion: CGFloat) -> CGPath {
let height: CGFloat = 37.0
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0)) // start top left
path.addLine(to: CGPoint(x: (postion - height * 2), y: 0)) // the beginning of the trough
// first curve down
path.addCurve(to: CGPoint(x: postion, y: height),
controlPoint1: CGPoint(x: (postion - 30), y: 0), controlPoint2: CGPoint(x: postion - 35, y: height))
// second curve up
path.addCurve(to: CGPoint(x: (postion + height * 2), y: 0),
controlPoint1: CGPoint(x: postion + 35, y: height), controlPoint2: CGPoint(x: (postion + 30), y: 0))
// complete the rect
path.addLine(to: CGPoint(x: self.frame.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.height))
path.close()
return path.cgPath
}