Shrink The Shape Keep Margin in Swift - ios

I draw a shape, I want shrink it with a specific value, I use transform scale with an anchor point but it isn't the result that I'm expected. I want space between the edges of the original shape and new shape have the same value.
here is the code:
func drawShape(){
let w = self.frame.width
let h = self.frame.height
let corner : CGFloat = 0
let center = CGPoint(x: w / 2, y: h / 2)
let disW = w / 3
let disH = h / 3
let point1 = CGPoint(x: 0, y: 2 * disH)
let point2 = center
let point3 = CGPoint(x: 2 * disW, y: h)
let point4 = CGPoint(x: 0, y: h)
let path = CGMutablePath()
path.move(to: point1)
path.addArc(tangent1End: point2, tangent2End: point3, radius: corner)
path.addArc(tangent1End: point3, tangent2End: point4, radius: corner)
path.addArc(tangent1End: point4, tangent2End: point1, radius: corner)
path.addArc(tangent1End: point1, tangent2End: point2, radius: corner)
let layer = CAShapeLayer()
layer.strokeColor = UIColor.black.cgColor
layer.fillColor = UIColor.lightGray.cgColor
layer.lineWidth = 1
layer.path = path
layer.frame = self.frame
layer.anchorPoint = CGPoint(x: 0, y: 1)
layer.frame = self.frame
layer.transform = CATransform3DMakeScale(0.9, 0.9, 1)
self.layer.addSublayer(layer)
}

Finally, I found a solution. Instead of use transform scale, I changed its corner points.

Related

SpriteKit: Square to Circle animation using SKShapeNode UIBezierPath rounded corners

I'm trying to smoothly animate a square into a circle in SpriteKit.
I'm creating the SKShape with a UIBezierPath using rounded corners. Then, I vary the corner radii to animate.
My problem is that I seem to have a jump in the animation, please see the gif below. Preferably using the rounded corners technique, how can I get it to be smooth?
"Jumpy" problem
let shape = SKShapeNode()
let l: CGFloat = 100.0
shape.path = UIBezierPath(roundedRect: CGRect(x: -l/2, y: -l/2, width: l, height: l), byRoundingCorners: [.topLeft, .bottomLeft, .topRight, .bottomRight], cornerRadii: CGSize(width: 0, height: 0)).cgPath
shape.position = CGPoint(x: frame.midX, y: frame.midY)
shape.fillColor = .white
addChild(shape)
let action = SKAction.customAction(withDuration: 1) { (node, t) in
let shapeNode = node as! SKShapeNode
shapeNode.path = UIBezierPath(roundedRect: CGRect(x: -l/2, y: -l/2, width: l, height: l), byRoundingCorners: [.topLeft, .bottomLeft, .topRight, .bottomRight], cornerRadii: CGSize(width: t * l / 2, height: 0)).cgPath
}
shape.run(SKAction.repeatForever(action))
Animation debugging
To debug I have created some shapes with progressively larger corner radii as you can see below. The numbers represent the ratio of the corner radii to the length of the square. As you can see there is a jump between 0.3 and 0.35. I can't see what I'm missing.
let cols = 10
let rows = 1
let l: Double = 30.0
let max: Double = l / 2
let delta: Double = l * 2
for i in 0..<rows * cols {
let s = SKShapeNode()
let c: Double = Double(i % cols)
let r: Double = floor(Double(i) / Double(cols))
let pct: Double = Double(i) / (Double(rows) * Double(cols))
let rad = pct * max
s.path = UIBezierPath(roundedRect: CGRect(x: -l/2, y: -l/2, width: l, height: l), byRoundingCorners: [.topRight, .bottomRight, .topLeft, .bottomLeft], cornerRadii: CGSize(width: pct * max, height: pct * max)).cgPath
s.position = CGPoint(x: c * delta - Double(cols) / 2.0 * delta, y: r * delta - Double(rows) / 2.0 * delta)
s.lineWidth = 1.5
s.strokeColor = .white
addChild(s)
let t = SKLabelNode(text: String(format:"%0.2f", rad / l))
t.verticalAlignmentMode = .center
t.horizontalAlignmentMode = .center
t.fontName = "SanFrancisco-Bold"
t.fontSize = 15
t.position = CGPoint(x: 0, y: -delta * 0.66)
s.addChild(t)
}
You may not find the answer using a current api. But you can draw it by yourself.
let duration = 10.0
let action = SKAction.customAction(withDuration: duration) { (node, t) in
let shapeNode = node as! SKShapeNode
let path = CGMutablePath()
let borderRadius = l/2 * t / CGFloat(duration);
path.move(to: CGPoint.init(x: -l/2, y: -l/2 + borderRadius));
path.addLine(to: CGPoint.init(x: -l/2, y: l/2 - borderRadius));
path.addArc(tangent1End: CGPoint(x: -l/2, y: l/2), tangent2End: CGPoint(x: -l/2 + borderRadius, y: l/2), radius: borderRadius)
path.addLine(to: CGPoint.init(x: l/2 - borderRadius, y: l/2 ));
path.addArc(tangent1End: CGPoint(x: l/2, y: l/2), tangent2End: CGPoint(x: l/2, y: l/2 - borderRadius), radius: borderRadius)
path.addLine(to: CGPoint.init(x: l/2, y: -l/2 + borderRadius));
path.addArc(tangent1End: CGPoint(x: l/2, y: -l/2), tangent2End: CGPoint(x: l/2 - borderRadius, y: -l/2), radius: borderRadius)
path.addLine(to: CGPoint.init(x: -l/2 + borderRadius, y: -l/2));
path.addArc(tangent1End: CGPoint(x: -l/2, y: -l/2), tangent2End: CGPoint(x: -l/2, y: -l/2 + borderRadius), radius: borderRadius)
path.closeSubpath()
shapeNode.path = path
}
The documentation of UIBezierPath.init(roundedRect:cornerRadius:) states that:
The radius of each corner oval. A value of 0 results in a rectangle without rounded corners. Values larger than half the rectangle’s width or height are clamped appropriately to half the width or height.
But I tested this and it is actually happening exactly for values larger than a third of the width/height and not at a half of it. I'm filling a bug report and will let's hope it is quickly solved.
Bug report link: https://bugs.swift.org/browse/SR-10496

