Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer - ios

Tags:

ios

swift

xcode7

I am developing ios app by using swift. I am using xcode7.0.1. with TableViewController. I want to expand when i click row and collapse when i click it again. I am following the tutorial from gitHub. Now i am facing the error Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer for the key path "frame" from <X.expandTips 0x145d8d20> because it is not registered as an observer.'

I hope following line of code will causing the issue.

My UITableViewCell class code:

func checkHeight(){
        expandaple_view.hidden = (frame.size.height < expandTips.expandedHeight)
    }
    
    func WatchFrameChanges() {
        addObserver(self , forKeyPath: "frame", options: .New, context: nil )
        checkHeight() 
    }
    
    func ignoreFrameChanges(){
       
        removeObserver(self, forKeyPath: "frame")
        
    }
    
    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if keyPath == "frame"{
            checkHeight()
        }
    }

And in my TableViewController code:

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
       (cell as! expandTips) . WatchFrameChanges()
    }
    
    override func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        (cell as! expandTips) . ignoreFrameChanges()
    }
    
    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        if (indexPath == selectedIndexPath ){
            return expandTips.expandedHeight
        }else {
            return expandTips.defaultHeight
        }
    }

I am new to ios. I hope it must be simple issue. Please someone help me to solve this.

I don't know what are the details needs to post here. Please comment if i want to add more details.

like image 987
Amsheer Avatar asked Oct 02 '15 06:10

Amsheer


2 Answers

From the comment of @Leandros I found the solution. Just tracking the observer using boolean variable.

Use of following code, I found the solution:

 var frameAdded = false
 func checkHeight(){

        expanding_view.hidden = (frame.size.height < expandTips.expandedHeight)
    }

    func WatchFrameChanges() {
        if(!frameAdded){
        addObserver(self , forKeyPath: "frame", options: .New, context: nil )
            frameAdded = true
        }
    }

    func ignoreFrameChanges() {
          if(frameAdded){
        removeObserver(self, forKeyPath: "frame")
            frameAdded = false
        }
    }

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if keyPath == "frame"{
            checkHeight()
        }
    }
    deinit {
        print("deinit called");
        ignoreFrameChanges()
    }
like image 145
Amsheer Avatar answered Oct 17 '22 02:10

Amsheer


Removing the KVO observer is throwing an exception, if there is no observer registered.

Unfortunately, there is no way to check if an observer is registered. A well known workaround is to just catch the exception like this (Objective-C):

@try {
    [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(isFinished))];
} @catch (NSException * __unused exception) {}

To do it in Swift, you have to be using Swift 2.1, since until than it lacked support for try {} catch {}. You can see how it works in 2.1 here.

Correction: Although Swift 2 introduced its own error handling convention, with do/try/catch keywords, these do not mean the same thing as they do in Objective-C, and there is still (as of Swift 2.1 and Xcode 7.1) no way in Swift to handle NSExceptions thrown from the system libraries other than to write Objective-C code to @catch them, and then call that code from Swift.

You can read more about KVO in general here, which also has a section about safe unsubscribing which mentions this unfortunate design.

like image 43
Leandros Avatar answered Oct 17 '22 00:10

Leandros