Sans Pareil Technologies, Inc.

Key To Your Business

Lab 5: Text Input


In this exercise we will look at how we can handle custom text types and apply rules to user input via text input components.
Displaying formatted text 
Create a project named lab5 and create an interface with two buttons - HTML and RTF, and a UITextView that fills the rest of the screen.
Screen Shot 2017-09-18 at 16.48.21
We will use two resources files that we will load and display in the text view when the appropriate buttons are clicked. Add a lorem.html and lorem.rtf file to the project. They may contain any text, just needs to be HTML and RTF documents.

Modify the ViewController and add the action handler functions which will load the resource files and represent them using a NSAttributedString class.


Xcode 7
  @IBOutlet weak var textView: UITextView!
  
  @IBAction func displayHtml() {
    if let url = NSBundle.mainBundle().URLForResource("lorem", withExtension: "html") {
      do {
        let data = NSData(contentsOfURL: url)
        let attrStr = try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType], documentAttributes: nil)
        textView.attributedText = attrStr
      }
      catch {
        NSLog("error creating attributed string from HTML”)
      }
    }
  }
  
  @IBAction func displayRtf() {
    if let url = NSBundle.mainBundle().URLForResource("lorem", withExtension: "rtf") {
      do {
        let data = NSData(contentsOfURL: url)
        let attrStr = try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute:NSRTFTextDocumentType], documentAttributes: nil)
        textView.attributedText = attrStr
      }
      catch {
        NSLog("error creating attributed string from RTF“)
      }
    }
  }
Xcode 8+
  @IBOutlet weak var textView: UITextView!
  
  @IBAction func displayHtml() {
    if let url = Bundle.main.url(forResource: "lorem", withExtension: "html") {
      do {
        let data = try Data(contentsOf: url)
        let attrStr = try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType], documentAttributes: nil)
        textView.attributedText = attrStr
      }
      catch {
        NSLog("error creating attributed string")
      }
    }
  }
  
  @IBAction func displayRtf() {
    if let url = Bundle.main.url(forResource: "lorem", withExtension: "rtf") {
      do {
        let data = try Data(contentsOf: url)
        let attrStr = try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute:NSRTFTextDocumentType], documentAttributes: nil)
        textView.attributedText = attrStr
      }
      catch {
        NSLog("error creating attributed string")
      }
    }
  }
Formatting Selected Text 
We will now use the textStorage property for a UITextView to change the formatting attributes of the user selected text in the view.

To make things easier, we will add an extension to UITextView that will return a range of the selected text within the text displayed in the view.

Xcode 7
extension UITextView {
    func selectedTextIndexes() -> (startIndex:String.Index, endIndex:String.Index)? {
        if let range = self.selectedTextRange {
            if !range.empty {
                let location = self.offsetFromPosition(self.beginningOfDocument, toPosition: range.start)
                let length = self.offsetFromPosition(range.start, toPosition: range.end)
                
                let startIndex = self.text!.startIndex.advancedBy(location)
                let endIndex = self.text!.startIndex.advancedBy(length)
                
                return (startIndex, endIndex)
            }
        }
        return nil
    }
}
Xcode 8+
extension UITextView {
  func selectedTextIndexes() -> (startIndex:String.Index, endIndex:String.Index)? {
    if let range = self.selectedTextRange {
      if !range.isEmpty {
        let location = self.offset(from: self.beginningOfDocument, to: range.start)
        let length = self.offset(from: range.start, to: range.end)
        
        let startIndex = self.text!.index(self.text!.startIndex, offsetBy: location)
        let endIndex = self.text!.index(startIndex, offsetBy: length)
        
        return (startIndex, endIndex)
      }
    }
    return nil
  }
}
Implement the UITextViewDelegate.textViewDidChangeSelection(textView:) delegate function to intercept text selection events.

Xcode 7
    func textViewDidChangeSelection(textView: UITextView) {
        if let _ = textView.selectedTextIndexes() {
            textView.textStorage.addAttributes([NSFontAttributeName: UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)], range: textView.selectedRange)
        }
    }
Xcode 8+
  func textViewDidChangeSelection(_ textView: UITextView) {
    if let selectedTextIndexes = textView.selectedTextIndexes() {
      let startIndex = selectedTextIndexes.startIndex
      let endIndex = selectedTextIndexes.endIndex
      if (textView.text?.substring(with: startIndex..<endIndex)) != nil {
        textView.textStorage.addAttributes([NSFontAttributeName:UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)], range: textView.selectedRange)
      }
    }
  }