Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-09-26

Swift Design Pattern: Swift Enum's, the Switch statement and an alternative solution

I love the Swift Enum's. Full stop.

But working with enum's and the switch statement? I don't like that too much, tends to make the code pretty obscure. In Swift 2 there is the if case which is better, but still does not make my code very readable.

Luckily there is an alternative solution possible, what I would call the is-enum pattern:

Traditionally I would define an enum like:

enum ValueItem {
    case NULL
    case BOOL(Bool)
    case STRING(String)
    case NUMBER(NSNumber)
    case ERROR(code: String, reason: String)
}

Let's say I am interested in a number and bool and only then want to execute some code. I have the following possibilities:

    // Only for bool and number:
    
    switch val {
    case .BOOL, .NUMBER:
        // code
    default: break
    }

Or:
    // Only for bool and number:
    
    var boolOrNum: false
    if case .NUMBER = val { boolOrNum = true }
    if case .BOOL = val { boolOrNum = true }
    
    if boolOrNum {
        // code
    }


Neither of which is not very transparent imo. What I would like is this:

    if val.isBool || val.isNumber {
        // code
    }

Even the comment line is no longer needed, the code is completely self documenting.

The only thing we have to do to enable this is to add the corresponding var's to the enum declaration:

enum ValueItem {
    case NULL
    case BOOL(Bool)
    case STRING(String)
    case NUMBER(NSNumber)
    case ERROR(code: String, reason: String)
    
    var isNull: Bool {
        if case .NULL = self { return true }
        return false
    }
    var isBool: Bool {
        if case .BOOL = self { return true }
        return false
    }
    
    var isString: Bool {
        if case .STRING = self { return true }
        return false
    }

    var isNumber: Bool {
        if case .NUMBER = self { return true }
        return false
    }

    var isError: Bool {
        if case .ERROR = self { return true }
        return false
    }
}

Easy.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-09-22

Creating a NSScrollView with variable size content

From time to time I have to create a NSScrollView with a custom content NSView that changes its size, like a text editor. In addition, the "canvas size" should appear unlimited in horizontal and vertical direction. Now, this is not very difficult, but it routinely catches me off-guard and I spend more time than I should on getting the fundamentals right. To my defence, Apple has changed the general approach a couple of times ... but enough excuses. In this post I want to detail the steps to be taken such that I save myself some effort the next time I have to do this...

Step 1: Create a xib file with a NSWindow in it, this window is the "document window".

Step 2: Drag and drop a NSView into the view of the window.

Step 3: Select the inner NSView and choose (Xcode 7) menu item "Editor -> Embed in -> Scroll View". This step is important, it sets up internals such that the inner NSView is connected correctly to the NSScrollView. Do not try to do this manually by dragging and dropping a NSScrollView into the window!

