swiftuiviewibinspectable

IBInspectable variable value is not changing in interface builder and simulator


I am creating custom parallelogram view and view is rendering fine with default offset value. But when i change the offset value from ib it's not working

@IBDesignable class CustomParallelogramView: UIView {


    @IBInspectable var offset: Float = 10.0

    var path = UIBezierPath()

    lazy var shapeLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        layer.path = path.cgPath
        return layer
    }()

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

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

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        drawParallelogram()
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        updateFrame()
    }



    func drawParallelogram() {
        print(offset)
        path.move(to: CGPoint(x: bounds.minX + CGFloat(offset), y: bounds.minY))
        path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
        path.addLine(to: CGPoint(x: bounds.maxX - CGFloat(offset), y: bounds.maxY))
        path.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
        path.close()
        self.layer.mask = shapeLayer

    }

    func updateFrame() {
        shapeLayer.frame = bounds
    }

}

I changed the offset value from IB but it doesn't changes IB and simulator


Solution

  • A couple of thoughts:

    1. As Daniel said, you really want to call drawParallelgram in your offset observer.

    2. Also, in layoutSubviews, you’re updating the frame of your shape layer. You want to reset its path and update your mask again.

    3. You’re just adding more and more strokes to your UIBezierPath. You probably just want to make it a local variable and avoid adding more and more strokes to the existing path.

    4. The prepareForInterfaceBuilder suggests some misconception about the purpose of this method. This isn’t for doing initialization when launching from IB. This is for doing some special configuration, above and beyond what is already done by the init methods, required by IB.

      E.g. If you have a sophisticated chart view where you’ll be programmatically providing real chart data programmatically later, but you wanted to see something in IB, nonetheless, you might have prepareForInterfaceBuilder populate some dummy data, for example. But you shouldn’t repeat the configuration you already did in init methods.

    5. It’s not relevant here (because I’m going to suggest getting rid of these init methods), but for what it’s worth, if I need to do configuration during init, I generally write two init methods:

      override init(frame: CGRect = .zero) {
          super.init(frame: frame)
      
          <#call configure routine here#>
      }
      
      required init?(coder aDecoder: NSCoder) {
          super.init(coder: aDecoder)
      
          <#call configure routine here#>
      }
      

      Note that in init(frame:) I also supply a default value of .zero. That ensures that I’m covered in all three scenarios:

      • CustomView()
      • CustomView(frame:); or
      • if the storyboard calls CustomView(decoder:).
         

      In short, I get three init methods for the price of two. Lol.


    All that being said, you can greatly simplify this:

    @IBDesignable public class CustomParallelogramView: UIView {
    
        @IBInspectable public var offset: CGFloat = 10 { didSet { updateMask() } }
    
        override public func layoutSubviews() {
            super.layoutSubviews()
            updateMask()
        }
    
        private func updateMask() {
            let path = UIBezierPath()
            path.move(to: CGPoint(x: bounds.minX + offset, y: bounds.minY))
            path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
            path.addLine(to: CGPoint(x: bounds.maxX - offset, y: bounds.maxY))
            path.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
            path.close()
    
            let shapeLayer = CAShapeLayer()
            shapeLayer.path = path.cgPath
            layer.mask = shapeLayer
        }
    }