Problem with scaling animation of CAShapeLayer [duplicate] - ios

This question already has answers here:
UIView animateWithDuration doesn't animate cornerRadius variation
(8 answers)
Closed 2 years ago.
I created simple path for tooltip of a slider. I wanted it to animate as it appears so a user would know that he can drag it.
But it does not animate:
let upperToolTipPath: CAShapeLayer = CAShapeLayer()
var path = UIBezierPath(roundedRect: CGRect(x: 0,y: 0, width: 65, height: 30), cornerRadius: 3)
path.stroke()
path.move(to: CGPoint(x: 50, y: 10))
path.addLine(to: CGPoint(x: 40, y: 30))
path.addLine(to: CGPoint(x: 15, y: 10))
upperToolTipPath.path = path.cgPath
self.layer.insertSublayer(upperToolTipPath, at: 0)
UIView.animate(
withDuration: 5,
delay: 3,
options: [.repeat, .autoreverse, .curveEaseIn],
animations: {
self.upperToolTipPath.transform = CATransform3DMakeScale(2, 2, 1)
})
Could you help me with the animation?

UIView.animate is for view animation. But upperToolTipPath is a layer; it is not a view's primary layer, so it can be animated only with layer animation. That means Core Animation, i.e. CABasicAnimation.
So to make the animation you wish to make, you will need to use Core Animation.
Alternatively, you could make a view that hosts the shape layer as its primary layer. Then you would be able to use view animation.
But I think it's better in this case to use Core Animation. Here's an example of the sort of thing I think you are trying to do:
let upperToolTipPath = CAShapeLayer()
upperToolTipPath.frame = CGRect(x: 0,y: 0, width: 65, height: 30)
let path = UIBezierPath(roundedRect: CGRect(x: 0,y: 0, width: 65, height: 30),
cornerRadius: 3)
upperToolTipPath.fillColor = UIColor.clear.cgColor
upperToolTipPath.strokeColor = UIColor.black.cgColor
upperToolTipPath.path = path.cgPath
self.layer.insertSublayer(upperToolTipPath, at: 0)
let b = CABasicAnimation(keyPath: #keyPath(CALayer.transform))
b.duration = 5
b.beginTime = CACurrentMediaTime() + 3
b.toValue = CATransform3DMakeScale(2, 2, 1)
b.repeatCount = .greatestFiniteMagnitude
b.autoreverses = true
upperToolTipPath.add(b, forKey:nil)

Related

Swift. disable antialiasing when UIBezierPath append

I'd like to append UIBezierPath without antialiasing.
Here is what I tried. testView's frame is 10x10
let shadowLayer = CALayer()
var path: UIBezierPath!
shadowLayer.frame = testView.bounds
path = UIBezierPath(rect: CGRect(x: 1, y: 1, width: 3, height: 3))
shadowLayer.shadowPath = path.cgPath
shadowLayer.shadowColor = UIColor.red.cgColor
shadowLayer.shadowOffset = .init(width: 0, height: 0)
shadowLayer.shadowOpacity = 1
shadowLayer.shadowRadius = 0
testView.layer.addSublayer(shadowLayer)
The red rectangle inside testView is 3x3, no problem.
But when I append another UIBezierPath.
...
path = UIBezierPath(rect: CGRect(x: 1, y: 1, width: 3, height: 3))
path.append(UIBezierPath(rect: CGRect(x: 5, y: 5, width: 3, height: 3)))
...
The red rectangles are getting antialiasing.
How do I disable antialiasing when appending UIBezierPath? Thanks.
You can create a new graphics context, set the shouldAntialias attribute to false and add the cgPath from your UIBezierPath to it. Or you can move it to draw(_:), get the current context and do the same.
https://developer.apple.com/documentation/appkit/nsgraphicscontext/1529486-shouldantialias

What is the proper way to scale a view while animating it with CAKeyframeAnimation and BezierPath

The following code effectively animates and scales a view along a BezierPath but I feel like I'm cheating since I'm mixing UIKit and CAKeyframeAnimation (Core Animation) animations.
Is there a better way to scale a view while animating it along the BezierPath?
func animateIt(){
let myView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
myView.backgroundColor = .blue
myView.layer.cornerRadius = myView.frame.height / 2
view.addSubview(myView)
let startPoint = CGPoint(x: 250, y: 500)
let apexPoint = CGPoint(x: 100, y: 50)
let endPoint = CGPoint(x: 50, y: 350)
let durationTime = 1.5
let path = UIBezierPath()
path.move(to: startPoint)
path.addQuadCurve(to: endPoint, controlPoint: apexPoint)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.duration = durationTime
myView.layer.add(animation, forKey: "bezier")
myView.center = endPoint
UIView.animate(withDuration: durationTime, animations: {
myView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5);
})
}
I tried using a second CAKeyframeAnimation animation instead of the UIKit animation and used the array of key values but no matter what I do, I cannot get a uniform scale, it scales at the beginning but not at the end.
let scaleAnimation = CAKeyframeAnimation(keyPath: "scale")
scaleAnimation.values = [1.0, 0.9, 0.5,]
myView.layer.add(scaleAnimation, forKey: "scale")
Thanks!

Why the edges of UIView are not smooth for huge corner radius?