Step 4: Create your document view, I will call it LineView. This class inherits from NSView. This line view will need access to the document, for this I will add a reference to it (the document is created in step 7):

    var document: LineDocument! {

Step 5: In the xib file, select the inner NSView (inside the scrollview) and open the identity inspector. Now change the class of the NSView to your chosen document view (in my case: LineView).

Step 6: Also in the xib file, add constraints to LineView to position and size it. Do the same for the scrollview inside its superview. By default I add four constraints such that each view completely fills its superview.

The above will set up a functioning scrollview. But since I want to open a variable size document in it, here are the next things to do.

Step 7: Create your own document class by creating a new class (LineDocument) and subclass from NSDocument.

Step 8: Create a document window controller, inherit from NSWindowController. The window controller (LineDocumentWindowController) will be used to give the LineView inside the scrollview access to its line document object. In order to do so add the following to the implementation to the window controller:

    @IBOutlet weak var lineView: LineView!
    
    override func windowDidLoad() {
        lineView.document = document as? LineDocument

    }

Step: 9: In the xib file, change the class of the File Owner (using the identity inspector) to the new window controller.

Step 10: In the xib file, connect the lineView outlet in the file owner to the LineView in the scroll view.

Step 11: In our document (LineDocument) add the following code to create the document window:

    override func makeWindowControllers() {
        self.addWindowController(LineDocumentWindowController(windowNibName: "LineDocument"))
    }

Note: this assumes that the xib file is called "LineDocument.xib"

Of course you will need to add code to your document to manage the data that makes up the document.

And of course you will need to add code to your document view to actually create the view content (in drawRect).

There is however one more thing to do: if the users adds content to the document you will need to update the frame of the document view. This is best done by a notification, raised by the document, observed by the view.

So far so good, but if you want a background pattern (for example alternating line background colours) another problem will crop up: when the scrollview is sized to be bigger than necessary for the document, the background pattern will only be drawn directly behind the document content and not in the surrounding space.
To fix this it is necessary to artificially change the size of the frame of the document view to include the "empty" space in the document view.
For this, I use a function "adjustFrame" which is called in drawRect. A better solution would be to call that function only when the window is resized and when the size of the document changes. But simply including it in drawRect will suffice.

The adjustFrame function in the document view class looks as follows:

    /// Resizes the frame to always fill out the available space. This ensures that the whole frame in NSScrollView is always filled out with the background pattern, giving the illusion of an endlessly big canvas.

    private func adjustFrame() {
        
        // Determine the minimum size needed for our content area, then see if the contentview is bigger. When either width or height of the contentview area is bigger, use those values.
        
        let neededFrame = minimumLinesRectangle
        let availableSize = enclosingScrollView!.contentSize
        var newFrame = frame
        
        newFrame.size.width = max(neededFrame.size.width, availableSize.width)
        newFrame.size.height = max(neededFrame.size.height, availableSize.height)
        
        frame = newFrame
    }

PS: The minimumLinesRectangle is a property that evaluates the minimum size needed to display all of the document data.

Update 2015.10.01: I still have problems where the scrollview sometimes jumps back to the upper-left corner of the documentView. I finally solved that by creating the scrollview programatically. Seems there is a difference between interface builder and programatically created scrollview hierarchies. Go figure. You can read about the programatic example here.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-09-21

Swift extensions: Comparable limiters

Your code will likely contain some of these kind of statements:

    var b = calculateValue(a)
    if b > limit { b = limit }

Typically this kind of construction is used on array indicies as well.

    var b = calculateValue(a)
    if b > array.count { b = array.count }

My problem with this construction is that it is not immediately clear what is being done, I have to read it to see that in fact this is no more than limiting the calculated value to a certain maximum.

Luckily most of the numbers that we need this for implement the Comparable protocol. This allows us to write a little extension:

extension Comparable {
    
    func limitTo(max max: Self) -> Self {
        if self < max { return self }
        return max
    }

    func limitTo(min min: Self) -> Self {
        if self > min { return self }
        return min
    }
    
    func limitTo(min min: Self, max: Self) -> Self {
        return self.limitTo(max: max).limitTo(min: min)
    }

    mutating func limitInPlaceTo(min min: Self) {
        if self < min { self = min }
    }
    
    mutating func limitInPlaceTo(max max: Self) {
        if self > max { self = max }
    }
    
    mutating func limitInPlaceTo(min: Self, max: Self) {
        self = self.limitTo(min:min, max: max)
    }
}

With this extension the above lines can be rewritten as:

    var b = calculateValue(a).limitTo(max: limit)

and

    var b = calculateValue(a).limitTo(max: array.count)

Of course the other functions can be used as well:

    b.limitInPlaceTo(min: limit)

Another advantage is that using these functions can change some of the variable definitions from a var to a let because it is no longer necessary to perform the limiting operation separate.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-09-20

Swift extensions: OptionSetType factory

In Swift 2.0 Apple has introduced the OptionSetType, this basic type is used for options parameters into the Cocoa API's. As an example I will use NSDirectoryEnumerationOptions

Unfortunately I have not been able to discover (so far) a nice way to assemble such options.
I thus end up coding this as:

let dirScanOptions = NSDirectoryEnumerationOptions(rawValue :
   NSDirectoryEnumerationOptions.SkipsHiddenFiles.rawValue |
   NSDirectoryEnumerationOptions.SkipsPackageDescendants.rawValue |
   NSDirectoryEnumerationOptions.SkipsSubdirectoryDescendants.rawValue)

That works, but is more verbose than necessary imo.

I thus wrote a little extension that gives me a factory method to create these kind of values as follows:

let dirScanOptions = NSDirectoryEnumerationOptions.withOptions(.SkipsHiddenFiles, .SkipsPackageDescendants, .SkipsSubdirectoryDescendants)

The extension itself is simple enough:

extension OptionSetType where RawValue : BitwiseOperationsType {
    
    static func withOptions(items: Self...) -> Self {
        var result = items[0]
        for item in items {
            result = Self(rawValue: (result.rawValue | item.rawValue))
        }
        return result
    }

}

Since the actual 'or'-ing is rather fast, I have skipped the effort of starting at the second item. Or-ing the first item with itself will not change the result and could be even faster than excluding the first item from the loop.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-09-19

Code sample: Throwing an NSError to OS-X

NSError is used in document based app's to inform the OS about errors that occurred while opening or saving data (and in other places as well).
Since the OS will attempt to inform the user with a popup about the error, it is good practise to provide actual useful information in the thrown error such that the user sees more than "app X has thrown error 16"

Here an example on how to do this for the readFromData function.

override func readFromData(data: NSData, ofType typeName: String) throws {
                        
        if let fileAsString = NSString(bytes: data.bytes, length: data.length, encoding: NSUTF8StringEncoding) as? String {
                        
            // do stuff ....
            
        } else {
            
            let errorDict = [
                NSLocalizedDescriptionKey : "Could not convert the file data to a UTF8 string. (File at path = \(fileURL?.path))",
                NSLocalizedRecoverySuggestionErrorKey: "Try to open the file in TextEdit to see if it does contain text."
            ]

            let error = NSError(
               domain: Globals.errorDomain,
               code: Globals.errorCode_cannotRetrieveFileAsString, userInfo: errorDict)
            throw error
        }

    }

The fileURL is defined in NSDocument and can be used to get the URL of the file that is being openend.

The popup dialogue that is provided to the user uses the two key's in the errorDict to retrieve information from the error. (NSLocalizedDescriptionKey and NSLocalizedRecoverySuggestionErrorKey) There are more key's, but these two are the one's you should provide as a minimum.

It is customary to provide the errorDomain in backward URL notation, eg: "com.mycompany.MyGreatApp.ErrorDomain" the code can be any number within this error domain.

If you do not provide an errorDict, OS-X will create an localized description from the error domain and code number.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-09-18

Code sample: NSError and Swift 2.0

This is a follow up on a previous post about the NSError (pointer).

In Swift 2.0 the NSError in the NSFileManager calls have been replaced with try blocks.

Here is how Swift 2.0 implements the NSError in try blocks:

    lazy private var applicationSupportLogfileDirectory: String? = {


        let fileManager = NSFileManager.defaultManager()
        
        do {
            let applicationSupportDirectory =
                try fileManager.URLForDirectory(
                    NSSearchPathDirectory.ApplicationSupportDirectory,
                    inDomain: NSSearchPathDomainMask.UserDomainMask,
                    appropriateForURL: nil,
                    create: true).path!
                
            let appName = NSProcessInfo.processInfo().processName
            let dirUrl = NSURL(fileURLWithPath: applicationSupportDirectory, isDirectory: true).URLByAppendingPathComponent(appName)
            return dirUrl.URLByAppendingPathComponent("Logfiles").path

        } catch let error as NSError {
        
            let message: String = "Could not get application support directory, error = " + (error.localizedDescription ?? "Unknown reason")
            return nil
        }

    }()

Or without all the verbose stuff:

        do {
            
            try NSFileManager.defaultManager()...some file manager call that throws an error
                
            ...

        } catch let error as NSError {
        
            print "Could not ... " + (error.localizedDescription ?? "Unknown reason")

        }

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-09-16

Code sample: A runloop observer in Swift.

The problem: I have a text file that must conform to certain syntax rules. The user can edit the file, and the GUI should show if the lexical structure is correct. If not, it should show where the syntax rules are violated and show some information as to which rule is violated. (Very similar to spelling errors in text editing GUI's)

After each editing operation (delete char, insert char, etc) the entire file must be checked again for a correct syntax. Of course when a file gets really large, these checks can take a while. This delay should not impact the user while he is typing.

The solution is to do the syntax check in its own thread, not in the main runloop that processes the GUI. But the results of these checks must be fed back into the GUI processing thread (main runloop). Apple has created runloop observers for such situations.

Once the main runloop has processed all events for the current go-around it is put to sleep (or made waiting) until new events arrive. It is possible to start a process of our own when this happens. That process is called a runloop observer and forms part of the main runloop. As such, the runloop observer can safely update the GUI.

Ok, so far the theory, here is the code:

    private var editCount: Int = 0
    private let parseQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
    private var parseResults: Array<Parser.Result> = []

    func lexicalStructureChanged() {
        
        // When the lexical structure changes, the parser must process the entire document again.
        // Since parsing large documents can take some time, parsing is done in a separate thread.
        // This thread is executed asynchronously on the parseQueue. It communicates the results back via the parseResults array.
        // A runloop observer is added to the current runloop to check for the availability of results. That observer will keep observing until a result is found and then dismiss itself.
        // Note that the observer will not try to update a document with a different "editCount" as the editCount the parser was started with. This ensures that old results are discarded.
        // The editCount will always be >0, but the parser editCount may be 0 if there was no error, and thus no action to be taken.
        
        let runLoopObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.BeforeWaiting.rawValue, Boolean(1), 0, {
            
            (observer: CFRunLoopObserver!, activity: CFRunLoopActivity) -> Void in // This will keep a reference to self alive until all parse results are processed
            
            // Check if a result is available
            
            if self.parseResults.isEmpty { return }
            
            
            // There is a result, this observer can be detached
            
            CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes)
            
            
            // Process the result
            
            self.processParseResults()
            
        } )
        
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), runLoopObserver, kCFRunLoopCommonModes)
        
        editCount++

        dispatch_async(parseQueue, { // This will keep a reference to self alive until the parser is done
            self.parseResults.append(Parser.parse(self.lines, editCount: self.editCount))
        } )
    }

    private func processParseResults() {

        let (count, lineIndex, elementIndex, message) = parseResults.removeAtIndex(0)

        if count < editCount { return }
        ...
    }