How to draw CATransform3D for this layer?

I'm using CATransform3D and CAShapeLayer to create a layer like below
Here is my code.
let path = CGMutablePath()
let startPoint = CGPoint(x: center.x - width / 2, y: center.y - height / 2)
path.move(to: startPoint)
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y))
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y + height))
path.addLine(to: CGPoint(x: startPoint.x, y: startPoint.y + height))
path.closeSubpath()
let backgroundLayer = CAShapeLayer()
backgroundLayer.path = path
backgroundLayer.fillColor = UIColor.clear.cgColor
backgroundLayer.strokeColor = boarderColor.cgColor
var transform = CATransform3DIdentity
transform.m34 = -1 / 500
let angle = 45.toRadians
backgroundLayer.transform = CATransform3DRotate(transform, angle, 1, 0, 0)
The output is like below.
What is the reason for the difference of shape?
The backgroundLayer needs a frame and a position. If these are added, the result is as follows:
Source
Here a slightly modified version of your code that gives the result shown in the screenshot.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let boarderColor = UIColor.red
let height: CGFloat = 400
let width: CGFloat = 250
let center = CGPoint(x: width / 2.0, y: height / 2)
let path = CGMutablePath()
let startPoint = CGPoint(x: center.x - width / 2, y: center.y - height / 2)
path.move(to: startPoint)
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y))
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y + height))
path.addLine(to: CGPoint(x: startPoint.x, y: startPoint.y + height))
path.closeSubpath()
let backgroundLayer = CAShapeLayer()
backgroundLayer.path = path
backgroundLayer.fillColor = UIColor.clear.cgColor
backgroundLayer.strokeColor = boarderColor.cgColor
//these two lines are missing
backgroundLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
backgroundLayer.position = CGPoint(x: self.view.bounds.width / 2.0, y: self.view.bounds.height / 2)
var transform = CATransform3DIdentity
transform.m34 = -1 / 500
let angle = CGFloat(45 * Double.pi / 180.0)
backgroundLayer.transform = CATransform3DRotate(transform, angle, 1, 0, 0)
self.view.layer.addSublayer(backgroundLayer)
}
}