class AttributedView: UIView {
private let gradient = CAGradientLayer()
var cornerRadius: CGFloat = 3 {
didSet {
layer.cornerRadius = cornerRadius
}
}
}
I simply use it for both: button view (corner radius: 20) and background circle (corner radius: 600).
Why button is smooth, and background is not?
With iOS 13.0 you can simple do, in addition to setting corner radius
yourView.layer.cornerCurve = .continuous
You should use bezzier paths and draw circle. After that you will receive nice, smooth edges.
let context = UIGraphicsGetCurrentContext()!
let gradient = CGGradient(colorsSpace: nil, colors: [UIColor.red.cgColor, UIColor.white.cgColor] as CFArray, locations: [0, 1])!
let ovalPath = UIBezierPath(ovalIn: CGRect(x: 64, y: 9, width: 111, height: 93))
context.saveGState()
ovalPath.addClip()
context.drawLinearGradient(gradient, start: CGPoint(x: 119.5, y: 9), end: CGPoint(x: 119.5, y: 102), options: [])
context.restoreGState()
UIBezierPath is a simple and efficient class for drawing shapes using Swift, which you can then put into CAShapeLayer, SKShapeNode, or other places. It comes with various shapes built in, so you can write code like this to create a rounded rectangle or a circle:
let rect = CGRect(x: 0, y: 0, width: 256, height: 256)
let roundedRect = UIBezierPath(roundedRect: rect, cornerRadius: 50)
let circle = UIBezierPath(ovalIn: rect)
You can also create custom shapes by moving a pen to a starting position then adding lines:
let freeform = UIBezierPath()
freeform.move(to: .zero)
freeform.addLine(to: CGPoint(x: 50, y: 50))
freeform.addLine(to: CGPoint(x: 50, y: 150))
freeform.addLine(to: CGPoint(x: 150, y: 50))
freeform.addLine(to: .zero)
If your end result needs a CGPath, you can get one by accessing the cgPath property of your UIBezierPath.
You probably should clip bounds of this view:
attributedView.clipsToBounds = true

Playground code: AffineTransform and AnchorPoint don't work as I want

I want to shrink a triangle downward as below:
But the triangle shrink upward as below:
The transform code is below:
layer.setAffineTransform(CGAffineTransformMakeScale(1.0, 0.5))
I tried to use anchorPoint but I can't scceed.
//: Playground - noun: a place where people can play
import UIKit
let view = UIView(frame: CGRectMake(0, 0, 200, 150))
let layer = CAShapeLayer()
let triangle = UIBezierPath()
triangle.moveToPoint (CGPointMake( 50, 150))
triangle.addLineToPoint(CGPointMake(100, 50))
triangle.addLineToPoint(CGPointMake(150, 150))
triangle.closePath()
layer.path = triangle.CGPath
layer.strokeColor = UIColor.blueColor().CGColor
layer.lineWidth = 3.0
view.layer.addSublayer(layer)
view
layer.setAffineTransform(CGAffineTransformMakeScale(1.0, 0.5))
view
Anish gave me a advice but doesn't work well:
layer.setAffineTransform(CGAffineTransformMakeScale(2.0, 0.5))
Transforms operate around the layer's anchorPoint, which by default is at the center of the layer. Thus, you shrink by collapsing inwards, not by collapsing downwards.
If you want to shrink downward, you will need to scale down and change the view's position (or use a translate transform in addition to your scale transform), or change the layer's anchorPoint before performing the transform.
Another serious problem with your code is that your layer has no assigned size. This causes the results of your transform to be very misleading.
I ran this version of your code and got exactly the results you are asking for. I have put a star next to the key lines that I added. (Note that this is Swift 3; you really need to step up to the plate and update here.)
let view = UIView(frame:CGRect(x: 0, y: 0, width: 200, height: 150))
let layer = CAShapeLayer()
layer.frame = view.layer.bounds // *
let triangle = UIBezierPath()
triangle.move(to: CGPoint(x: 50, y: 150))
triangle.addLine(to: CGPoint(x: 100, y: 50))
triangle.addLine(to: CGPoint(x: 150, y: 150))
triangle.close()
layer.path = triangle.cgPath
layer.strokeColor = UIColor.blue.cgColor
layer.lineWidth = 3
view.layer.addSublayer(layer)
view
layer.anchorPoint = CGPoint(x: 0.5, y: 0) // *
layer.setAffineTransform(CGAffineTransform(scaleX: 1, y: 0.5))
view
Before:
After:

How to create a view like UIPopover using UIBezierPaths in iPhone?

I have an app in which there are so many popover and every popover so many objects like UIButtons, UILable,UITableView.
Now I want to create view like popover using UIBezierpathswith upside arrow.
How can I do that in a UIView?
don't want to any third party tool.
Any help will be appreciated.
Check my answer from link here and just change the point values according to your need or path.
Hope it will help you.
I am late but could be helpful to someone
func drawPopoverRadius(text: String) -> UIView {
let layer = CAShapeLayer()
let path = UIBezierPath()
path.move(to: .zero)
path.addLine(to: CGPoint(x: 51, y: 0))
path.addLine(to: CGPoint(x: 51, y: 48))
path.addLine(to: CGPoint(x: 51/2 - 4, y: 48))
path.addLine(to: CGPoint(x: 51/2, y: 52))
path.addLine(to: CGPoint(x: 51/2 + 4, y: 48))
path.addLine(to: CGPoint(x: 0, y: 48))
path.close()
layer.path = path.cgPath
layer.fillColor = UIColor.gray.cgColor
layer.anchorPoint = .zero
layer.name = "popover"
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 51, height: 52))
label.clipsToBounds = false
label.numberOfLines = 0
label.font = NunitoSans.regular.font(size: 14)
label.textColor = .white
label.text = text
label.layer.addSublayer(layer)
return label
}

Resources