The first function lexicalStructureChanged is called for every lexical change to the document.

The second function is only shown to illustrate that the oldest entry in the parseResults array is removed when the runloop observer is called.

The runloop is first created (let runLoopObserver =) then added (CFRunLoopAddObserver) and after the runloop observer is in place, the parser is started as an asynchronous process using GCD (dispatch_async).

The parser does nothing with the editCount parameter except returning exactly the same value as it was started with. Thus allowing the process processParseResults to ignore results that were returned for older edits of the document. This will probably only happen when the user types very fast and the document is sufficiently large. In my tests so far the parsing was always complete in less than a millisecond, thus the updates to the GUI were faster than my typing!

Note that both code blocks (closures) retain "self". This ensures that Self is available when the parseResults and the runloop observer are called. The "weak" or "unowned" modifiers should not be used. Otherwise a runtime error could occur when the user closes the document before the parser is ready.

PS: Make sure that you do not update the GUI from a process in the parseQueue, you will get very strange error messages from CoreAnimation that way. If there is a need to update the GUI from that thread use:

                dispatch_async(dispatch_get_main_queue(), { guiUpdate() })

(and yes, you could do all GUI updating that way, but it gets very difficult to know which update is still applicable and which is not)

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-09-15

Swift Gotcha: Unexpected observer behaviour for inout arguments