UIBezierPath Creating Triangle,Square,Circle Swift

I am attempting to create three shapes: a Circle, Square & Triangle. I have created the circle & square, but am unable to create the triangle. My biggest issue is to keep all three shapes in the center of the screen. The circle and square are fine, but when I attempt to make the triangle it does not work. I am also trying to make the triangle look like a "play button" so that the "tip" of the triangle is facing to the right. Here is the code.
func trainglePathWithCenter(center: CGPoint, side: CGFloat) -> UIBezierPath {
let path = UIBezierPath()
let startX = center.x - side / 2
let startY = center.y - side / 2
path.move(to: CGPoint(x: startX, y: startY))
path.addLine(to: CGPoint(x: startX, y: startY - side))
path.addLine(to: CGPoint(x: startX + side, y: startY + side/2))
path.close()
return path
}
func circlePathWithCenter(center: CGPoint, radius: CGFloat) -> UIBezierPath {
let circlePath = UIBezierPath()
circlePath.addArc(withCenter: center, radius: radius, startAngle: -CGFloat(M_PI), endAngle: -CGFloat(M_PI/2), clockwise: true)
circlePath.addArc(withCenter: center, radius: radius, startAngle: -CGFloat(M_PI/2), endAngle: 0, clockwise: true)
circlePath.addArc(withCenter: center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI/2), clockwise: true)
circlePath.addArc(withCenter: center, radius: radius, startAngle: CGFloat(M_PI/2), endAngle: CGFloat(M_PI), clockwise: true)
circlePath.close()
return circlePath
}
func squarePathWithCenter(center: CGPoint, side: CGFloat) -> UIBezierPath {
let squarePath = UIBezierPath()
let startX = center.x - side / 2
let startY = center.y - side / 2
squarePath.move(to: CGPoint(x: startX, y: startY))
squarePath.addLine(to: squarePath.currentPoint)
squarePath.addLine(to: CGPoint(x: startX + side, y: startY))
squarePath.addLine(to: squarePath.currentPoint)
squarePath.addLine(to: CGPoint(x: startX + side, y: startY + side))
squarePath.addLine(to: squarePath.currentPoint)
squarePath.addLine(to: CGPoint(x: startX, y: startY + side))
squarePath.addLine(to: squarePath.currentPoint)
squarePath.close()
return squarePath
}
Where am I going wrong with the geometry for the triangle?
You subtracted when you need to add. Remember, +Y is down.
Change:
path.addLine(to: CGPoint(x: startX, y: startY - side))
To:
path.addLine(to: CGPoint(x: startX, y: startY + side))
Here it is running in a Playground:
Here's the full code for the Playground demo:
class Custom: UIView {
override func draw(_ rect: CGRect) {
let path = trainglePathWithCenter(center: self.center, side: self.bounds.width / 2)
path.stroke()
}
func trainglePathWithCenter(center: CGPoint, side: CGFloat) -> UIBezierPath {
let path = UIBezierPath()
let startX = center.x - side / 2
let startY = center.y - side / 2
path.move(to: CGPoint(x: startX, y: startY))
path.addLine(to: CGPoint(x: startX, y: startY + side))
path.addLine(to: CGPoint(x: startX + side, y: startY + side/2))
path.close()
return path
}
}
let custom = Custom(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
custom.backgroundColor = .white
Result triangles
Code in swift5
//TriangleView
extension UIView {
func setRightTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width //you can use triangleView.frame.size.height
let path = CGMutablePath()
path.move(to: CGPoint(x: heightWidth/2, y: 0))
path.addLine(to: CGPoint(x:heightWidth, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setLeftTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: heightWidth/2, y: 0))
path.addLine(to: CGPoint(x:0, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setUpTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth, y:heightWidth))
path.addLine(to: CGPoint(x:0, y:heightWidth))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setDownTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth, y:0))
path.addLine(to: CGPoint(x:0, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
}

How to display image in diamond shape in swift?

I want to display images in diamond shape when I am giving width and height 120 and apply the corner radiu. I am getting diamond shape approximately but not getting exact diamond shape so any one suggest me it helpful to me.
self.imageView.layer.cornerRadius = self.imageView.frame.size.width / 2
self.imageView.clipsToBounds = true
If you have an image view and want to crop it to a diamond (rhombus) shape, you should:
Create UIBezierPath in diamond shape;
Use that as the path of a CAShapeLayer;
Set that CAShapeLayer as the mask of the UIImageView's layer
In Swift 3 and later, that might look like:
extension UIView {
func addDiamondMask(cornerRadius: CGFloat = 0) {
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.midX, y: bounds.minY + cornerRadius))
path.addLine(to: CGPoint(x: bounds.maxX - cornerRadius, y: bounds.midY))
path.addLine(to: CGPoint(x: bounds.midX, y: bounds.maxY - cornerRadius))
path.addLine(to: CGPoint(x: bounds.minX + cornerRadius, y: bounds.midY))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.lineWidth = cornerRadius * 2
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineCap = kCALineCapRound
layer.mask = shapeLayer
}
}
So, just call addDiamondMask(cornerRadius:) (where the cornerRadius is optional), on your image view.
imageView.addDiamondMask()
That yields:
For Swift 2 rendition, see previous revision of this answer.
An alternate algorithm for rounding of corners might be:
extension UIView {
func addDiamondMask(cornerRadius: CGFloat = 0) {
let path = UIBezierPath()
let points = [
CGPoint(x: bounds.midX, y: bounds.minY),
CGPoint(x: bounds.maxX, y: bounds.midY),
CGPoint(x: bounds.midX, y: bounds.maxY),
CGPoint(x: bounds.minX, y: bounds.midY)
]
path.move(to: point(from: points[0], to: points[1], distance: cornerRadius, fromStart: true))
for i in 0 ..< 4 {
path.addLine(to: point(from: points[i], to: points[(i + 1) % 4], distance: cornerRadius, fromStart: false))
path.addQuadCurve(to: point(from: points[(i + 1) % 4], to: points[(i + 2) % 4], distance: cornerRadius, fromStart: true), controlPoint: points[(i + 1) % 4])
}
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.clear.cgColor
layer.mask = shapeLayer
}
private func point(from point1: CGPoint, to point2: CGPoint, distance: CGFloat, fromStart: Bool) -> CGPoint {
let start: CGPoint
let end: CGPoint
if fromStart {
start = point1
end = point2
} else {
start = point2
end = point1
}
let angle = atan2(end.y - start.y, end.x - start.x)
return CGPoint(x: start.x + distance * cos(angle), y: start.y + distance * sin(angle))
}
}
Here I'm doing quad bezier in the corners, but I think the effect for rounded corners is slightly better than the above if the diamond is at all elongated.
Anyway, that yields:
SWIFT 5
extension UIView {
func addDiamondMask(cornerRadius: CGFloat = 0) {
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.midX, y: bounds.minY + cornerRadius))
path.addLine(to: CGPoint(x: bounds.maxX - cornerRadius, y: bounds.midY))
path.addLine(to: CGPoint(x: bounds.midX, y: bounds.maxY - cornerRadius))
path.addLine(to: CGPoint(x: bounds.minX + cornerRadius, y: bounds.midY))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.lineWidth = cornerRadius * 2
shapeLayer.lineJoin = .round
shapeLayer.lineCap = .round
layer.mask = shapeLayer
}
}

