Search code examples
iosswiftxcode6cursoruitextview

Cursor moving up and down


I have a UITextView in my app which is updated as the user types. But when I start typing in the UITextView, the cursor starts to flicker and move up and down quickly. How can I fix this bug?

This is how the UITextView is update-

A Cocoa Touch Class file called Highlighter.swift-

import Foundation
import UIKit

// text hightlighter

class SyntaxGroup {

var wordCollection : [String]
var type : String
var color : UIColor

init(wordCollection_I : [String], type_I : String, color_I: UIColor) {

    wordCollection = wordCollection_I
    type = type_I
    color = color_I

}
}

class SyntaxDictionairy {

var collections : [SyntaxGroup] = []

}

class SyntaxRange {

var range : NSRange
var color : UIColor

init (color_I : UIColor, range_I : NSRange) {
    color = color_I
    range = range_I
}

}

class HighLighter {

private var ranges : [SyntaxRange] = []
var highlightedString : NSMutableAttributedString = NSMutableAttributedString()
var syntaxDictionairy : SyntaxDictionairy

init (syntaxDictionairy_I : SyntaxDictionairy) {

    syntaxDictionairy = syntaxDictionairy_I

}

func run(string : String?, completion: (finished: Bool) -> Void) {

    ranges = []
    highlightedString = NSMutableAttributedString()
    var baseString = NSMutableString()

    let qualityOfServiceClass = QOS_CLASS_DEFAULT
    let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
    dispatch_async(backgroundQueue) { () -> Void in

        if string != nil && string != "" {

            self.highlightedString = NSMutableAttributedString(string: string!)

            for i in 0..<self.syntaxDictionairy.collections.count {

                for iB in 0..<self.syntaxDictionairy.collections[i].wordCollection.count {

                    let currentWordToCheck = self.syntaxDictionairy.collections[i].wordCollection[iB]
                    baseString = NSMutableString(string: string!)

                    while baseString.containsString(self.syntaxDictionairy.collections[i].wordCollection[iB]) {

                        let nsRange = (baseString as NSString).rangeOfString(currentWordToCheck)
                        let newSyntaxRange = SyntaxRange(color_I: self.syntaxDictionairy.collections[i].color, range_I: nsRange)
                        self.ranges.append(newSyntaxRange)

                        var replaceString = ""
                        for _ in 0..<nsRange.length {
                            replaceString += "§" // secret unallowed character
                        }
                        baseString.replaceCharactersInRange(nsRange, withString: replaceString)
                    }
                }
            }
            for i in 0..<self.ranges.count {

                self.highlightedString.addAttribute(NSForegroundColorAttributeName, value: self.ranges[i].color, range: self.ranges[i].range)

            }
        }

        dispatch_sync(dispatch_get_main_queue()) { () -> Void in

            completion(finished: true)
        }

    }
}

}

This is the code in the ViewController.swift file-

import UIKit

class ViewController: UIViewController, UITextViewDelegate {

@IBOutlet weak var myTextView: UITextView!

var syntaxHighLighter : HighLighter!

override func viewDidLoad() {
    super.viewDidLoad()
    setUpHighLighter()
    myTextView.delegate = self

}

func setUpHighLighter() {

    // build a dict of words to highlight
    let redColor = UIColor(red: 0.5, green: 0.0, blue: 0.0, alpha: 1.0)
    let blueColor = UIColor(red: 0.0, green: 0.0, blue: 0.5, alpha: 1.0)
    let greenColor = UIColor(red: 0.0, green: 0.5, blue: 0.0, alpha: 1.0)

    let redGroup = SyntaxGroup(wordCollection_I: ["red","bordeaux"], type_I: "Color", color_I: redColor)
    let blueGroup = SyntaxGroup(wordCollection_I: ["coralblue","blue","skyblue","azur"], type_I: "Color", color_I: blueColor)
    let greenGroup = SyntaxGroup(wordCollection_I: ["green"], type_I: "Color", color_I: greenColor)

    let dictionairy : SyntaxDictionairy = SyntaxDictionairy()
    dictionairy.collections.append(blueGroup)
    dictionairy.collections.append(greenGroup)
    dictionairy.collections.append(redGroup)

    syntaxHighLighter = HighLighter(syntaxDictionairy_I: dictionairy)

}

func textViewDidChange(textView: UITextView) {
    let currentRange = myTextView.selectedRange
    syntaxHighLighter.run(myTextView.text) { (finished) -> Void in
        self.myTextView.attributedText = self.syntaxHighLighter.highlightedString
        self.myTextView.selectedRange = currentRange
    }

}

   }

