Search code examples
iosswiftuiviewcontrollertabbarcontroller

Call method in another View Controller, but not segue to it?


I have a tabbed Application.

tab A : Download Button.

tab B : TableView

What I want to do is when click the button in A, I want the download function in B be called.

Now I have several scenarios after click the button:

  • stay in A
  • if I click B now, I can see the download process, or if finished, I can see the file there.

    +----------------+         +-----------------+
    |                |         |downloaded files |
    |                |         +-----------------+
    | +------------+ |         |                 |
    | | download   | |         +-----------------+
    | +------------+ |         |                 |
    |                |         |                 |
    +-------+--------+         +--------+--------+
    |now in |   B    |         |  A     | now in |
    |  A    |        |         |        |    B   |
    +-------+--------+         +--------+--------+
    

Now I write the download function in A, and save all the files in a directory, whenever I click the tab B, the viewWillAppear function will load the downloaded files from the directory, but the problem is I want to show the download process in case the file is large.

Any suggestion and code snippet are appreciated.


Solution

  • Using Swift 3

    Before I provide a solution I'll point you to @Paulw11's comment, he's 100% correct. The proper convention is to pull your connectivity logic out of your view controllers. The only code you should have in your view controllers is the code necessary to display your view. Connecting to a server and managing the result has nothing to do with actually presenting the resulting data in the view.

    If you really felt compelled to call a function directly from another view, you shouldn't do it by creating a new instance of your view controller. Rather, you should just get the existing instance like this:

    let app = UIApplication.shared.delegate! as! AppDelegate
        if let viewControllers = app.window?.rootViewController?.childViewControllers {
            viewControllers.forEach { vc in
                if let view = vc as? AnotherViewController {
                    view.downloadIt()
                }
            }
        }
    

    Personally, I'd recommend pulling this logic out of your view controller either way. If you're trying to keep things minimal, or if this is the only web service call you are making, you could simply do something like this:

    Add a new Swift file with a protocol and an extension to that protocol:

    protocol Downloadable : class { }
    
    extension Downloadable {
    
        func downloadStuff() -> String {
           // Add your download logic here
           return "downloading"
        } 
    }
    

    Then, in any view you want to be able to use this download function just add the protocol to your View Controller like this:

    class ViewController: UIViewController, Downloadable {}
    

    Then you can very easily get your downloaded data like this:

    @IBAction func handleDownloadTapped(_ sender: UIButton) {
        let result = downloadStuff()
        print(result)
    }
    

    Again, you shouldn't get in the habit of doing this as it can get messy if you are working with a few different api calls.

    The best practice is to create a class that contains your web service stuff, I personally like the objc.io approach. You can find a tutorial here.

    https://talk.objc.io/episodes/S01E01-networking

    Basically you'll create a service like this

    public typealias JSONDict = [String: AnyObject]
    
    struct Resource<A> {
        let url: URL
        let parse: (NSData) -> A?
    }
    
    let url = URL(string: "https://url")!
    
    let myResource = Resource<[JSONDict]>(url: url) { data in
        let json = try? JSONSerialization.jsonObject(with: data as Data, options: [])
        return json as? [JSONDict]
    }
    
    final class Connect {
        class func load<A>(resource: Resource<A>, completion: @escaping (A?) -> Void) {
            URLSession.shared.dataTask(with: resource.url) { (data, _, _ ) in
    
                if let data = data {
                    completion(resource.parse(data as NSData))
                } else {
                    completion(nil)
                }
                }.resume()
        }
    }
    

    And you can call it wherever you need to like this:

    Connect.load(resource: myResource) { result in
        // do stuff with result
    }
    

    I hope this was helpful to someone