How to create rounded corners with CAShapeLayer

Is there a way to add rounded corners to a CAShapeLayer? In my case I needed the shape layer to create a dashed border via lineDashPattern.
^ notice how the dashed line is not rounded
The answer is simple. Create a bézier path with rounded corners.
UPDATE for Swift
view.clipsToBounds = true
view.layer.cornerRadius = 10.0
let border = CAShapeLayer()
border.path = UIBezierPath(roundedRect:view.bounds, cornerRadius:10.0).cgPath
border.frame = view.bounds
border.fillColor = nil
border.strokeColor = UIColor.purple.cgColor
border.lineWidth = borderWidth * 2.0 // doubled since half will be clipped
border.lineDashPattern = [15.0]
view.layer.addSublayer(border)
Objective-C
// (This old code assumes this is within a view with a custom property "border".)
self.clipsToBounds = YES;
self.layer.cornerRadius = 10.0;
self.border = [CAShapeLayer layer];
self.border.fillColor = nil;
self.border.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10.0].cgPath;
self.border.frame = self.bounds;
self.border.strokeColor = [UIColor purpleColor].CGColor;
self.border.lineWidth = borderWidth * 2; // double desired width as half will be clipped
self.border.lineDashPattern = #[#15];
[self.layer addSublayer:self.border];
In swift 4 I created a UIView category (UIView+Borders) with the following function:
func borderDash(withRadius cornerRadius: Float, borderWidth: Float, borderColor: UIColor, dashSize: Int) {
let currentFrame = self.bounds
let shapeLayer = CAShapeLayer()
let path = CGMutablePath()
let radius = CGFloat(cornerRadius)
// Points - Eight points that define the round border. Each border is defined by two points.
let topLeftPoint = CGPoint(x: radius, y: 0)
let topRightPoint = CGPoint(x: currentFrame.size.width - radius, y: 0)
let middleRightTopPoint = CGPoint(x: currentFrame.size.width, y: radius)
let middleRightBottomPoint = CGPoint(x: currentFrame.size.width, y: currentFrame.size.height - radius)
let bottomRightPoint = CGPoint(x: currentFrame.size.width - radius, y: currentFrame.size.height)
let bottomLeftPoint = CGPoint(x: radius, y: currentFrame.size.height)
let middleLeftBottomPoint = CGPoint(x: 0, y: currentFrame.size.height - radius)
let middleLeftTopPoint = CGPoint(x: 0, y: radius)
// Points - Four points that are the center of the corners borders.
let cornerTopRightCenter = CGPoint(x: currentFrame.size.width - radius, y: radius)
let cornerBottomRightCenter = CGPoint(x: currentFrame.size.width - radius, y: currentFrame.size.height - radius)
let cornerBottomLeftCenter = CGPoint(x: radius, y: currentFrame.size.height - radius)
let cornerTopLeftCenter = CGPoint(x: radius, y: radius)
// Angles - The corner radius angles.
let topRightStartAngle = CGFloat(Double.pi * 3 / 2)
let topRightEndAngle = CGFloat(0)
let bottomRightStartAngle = CGFloat(0)
let bottmRightEndAngle = CGFloat(Double.pi / 2)
let bottomLeftStartAngle = CGFloat(Double.pi / 2)
let bottomLeftEndAngle = CGFloat(Double.pi)
let topLeftStartAngle = CGFloat(Double.pi)
let topLeftEndAngle = CGFloat(Double.pi * 3 / 2)
// Drawing a border around a view.
path.move(to: topLeftPoint)
path.addLine(to: topRightPoint)
path.addArc(center: cornerTopRightCenter,
radius: radius,
startAngle: topRightStartAngle,
endAngle: topRightEndAngle,
clockwise: false)
path.addLine(to: middleRightBottomPoint)
path.addArc(center: cornerBottomRightCenter,
radius: radius,
startAngle: bottomRightStartAngle,
endAngle: bottmRightEndAngle,
clockwise: false)
path.addLine(to: bottomLeftPoint)
path.addArc(center: cornerBottomLeftCenter,
radius: radius,
startAngle: bottomLeftStartAngle,
endAngle: bottomLeftEndAngle,
clockwise: false)
path.addLine(to: middleLeftTopPoint)
path.addArc(center: cornerTopLeftCenter,
radius: radius,
startAngle: topLeftStartAngle,
endAngle: topLeftEndAngle,
clockwise: false)
// Path is set as the shapeLayer object's path.
shapeLayer.path = path;
shapeLayer.backgroundColor = UIColor.clear.cgColor
shapeLayer.frame = currentFrame
shapeLayer.masksToBounds = false
shapeLayer.setValue(0, forKey: "isCircle")
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = borderColor.cgColor
shapeLayer.lineWidth = CGFloat(borderWidth)
shapeLayer.lineDashPattern = [NSNumber(value: dashSize), NSNumber(value: dashSize)]
shapeLayer.lineCap = kCALineCapRound
self.layer.addSublayer(shapeLayer)
self.layer.cornerRadius = radius;
}

Resources