Swift - Setting Calayer Bounds or Frame Not Working

Cannot layout CALayer frame correctly

Here is the simple fix for your issue provide the bounds instead of the frame as the frame for the CALayer, here's how:

darkShadow.frame = bounds

To understand how this works you need to know the basics of UIKit layout and the difference between frame and bounds (You might wanna google this). In simple words, frame is the coordinates of the view with respect to super and bounds is with respect to itself.

Note: (I'm adding this here just as a guideline) It's a common beginner mistake. There are plenty of online resources that explain the difference between frame and bounds. Although you might find it a bit overwhelming in the beginning you will get to know the difference eventually. Here are the ones I found useful and could be helpful for you as well :- Youtube Video of Saun Alen and Article from hacking with swift.

CALayer bounds don't change after setting them to a certain value

The way to change the apparent drawing size of a layer is not to change its bounds but to change its transform. To make the layer look larger, including its drawing, apply a scale transform.

CALayers didn't get resized on its UIView's bounds change. Why?

I used the same approach that Solin used, but there's a typo in that code. The method should be:

- (void)layoutSubviews {
[super layoutSubviews];
// resize your layers based on the view's new bounds
mylayer.frame = self.bounds;
}

For my purposes, I always wanted the sublayer to be the full size of the parent view. Put that method in your view class.

CALayer content goes out of bounds - iOS

You should put the layer you are scaling inside of another layer and mask that one instead (the superlayer). The same thing works with views.

I.e. You have two views / layers: clippingView and scalingView where scalingView is the subview of clippingView and clippingView is the view that actually clips to it's bounds.

[clippingView addSubview:scalingView];
clippingView.clipsToBounds = YES;

or using layers

[clippingLayer addSublayer:scalingLayer];
clippingLayer.masksToBounds = YES;

why isnt my CaLayer showing up on top of the UIView?

There are a few things going wrong:

  1. The frame/bounds of the CALayer need to be set accurately. Keep in mind that this could change from its initial placement. For example, in my first test, the layer believed that it was a 0x0 rectangle.

  2. The path of the curve has to be correctly set (related to #1)

  3. Your current time of .infinity would've made the animation take an infinite amount of time to complete. I think what you want instead is an animation that takes a certain amount of time and repeats infinitely.

There may be been another change I'm now forgetting about, but this should get you started:


class LoadingView: UIView {

let circleLayer = CAShapeLayer()

private var circlePath : UIBezierPath = .init()
let size : CGFloat = 50

init() {
super.init(frame: .zero)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func startLoading() {
if let topView = UIApplication.topViewController()?.view {
topView.addSubview(self)
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.centerYAnchor.constraint(equalTo: topView.centerYAnchor),
self.centerXAnchor.constraint(equalTo: topView.centerXAnchor),
self.widthAnchor.constraint(equalToConstant: CGFloat(size)),
self.heightAnchor.constraint(equalToConstant: CGFloat(size)),
])
self.layer.cornerRadius = frame.height/3
backgroundColor = .secondarySystemBackground
self.layer.addSublayer(circleLayer)
calculateCirclePath()
animateCircle(duration: 1, repeats: true)
}
}

func calculateCirclePath() {
self.circlePath = UIBezierPath(arcCenter: CGPoint(x: size / 2, y: size / 2), radius: size / 2, startAngle: 0.0, endAngle: CGFloat(Double.pi*2), clockwise: true)
}

override func layoutSubviews() {
circleLayer.frame = self.layer.bounds
}

private func animateCircle(duration: TimeInterval, repeats: Bool) {
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.blue.cgColor
circleLayer.lineWidth = 1.0

// Don't draw the circle initially
circleLayer.strokeEnd = 0.0

// Add the circleLayer to the view's layer's sublayers
self.layer.addSublayer(circleLayer)
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.repeatCount = repeats ? .infinity : 1
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
circleLayer.strokeEnd = 0
circleLayer.add(animation, forKey: "animateCircle")

let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.repeatCount = repeats ? .infinity : 1
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = Double.pi*3
rotationAnimation.duration = duration
rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
circleLayer.add(rotationAnimation, forKey: nil)

let fadeAnimation = CABasicAnimation(keyPath: "opacity")
fadeAnimation.repeatCount = repeats ? .infinity : 1
fadeAnimation.fromValue = 1
fadeAnimation.toValue = 0
fadeAnimation.duration = duration
fadeAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
circleLayer.add(fadeAnimation, forKey: nil)
}

func stopLoading() {
self.removeFromSuperview()
}
}

iOS - CALayer protruding bounds on orientation change

You are inserting a new layer each time when layoutSubviews method call. That means it shows the line of the first layer.

Just update the frame from layoutSubviews and call setup method from awakeFromNib.

class CustomView: UIView {
private let gradient = CAGradientLayer()

override init (frame: CGRect) {
super.init(frame: frame)
setupView()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

override func layoutSubviews() {
super.layoutSubviews()
gradient.frame = bounds
}

override func awakeFromNib() {
super.awakeFromNib()
setupView()
}

private func setupView() {
gradient.colors = [UIColor.blue.cgColor, UIColor.yellow.cgColor]

layer.insertSublayer(gradient, at: 0)
}
}



Related Topics



Leave a reply



Submit