iosswiftuiviewuikit-dynamics

Why terminating with uncaught exception of type NSException in .addObserver


I will use UIAttachmentBehavior to connect anchorPoint to a specific UIView and then use CAShapeLayer to draw a line.

enter image description here

I used addObserver for bring UIView's coordinates every minute.

However, an unknown error occurs. I don't think I'm familiar with this function.

Error: libc++abi.dylib: terminating with uncaught exception of type NSException

Code:

//: A UIKit based Playground for presenting user interface

import UIKit
import PlaygroundSupport

class MyViewController : UIViewController {
    private var animator: UIDynamicAnimator?
    private var gravity: UIGravityBehavior?
    private var attach: UIAttachmentBehavior?
    private var plateView: UIView?

    var lineLayer: CAShapeLayer?

    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white
        self.view = view

        plateView = UIView(frame: CGRect(x: 80, y: 150, width: 60, height: 60))
        plateView?.backgroundColor = UIColor.orange
        self.view.addSubview(plateView!)

        animator = UIDynamicAnimator(referenceView: view)

        gravity = UIGravityBehavior(items: [plateView!])
        animator?.addBehavior(gravity!)

        attach = UIAttachmentBehavior(item: plateView!, offsetFromCenter: UIOffset(horizontal: 0, vertical: -30), attachedToAnchor: CGPoint(x: 200, y: 100))

        attach?.damping = 0.1
        attach?.frequency = 0.6
        animator?.addBehavior(attach!)

        plateView?.addObserver(self, forKeyPath: "center", options: .new, context: nil)

        func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            updateLine()
        }

        func updateLine() {
            if nil == lineLayer {
                lineLayer = CAShapeLayer()
                lineLayer?.strokeColor = UIColor.purple.cgColor
                lineLayer?.fillColor = UIColor.clear.cgColor
                lineLayer?.lineWidth = 1.5
                lineLayer?.lineJoin = .round
                lineLayer?.strokeEnd = 1.0
                self.view.layer.addSublayer(lineLayer!)
            }

            let platePoint = view.convert(CGPoint(x: plateView!.bounds.midX, y: 0), from: plateView)
            let bezierPath = UIBezierPath()
            bezierPath.move(to: attach!.anchorPoint)
            bezierPath.addLine(to: platePoint)
            lineLayer?.path = bezierPath.cgPath
        }
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

I looked up the data, but the problem is so comprehensive, I can't find a clear answer.

I need to understand and understand the causes of this problem. Why do I have this problem with my code?

For your information I was following the tutorial of this document.


Solution

  • You need to move your observeValueForKeyPath:ofObject:change:context and updateLine functions out of the loadView() function.

    The observeValueForKeyPath:ofObject:change:context function needs overriding at the ViewController level, as you have added the observer on self which in this case is the view controller.

    See below:

    import UIKit
    import PlaygroundSupport
    
    class MyViewController : UIViewController {
        private var animator: UIDynamicAnimator?
        private var gravity: UIGravityBehavior?
        private var attach: UIAttachmentBehavior?
        private var plateView: UIView?
    
        var lineLayer: CAShapeLayer?
    
        override func loadView() {
            let view = UIView()
            view.backgroundColor = .white
            self.view = view
    
            plateView = UIView(frame: CGRect(x: 80, y: 150, width: 60, height: 60))
            plateView?.backgroundColor = UIColor.orange
            self.view.addSubview(plateView!)
    
            animator = UIDynamicAnimator(referenceView: view)
    
            gravity = UIGravityBehavior(items: [plateView!])
            animator?.addBehavior(gravity!)
    
            attach = UIAttachmentBehavior(item: plateView!, offsetFromCenter: UIOffset(horizontal: 0, vertical: -30), attachedToAnchor: CGPoint(x: 200, y: 100))
    
            attach?.damping = 0.1
            attach?.frequency = 0.6
            animator?.addBehavior(attach!)
    
            plateView?.addObserver(self, forKeyPath: "center", options: .new, context: nil)
        }
    
        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            updateLine()
        }
    
        func updateLine() {
            if nil == lineLayer {
                lineLayer = CAShapeLayer()
                lineLayer?.strokeColor = UIColor.purple.cgColor
                lineLayer?.fillColor = UIColor.clear.cgColor
                lineLayer?.lineWidth = 1.5
                lineLayer?.lineJoin = .round
                lineLayer?.strokeEnd = 1.0
                self.view.layer.addSublayer(lineLayer!)
            }
    
            let platePoint = view.convert(CGPoint(x: plateView!.bounds.midX, y: 0), from: plateView)
            let bezierPath = UIBezierPath()
            bezierPath.move(to: attach!.anchorPoint)
            bezierPath.addLine(to: platePoint)
            lineLayer?.path = bezierPath.cgPath
        }
    
    }
    // Present the view controller in the Live View window
    PlaygroundPage.current.liveView = MyViewController()