Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

iOS

Swift Weather App reload data

Does anyone know how to make the data reload if you hit home and then open the app again?

I'm assuming something in applicationWillEnterForeground but if I put the getCurrentWeatherData() in there it crashes and the text labels are nil?

Thanks.

Keith

10 Answers

You know how these problems can be, I just can't sleep with something fun like this to solve. What an sneaky little monster this problem seemed to be, luckily there's an easy solution:

The trick is to store a pointer to the ViewController you're using in a variable (in the the AppDelegate class) before the app fully closes (after hitting the Home button). Using an Obj-C example, I coded this up, so I may be butchering the cast, but it seems to work great:

STEP 1: In AppDelegate class

Create an optional variable to hold your current view controller. Make it optional, because it will be 'nil' until the Home button is pressed.

class AppDelegate: UIResponder, UIApplicationDelegate {

    // Make sure you use 'var', not 'let', here so it can be 
    // assigned later when the app goes into the background.
    var myViewController:ViewController?
    ...

STEP 2: Still in AppDelegate class, setup refresh

(Optional: I'd recommend putting a println("Goodbye world") call in your applicationDidEnterBackground() call so you know the app is going back and forth smoothly.

In the applicationWillEnterForeground() method

func applicationWillEnterForeground(application: UIApplication) {
     println("Hello again world")

     myViewController?.getCurrentWeatherData()
}

STEP 3: In ViewController Class

Add the following to the viewDidLoad() method call. This stores a pointer (meaning the memory location) to the view you'll later want to refresh, upon returning to the app.

override func viewDidLoad() {
    super.viewDidLoad()

    let appDelegate:AppDelegate = UIApplication.sharedApplication().delegate! as AppDelegate
    appDelegate.myViewController = self
}

For reference, here's the StackOverflow article on saving the UIView to the AppDelegate class: http://stackoverflow.com/questions/5082738/ios-calling-app-delegate-method-from-viewcontroller

For educational value, set a breakpoint when you call myViewController?.getCurrentWeatherData(), and run your program. Now in the Xcode's Debugging window open "Self > myViewController" and look at all the nice little labels nested in there. All of them have their proper memory locations, thus they're properly initialized and can be modified. (Note: they were initialized when the app first opened, not ever reinitialized.)

It's late, so I could've easily overlooked something. Please let me know if you see any problems with what I suggested.

Take care Keith!

-- Kyle


EDIT: I had the wrong method call in STEP 2, but I updated that.

Hi Kyle: Maybe you can help me with what I am trying to do. I am creating a timer app, where you set a timer, starts counting down until 0. Easy app. The only problem I am having is that when the app goes into the background, the timer stops until I reopen the app. Meaning that it will resume where it left off. So what I want to do, which I think the approach is something what you showed here. So I want to retrieve the value from my counter which is a variable, save it somewhere in the delegate along with the “time” that the app went to the background, and when the app comes back to the foreground, retrieve that information, calculate the time the app has been in the background, subtract it from the counter, and resume.

Is that even possible ?

Jhoan,

Your question is a good one.

There are several good approaches that you could take with this. I'm not sure how you're approaching it now, so I'll just give general suggestions:

While I'm not sure the details of your implementation, I'd probably recommend storing the start time as an NSDate(), then instead of incrementing/decrementing from an Integer/Float/Double, you can just calculate the time since the initial date, then remove that amount of time from your totalTime. That way when the app reopens, the time calculation is always correct. The calculation for determining how long ago an NSDate() is so light that it'll be nearly instantaneous.

import UIKit

class Timer {
    private var startTime: NSDate? // Recorded when the user starts the timer.
    private var totalTime: Double = 0.0 // If you're counting down, this is the total time designated by the user (e.g. 20 mins)
    private var currentTime: Double = 0.0 // Displayed time visible to user.

    private func startTimer() {
        startTime = NSDate()
    }

    private func updateTimer() {
        let timeDifference = startTime!.timeIntervalSinceNow // See http://stackoverflow.com/questions/4084341/how-to-calculate-time-in-hours-between-two-dates-in-ios

        if timeDifference >= totalTime {
            currentTime = 0.0
        }
        else {
            currentTime = totalTime - timeDifference
        }
    }
}

What you're trying to do is definitely possible. If you're interested in storing information between sessions, then using a database system like http://realm.io would work well.

Please let me know if you need clarification or additional help.

Good luck, Kyle

I see your approach, I like it. Thanks but there are some questions I do have, please bare with me since I’m a newbie at this. This will be my very first app. Perhaps I can give you my e-mail and can explain what I am doing, and maybe you can guide me better ?

jhoannarango@msn.com

Jhoan,

I'm happy to help, I often feel this way about new projects. Researching best practices and methodologies can be intimidating.

While emailing would work just fine, I'd like to recommend you open another question here on the forum. After that, come back here and paste the link to the question by adding a comment (it emails me when you do that, so I'll be notified). That way everyone will benefit from seeing our thought process. What do you think?

Kyle

Great Idea Kyle, will do that... Thanks

Hey Keith,

I ran into the question as well when developing the Weather App. The easy solution for me was to add a refresh button that then re-pulls the data. However, I looked on Stack Overflow and found this:

Stack Overflow

In the first comment (by "sudo rm -rf") under the question, he points out that applicationWillEnterForeground is the best approach for this. I can't confirm this as I haven't implemented it, but here are some Apple Docs that talk about that particular method:

Apple Docs

Hopefully this gets you pointed in the right direction. Good luck!

Kyle

Hey Kyle, thanks for responding. I was trying to use that applicationWillEnterForeground function but I can't call getCurrentWeatherData() inside that without getting errors. I'd like to know how to do it that way instead of the refresh for future apps. Maybe someone else knows...

Keith

Sorry, admittedly I skimmed over that part of your post. Obviously that's an important part, my bad.

Could you post the particular error you're getting? Also, after overriding the method, try printing something to the console to see if the method is doing what it should. That'd be a good place to start.

I'll think on it and see if I can come up with a good example for you in the next day or two.

Either way, best of luck!

No worries, it's nice you're even looking

func applicationWillEnterForeground(application: UIApplication) {

        let refreshdata = ViewController()
        refreshdata.getCurrentWeatherData()

    }

Then once I reopen the app it errors in the dispatch function starting with this line self.temperatureLabel.text = "(currentWeather.temperature)" - breakpoint saying EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP...

And console error: fatal error: unexpectedly found nil while unwrapping an Optional value

I've been playing around with my version of the app, and that's exactly the same problem I'm having. If you're familiar with the debugger in Xcode, and look at the labels, all mine are nil upon reopening the app. This will be the problem that you and I are having. If the label is nil, for whatever reason, its properties won't be available to be updated.

It's a wierd situation to me though, because they're obviously showing back up on the screen as if they were there and already have values. Perhaps the question we really should be asking is why are UI elements (e.g. labels) nil upon re-opening the app.

I have to head to bed now, but I'm going to be looking that up tomorrow. That's something I honestly don't know yet, but really should.

If you find anything on that, please let me know, and I'll do the same.

Take care, -- Kyle

Haha yeah I know how that goes, good job on figuring that out. I'll test it on mine today in a bit. I also found this late last night, it worked good too. I wonder what the preferred way is?

http://stackoverflow.com/questions/25716012/triggering-a-specific-action-when-the-app-enters-foreground-from-a-local-notifi

Your version, seemed to work good for me as well.

Thanks for taking the time.

Yea, no problem!

Best of luck, Kyle

Really good thread... Thanks guys!

For Step 1, I set my variable as "UIViewController" (was getting an error with just using "ViewController").

When I used "ViewController", I got the error -- Use of undeclared type 'ViewController' ???

Thanks Jonathon!

The reason we use View Controller in Step 1 is because the name of the file related to the view we're storing in the variable is actually called ViewController.swift. If your app's view (like the view on the Storyboard) is connected to the file called ThisControlsMyView.swift then you can replace the line in Step 1 with:

var myViewController: ThisControlsMyView?

...instead.

The principle at work here is based on the view controller, which only means the file that is connected to the view in your storyboard. When you set up an application using a Single View app, Xcode defaults to a Storyboard with a single view that is connected to a file named ViewController.swift.

Hopefully this all makes sense. Feel free to ask additional questions for clarification.

Good luck -- Kyle

Boom! Thanks Kyle... Had flipped those two around.

Appreciate the clarification :)

PS, Would the same methods be used for functions or such that you want to trigger when the app goes to the background for example?

Yep, you could definitely do that! Here's a great Stack Overflow page highlighting the differences, scroll down to the first answer, by "Dano":

http://stackoverflow.com/questions/3712979/applicationwillenterforeground-vs-applicationdidbecomeactive-applicationwillre

Let me know if you have any further questions.

Good luck -- Kyle

PS, Would the same methods be used for functions or such that you want to trigger when the app goes to the background for example?