Access label.text from separate class in swift

2019-04-17 02:06发布

问题:

I have a viewController with a button allowing user to download a file from a url. To keep my code clean I created a download class that I call from my view. It works fine but now I want to provide users with some UI cues about the download.

Let say I have a label and I want to change its text to "Downloaded" once the download is over. How one should do that?

here is the code I have:

FileViewController

@IBOutlet weak var downloadLbl: UILabel!

func downloadFile(sender:UIButton!)
    {      
        fileDownloader().download_zip(datastring, destination: path, name: naming, fileis: self.fileId)
    }

(this is only the important part)

then I have my download function inside a separate class file and I start a DownloadTaskSession and I use the following delegate to observe download completion

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
        println("session \(session) has finished the download task \(downloadTask) of URL \(location).")

        FileViewController().downloadLbl.text = "downloaded"
    }

the last line: FileViewController().downloadLbl.text = "downloaded" returns an error: fatal error: unexpectedly found nil while unwrapping an Optional value.

does someone could help with this?

CONCLUSION

Both proposed solution are working when it comes to create communication between classed but I went for the notification solution because in my case I had to update UI along with background thread progression and using protocol wasn't quite working as expected. The communication was made but updating the UI wasn't working all the time, even after using dispatch_async method to push back the changes on the main thread.

Using the notification system helped as it was easier to implement, managed and use for UI Modifications.

回答1:

FileViewController().downloadLbl.text = "downloaded"

is means "create new FileViewController, and set downloadLbl text".

You need to set text to existing FileViewController.
For telling message from URLSession completion blocks to FileViewController, you use delegate or notification pattern.

For example, notification pattern:

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
    println("session \(session) has finished the download task \(downloadTask) of URL \(location).")

    // notify download complete!
    let defaultCenter = NSNotificationCenter.defaultCenter()
    defaultCenter.postNotificationName("CompleteDownloadNotification",
    object: nil,
    userInfo: nil)
}

----------------

@IBOutlet weak var downloadLbl: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()

    // ready for receiving notification
    let defaultCenter = NSNotificationCenter.defaultCenter()
    defaultCenter.addObserver(self,
        selector: "handleCompleteDownload",
        name: "CompleteDownloadNotification",
        object: nil)
}

func handleCompleteDownload() {
    // if notification received, change label value
    downloadLbl.text = "downaloded"
}

func downloadFile(sender:UIButton!) {      
    fileDownloader().download_zip(datastring, destination: path, name: naming, fileis: self.fileId)
}

more detail: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/

Happy coding :)



回答2:

In this code:

FileViewController().downloadLbl.text = "downloaded"

You are creating a new FileViewController, not referencing an existing one. This new one doesn't have a label yet because its view hasn't been built. You reference downloadLbl, which is a UILabel!. It's nil, so your app crashes.

I have my download function inside a separate class file

This is a good practice, but it's a bad practice to have this separate class modifying UI. Instead you should write a Swift protocol that defines how these classes interact.

A simple example:

protocol MyAwesomeDownloadResponder : class {
    func downloadFinished()
}

Then you can implement this method in your view controller:

class FileViewController : UIViewController, MyAwesomeDownloadResponder {
    @IBOutlet weak var downloadLbl: UILabel!

    func downloadFinished() {
        downloadLbl.text = "Downloaded"
    }
}

Inside your download class, store a reference to responder, and then call the responder when the download finishes:

class Downloader {
    weak var responder : MyAwesomeDownloadResponder?

    init(responder : MyAwesomeDownloadResponder) {
        self.responder = responder
    }

    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
        responder?.downloadFinished()
    }
}

If you need to pass any information between these classes, define it in the protocol.