Search code examples
iosswiftuitextviewuiactivityindicatorview

How to add activity indicator to UITextView?


I have a UITextView that gets and loads a file from a server. How do I add an activity indicator to show that it's loading? The server hosting it is a bit slow.

Here is code:

let url = NSURL(string: "http://www.aliectronics.com.au/thefournobletruths.rtf")
let data = NSData(contentsOfURL: url!)

do {
    let options = [NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType]
    let attributedString = try NSAttributedString(data: data!, options: options, documentAttributes: nil)
    print(attributedString)
    textview2.attributedText = attributedString
    textview2.editable = false
} catch {
    NSLog("\(error)")

Solution

  • You should not use Data contents Of URL initializer to download data synchronously. There is no guarantee it will succeed. You need to download your data asynchronously using NSURLSession method dataTaskWithURL as I have already suggested to you at your last question. Regarding your new question it has been already answered here. Combining both answers you should be able to accomplish what you are trying to. More info commented at the code bellow::

    import UIKit
    class ViewController: UIViewController {
        @IBOutlet var textview2: UITextView!
        // declare your activityIndicator
        let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .white)
        // you will need a new view for your activity indicator and a label for your message
        let messageFrame = UIView()
        let strLabel = UILabel(frame: CGRect(x: 50, y: 0, width: 160, height: 50))
        func displayActivityIndicator() {
            strLabel.text = "Loading file"
            strLabel.textColor = .white
            messageFrame.layer.cornerRadius = 15
            messageFrame.backgroundColor = UIColor(white: 0, alpha: 0.7)
            activityIndicator.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
            activityIndicator.startAnimating()
            messageFrame.addSubview(activityIndicator)
            messageFrame.addSubview(strLabel)
            view.addSubview(messageFrame)
        }
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            // set your message frame (you can also position it using textview2.frame
            messageFrame.frame = CGRect(x: view.frame.midX - 80, y: view.frame.midY - 25 , width: 160, height: 50)
            // call your activity indicator method
            displayActivityIndicator()
            // call your method to download your data asynchronously
            getRTFData(from: "http://thewalter.net/stef/software/rtfx/sample.rtf", textview2)
        }
        func getRTFData(from link: String,_ textView: UITextView) {
            // make sure your link is valid NSURL using guard
            guard let url = URL(string: link) else { return }
            // creata a data task for your url
            URLSession.shared.dataTask(with: url) {
                (data, response, error) in
                // use guard to make sure you get a valid response from the server and your data it is not nil and you got no errors otherwise return
                guard
                    let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
                    let data = data, error == nil
                else { return }
                // you need to use dispatch async to update the UI
                DispatchQueue.main.async {
                    // stop animating the activity indicator and remove the messageFrame from the view
                    self.activityIndicator.stopAnimating()
                    self.messageFrame.removeFromSuperview()
    
                    // NSAttributedString data initialiser throws an error so you need to implement Do Try Catch error handling
                    do {
                        textView.attributedText = try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
                        textView.isEditable = false
                    } catch {
                        print(error)
                    }
                }
            }.resume()
        }
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    }