Solution

  • Check that you are running the latest OS and Xcode.

    Try this to figure out when it is jumping: You could again use a bool to check if the selection change is because of the reset after highlighting or not. If it is not you can use a breakpoint to see what is going on.

    func textViewDidChangeSelection(textView: UITextView) {
    
       print("selection changed: \(myTextView.selectedTextRange!)")
    
    }
    

    This could help: a check too see if the length of the textview is still equal to the length of the highlighted string.

    func textViewDidChange(textView: UITextView) {
    
        let currentRange = myTextView.selectedRange
    
        syntaxHighLighter.run(myTextView.text) { (finished) -> Void in
    
            // if the highlighter was slower than typing, ABORT
            guard let textInUITextView = self.myTextfield.attributedText where textInUITextView.length == self.syntaxHighLighter.highlightedString.length else {
                return
            }
    
            self.myTextView.attributedText = self.syntaxHighLighter.highlightedString
            self.myTextView.selectedRange = currentRange
        }
    
    }
    

    This could help: it will prevent the highlighter from running more than once.

    class HighLighter {
    
        private var ranges : [SyntaxRange] = []
        var highlightedString : NSMutableAttributedString = NSMutableAttributedString()
        var syntaxDictionairy : SyntaxDictionairy
    
        private var running : Bool = false
    
        init (syntaxDictionairy_I : SyntaxDictionairy) {
    
            syntaxDictionairy = syntaxDictionairy_I
    
        }
    
        func run(string : String?, completion: (finished: Bool) -> Void) {
    
            if running == true {
                print("double action")
                return
            }
    
            running = true
    
            ranges = []
            highlightedString = NSMutableAttributedString()
            var baseString = NSMutableString()
    
            let qualityOfServiceClass = QOS_CLASS_DEFAULT
            let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
            dispatch_async(backgroundQueue) { () -> Void in
    
                if string != nil && string != "" {
    
                    self.highlightedString = NSMutableAttributedString(string: string!)
    
                    for i in 0..<self.syntaxDictionairy.collections.count {
    
                        for iB in 0..<self.syntaxDictionairy.collections[i].wordCollection.count {
    
                            let currentWordToCheck = self.syntaxDictionairy.collections[i].wordCollection[iB]
                            baseString = NSMutableString(string: string!)
    
                            while baseString.containsString(self.syntaxDictionairy.collections[i].wordCollection[iB]) {
    
                                let nsRange = (baseString as NSString).rangeOfString(currentWordToCheck)
                                let newSyntaxRange = SyntaxRange(color_I: self.syntaxDictionairy.collections[i].color, range_I: nsRange)
                                self.ranges.append(newSyntaxRange)
    
                                var replaceString = ""
                                for _ in 0..<nsRange.length {
                                    replaceString += "§" // secret unallowed character
                                }
                                baseString.replaceCharactersInRange(nsRange, withString: replaceString)
                            }
                        }
                    }
                    for i in 0..<self.ranges.count {
    
                        self.highlightedString.addAttribute(NSForegroundColorAttributeName, value: self.ranges[i].color, range: self.ranges[i].range)
    
                    }
                }
    
                dispatch_sync(dispatch_get_main_queue()) { () -> Void in
                    self.running = false
                    completion(finished: true)
                }
            }
        }
    }