I'm building a custom view using an XIB file. However, I am facing a problem where the layers I add to the view (trackLayer) are not shown on the xib (circleLayer is an animation and I don't expect it to render in xib which is not possible to my knowledge). The code of the owner class for the XIB is shown as follows:
@IBDesignable
class SpinningView: UIView {
@IBOutlet var contentView: SpinningView!
//MARK: Properties
let circleLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()
let circularAnimation: CABasicAnimation = {
let animation = CABasicAnimation()
animation.fromValue = 0
animation.toValue = 1
animation.duration = 2
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
return animation
}()
//MARK: IB Inspectables
@IBInspectable var strokeColor: UIColor = UIColor.red {
...
}
@IBInspectable var trackColor: UIColor = UIColor.lightGray {
...
}
@IBInspectable var lineWidth: CGFloat = 5 {
...
}
@IBInspectable var fillColor: UIColor = UIColor.clear {
...
}
@IBInspectable var isAnimated: Bool = true {
...
}
//MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
initSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
initSubviews()
}
//MARK: Private functions
private func setup() {
setupTrack()
setupAnimationOnTrack()
}
private func setupTrack(){
trackLayer.lineWidth = lineWidth
trackLayer.fillColor = fillColor.cgColor
trackLayer.strokeColor = trackColor.cgColor
layer.addSublayer(trackLayer)
}
private func setupAnimationOnTrack(){
circleLayer.lineWidth = lineWidth
circleLayer.fillColor = fillColor.cgColor
circleLayer.strokeColor = strokeColor.cgColor
circleLayer.lineCap = kCALineCapRound
layer.addSublayer(circleLayer)
updateAnimation()
}
private func updateAnimation() {
if isAnimated {
circleLayer.add(circularAnimation, forKey: "strokeEnd")
}
else {
circleLayer.removeAnimation(forKey: "strokeEnd")
}
}
//MARK: Layout contraints
override func layoutSubviews() {
super.layoutSubviews()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(bounds.width / 2, bounds.height / 2) - circleLayer.lineWidth / 2
let startAngle = -CGFloat.pi / 2
let endAngle = 3 * CGFloat.pi / 2
let path = UIBezierPath(arcCenter: .zero, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
trackLayer.position = center
trackLayer.path = path.cgPath
circleLayer.position = center
circleLayer.path = path.cgPath
}
private func initSubviews() {
let bundle = Bundle(for: SpinningView.self)
let nib = UINib(nibName: "SpinningView", bundle: bundle)
nib.instantiate(withOwner: self, options: nil)
contentView.frame = bounds
addSubview(contentView)
}
}
When a view is subclassed in the Main.storyboard, I can see the image and the tracklayer as follows IB UIView but when I go over to the XIB, it does not have the trackLayer(circle around the image) XIB Image. While one can argue that it is working and why I am bothering with this, I think it is important that I design the XIB properly since another person might just see it as an view with only an image (no idea of the animating feature)
When you are designing your XIB, it is not an instance of your @IBDesignable SpinningView
. You are, effectively, looking at the source of your SpinningView
.
So, the code behind it is not running at that point - it only runs when you add an instance of SpinningView
to another view.
Take a look at this simple example:
@IBDesignable
class SpinningView: UIView {
@IBOutlet var contentView: UIView!
@IBOutlet var theLabel: UILabel!
//MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
initSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initSubviews()
}
private func initSubviews() {
let bundle = Bundle(for: SpinningView.self)
let nib = UINib(nibName: "SpinningView", bundle: bundle)
nib.instantiate(withOwner: self, options: nil)
contentView.frame = bounds
addSubview(contentView)
// this will change the label's textColor in Storyboard
// when a UIView's class is set to SpinningView
theLabel.textColor = .red
}
}
When I am designing the XIB, this is what I see:
But when I add a UIView
to another view, and assign its class as SpinningView
, this is what I see:
The label's text color get's changed to red, because the code is now running.
Another way to think about it... you have trackColor
, fillColor
, etc vars defined as @IBInspectable
. When you're designing your XIB, you don't see those properties in Xcode's Attributes Inspector panel - you only see them when you've selected an instance of SpinningView
that has been added elsewhere.
Hope that makes sense.