I recently ran into an observer problem: it was being called without updates to the associated variable.

Since I was using the associated variable as an inout argument, that was my first suspect. So I wrote a little test in playground to investigate this:

So, ... indeed. The observer of 'str' is called even though the property is not updated in "testDidSetObserver". Note that even when the if statement is removed from the function the observer is still called. It seems that using a property in an inout argument will always trigger the property observers after the function is finished.

I also ran a test that prints the value of "countDidSet" inside the function, and the count is 0 inside the function. Thus the observer is indeed triggered after the function finishes, whether the property is accessed or not.

So observers and inout arguments don't mix well.

PS: I also ran the test for the willSet observer, same results.

Note: As of Swift 2.2 this behaviour is now documented in the manual with a little explanation as to why.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-09-09

Adding tooltips to changing NSView content

In the App I am currently working on I have an array of strings in an NSView and some of these lines may have one or more errors in them. The error's are red underlined, but I needed a way to inform the user about the nature of the error. Tooltips are the best way imo, the user hovers the mouse over the underlined word, pauses a little, and up pops the error reason.
Next the user may edit the line and remove the cause of the error, the line is displayed again without the underlining, and of course the tooltip must disappear also.

So how to add and remove those tooltips?

It turns out that each NSView already the ToolTip management build in. We only have to create tooltips and add them to the view when we need them. And of course remove them when no longer needed.

This does mean that we need way to associate the tooltip with error no N in line X, and when that error disappears, the tooltip must be removed.

I found the easiest solution for me was to add the tooltip to the data model. Yes, Ugh.. I can hear you..

But the alternative is to build a paralel data structure in my subclassed NSView to (the lines in) the data model and somehow keep those two synchronised. A double Ugh..

So I added the tooltip to the data model.

