0
votes

I work on my first OSX app in Swift and I'm lost in some Cocoa concepts.

I have a custom NSView placed inside NSScrollView. I draw complicated chart in overrided drawRect function. Chart is pretty big but NSScrollView does its job to scroll the whole view.

What I'm not happy about is that I redraw the whole chart each time drawRect is called and it's called really frequently when scrolling, resizing, etc.

Drawing a chart requires a lot of calculations and a lot of shapes need to be painted. Doing it each time a drawRect is called seems to be wrong. I know I could limit drawing to the visible / dirty rectangle, but it would be even more complicated and require even more calculations.

Is there a better approach to drawing such a scrollable chart? It would be perfect to draw the whole chart once and not to worry about redrawing it later.

I want to add some interactivity (mouse clicks) to my chart in the future. It might be important information.

Thank you

1

1 Answers

3
votes

If your custom view doesn't need to be "live" during drawing, meaning that scrolling doesn't change the content of the view then you can do this multiple ways.

I have approached this type of problem by knowing when the view will scroll. One thing you might want to look at is registering your custom view for notifications from the scroll view. Specifically NSScrollViewWillStartLiveScrollNotification. When the view will scroll you can cache your custom view into an NSBitmapImageRep and draw that instead. I've done this before with a very complicated view and it worked really well.

Also another, possibly simpler thing to look at, is the scrollsDynamically property of NSScrollView.

In Swift 2, you can use the following to help

In your awakeFromNib

NSNotificationCenter.defaultCenter().addObserver(self, selector: "startScrolling", name: NSScrollViewWillStartLiveScrollNotification, object: self.scrollView)

NSNotificationCenter.defaultCenter().addObserver(self, selector: "endScrolling", name: NSScrollViewDidEndLiveScrollNotification, object: self.scrollView)

Then

func startScrolling() {
    //viewRep is an NSBitmapImageRep variable in your class
    //create a bitmap representation of your view
    self.lockFocus()
    self.viewRep = NSBitmapImageRep(focusedViewRect: self.bounds)
    self.unlockFocus()

    //set a flag which draw rect uses to know to draw the bitmaprep
    //viewRep is a Bool variable in your class
    self.scrollingFlag = true
    self.needsDisplay = true
}

func endScrolling() {
    self.scrollingFlag = false
    self.needsDisplay = true
}

your drawing method

override func drawRect(dirtyRect: NSRect) {
    if self.scrolling {
         self.viewRep.drawInRect(....) // you can draw the viewRep here
    }
    else {
         //your normal drawing code here
    }
}