2
votes

I have a custom UIView like so:

import UIKit

class MainLogoAnimationView: UIView {

    @IBOutlet var customView: UIView!
    var turquoiseCircle: AnimationCircleView!
    var redCircle: AnimationCircleView!
    var blueCircle: AnimationCircleView!


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

    func setup() {
        _ = Bundle.main.loadNibNamed("MainLogoAnimationView", owner: self, options: nil)?[0] as! UIView
        self.addSubview(customView)
        customView.frame = self.bounds
        setupAnimation()
    }

    let initialWidthScaleFactor: CGFloat = 0.06

    func setupAnimation() {
        let circleWidth = frame.size.width*initialWidthScaleFactor
        let yPos = frame.size.height/2 - circleWidth/2
        turquoiseCircle = AnimationCircleView(frame: CGRect(x: 0, y: yPos, width: circleWidth, height: circleWidth))
        turquoiseCircle.circleColor = UIColor(red: 137/255.0, green: 203/255.0, blue: 225/255.0, alpha: 1.0).cgColor
        turquoiseCircle.backgroundColor = UIColor.lightGray
        addSubview(turquoiseCircle)
        redCircle = AnimationCircleView(frame: CGRect(x: 100, y: yPos, width: circleWidth, height: circleWidth))
        addSubview(redCircle)
        blueCircle = AnimationCircleView(frame: CGRect(x: 200, y: yPos, width: circleWidth, height: circleWidth))
        blueCircle.circleColor = UIColor(red: 93/255.0, green: 165/255.0, blue: 213/255.0, alpha: 1.0).cgColor
        addSubview(blueCircle)
    }
}

I created a light blue colored UIView in interface builder and set the above view MainLogoAnimationView as its subclass. If I run the app I get this:

enter image description here

It seems that for the turquoiseCircle the frame hasn't been placed at x = 0 as I set it. Not sure what I am doing wrong here. Is it because it is placed before the MainLogoAnimationView is fully initialized so being placed incorrectly? I have tried setting the frames of the circles in layoutSubviews() and this also didn't make any difference. I seem to run into the problem a lot with views being misplaced and think I am missing something fundamental here. What am I doing wrong?

UPDATE:

import UIKit

class MainLogoAnimationView: UIView {

    @IBOutlet var customView: UIView!
    var turquoiseCircle: AnimationCircleView!
    var redCircle: AnimationCircleView!
    var blueCircle: AnimationCircleView!


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

    func setup() {
        _ = Bundle.main.loadNibNamed("MainLogoAnimationView", owner: self, options: nil)?[0] as! UIView
        self.addSubview(customView)
        customView.frame = self.bounds
        setupAnimation()
    }

    let initialWidthScaleFactor: CGFloat = 0.2


    func setupAnimation() {
        blueCircle = AnimationCircleView()
        blueCircle.translatesAutoresizingMaskIntoConstraints = false
        blueCircle.backgroundColor = UIColor.lightGray
        blueCircle.circleColor = UIColor.blue.cgColor
        addSubview(blueCircle)

        redCircle = AnimationCircleView()
        redCircle.translatesAutoresizingMaskIntoConstraints = false
        redCircle.backgroundColor = UIColor.lightGray
        addSubview(redCircle)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        let circleWidth = bounds.size.width*initialWidthScaleFactor
        let yPos = frame.size.height/2 - circleWidth/2
        blueCircle.frame = CGRect(x: 0, y: yPos, width: circleWidth, height: circleWidth)
        redCircle.frame = CGRect(x: frame.size.width/2 - circleWidth/2, y: yPos, width: circleWidth, height: circleWidth)
    }
}

and :

import UIKit

class AnimationCircleView: UIView {

    var circleColor = UIColor(red: 226/255.0, green: 131/255.0, blue: 125/255.0, alpha: 1.0).cgColor {
        didSet {
            shapeLayer.fillColor = circleColor
        }
    }

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

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

    let shapeLayer = CAShapeLayer()

    func setup() {
        let circlePath = UIBezierPath(ovalIn: self.bounds)
        shapeLayer.path = circlePath.cgPath
        shapeLayer.fillColor = circleColor
        shapeLayer.frame = self.bounds
        layer.addSublayer(shapeLayer)
    }

}

enter image description here

1

1 Answers

2
votes

It's definitely the case that the setting up in init will provide self.frame/self.bounds values for whatever the view's initialized values are and not the correct final values when laid out in the view hierarchy. Probably the best way to handle this is like you tried, to add the views in init, and use property to keep a reference to them (you already are doing this) and then set the frame in -layoutSubviews().

The reason it probably didn't work in -layoutSubviews() is for each view after creation you need to call .translatesAutoresizingMaskIntoConstraints = false. Otherwise with programmatically-created views the system will create constraints for the values when added as a subview, overriding any setting of frames. Try setting that and see how it goes.

EDIT: For resizing the CAShapeLayer, instead of adding as a sublayer (which would require manually resizing) since AnimationCirleView view is specialized the view can be backed by CAShapeLayer. See the answer at Overriding default layer type in UIView in swift