In objective-C the tooltip can consist of an NSString, in Swift that did not work for me, probably because String is bridged to NSString, but the tooltip needs to inherit from NSObject. Or maybe there is another reason. Anyhow this issue is moot as I needed to track the tooltips and thus had to create a class that encompasses the tooltip:

    class ToolTip: NSObject {
        
        var tip: String
        
        var tag: NSToolTipTag?
        
        init(tip: String) {
            self.tip = tip
            super.init()
        }
        
        override func view(view: NSView,
            stringForToolTip tag: NSToolTipTag,
            point: NSPoint,
            userData data: UnsafeMutablePointer<Void>) -> String {
                return self.tip
        }
    }
    

    typealias ToolTips = Array<ToolTip>

In each line in the datamodel I added some storage for the tooltips

    // Note: Never ever handle the tooltips from within the data model.
    // The tooltips are managed by the NSView that displays this line.
    

    var toolTips: ToolTips = ToolTips()

In MyDocumentView (that is subclassed from NSView) I added the following code to the code that draws a single line. Note that drawLine is called from drawRect.

    /// Draws the line for the given index.
    
    private func drawLine(index: Int) {
        
        // Note: the index can refer to a non-existing line!
        
        // Strategy:
        // 1. Determine the background color.
        // 2. Draw the backgroud color.
        // 3. Add the text.
        // 4. ...
        // 5. ...
        // 6. Add error marker and tooltip.
        // 7. ...
        
        
        let context = NSGraphicsContext.currentContext()!.CGContext
        
        
        // 1. Set the background color
        
        if ((index % 2) == 0) {
            CGContextSetFillColorWithColor(context, normalBackgroundColor)
        } else {
            CGContextSetFillColorWithColor(context, alternateBackgroundColor)
        }
        
        
        // Create the rectangle for the background color
        
        let backgroundRect = rectForLine(index)
        
        
        // 2. Draw the background
        
        CGContextBeginPath(context)
        CGContextFillRect(context, backgroundRect)
        CGContextDrawPath(context, kCGPathFill)
        
        
        // Draw the text, insertion point or selection only for existing lines
        
        if index < document.lines.count {

            
            // 3. Add the text
            
            let xOffset = MARGIN_BEFORE_LINE
            let yOffset = lineHeight * CGFloat(index) - MARGIN_BELOW_LINE
            (document.lines[index].string as NSString).drawAtPoint(CGPointMake(xOffset, yOffset), withAttributes: document.fontDict)
            
            
            // 4...
            
            
            // 5...
            
            
            // 6. Add error markers and tooltips.
            
            // Remove old tooltips associated with this line
            
            for toolTip in document.lines[index].toolTips {
                self.removeToolTip(toolTip.tag!)
            }
            document.lines[index].toolTips.removeAll(keepCapacity: true)

            for err in document.lines[index].errorData {
                
                let y = lineHeight * CGFloat(index) - MARGIN_BELOW_LINE
                
                let x = document.lines[index].offsetForIndex(err.range.startIndex)
                        + MARGIN_BEFORE_LINE
                
                let width = (document.lines[index].offsetForIndex(err.range.endIndex)
                            - document.lines[index].offsetForIndex(err.range.startIndex))
                
                
                // Do the red underlining
                
                CGContextSetAllowsAntialiasing(context, false); // Ensure hairline drawing
                CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0)
                CGContextSetLineWidth(context, 1.0)
                CGContextMoveToPoint(context, x, y + lineHeight - 1.0)
                CGContextAddLineToPoint(context, x + width, y + lineHeight - 1.0)
                CGContextDrawPath(context, kCGPathStroke)
                CGContextSetAllowsAntialiasing(context, true) // Disable hairlines
            
            
                // Add the new tooltip
            
                let toolTip = ToolTip(tip: err.reason)
                let toolTipRect = NSRect(x: x, y: y, width: width, height: lineHeight)
                toolTip.tag = addToolTipRect(toolTipRect, owner: toolTip, userData: nil)
                document.lines[index].toolTips.append(toolTip)
            }
        }
        
        
        // 7...

    }

Obviously this is not the complete code to draw the line, but everything related to the tooltips is in here.
Of note is the following: Before drawing the red underlining and registering the tooltips, the old tooltips for the line are removed from the view and the data model. Then new tooltips are created and added to the view and the datamodel. The data model only stores the tooltips in order to be able to remove them later. This way, when the user edits the line we can be sure that any old error related tooltips are gone from the view. The user thus gets the feedback that the error was corrected.
The other thing to note is that the tag value generated by the view is later used to remove the tooltip from the view.
Oh, one last thing: this view uses the flipped Y coordinates: override var flipped: Bool { get { return true }}

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.