I am designing an app which manages a tank of hot water. The app makes RESTful API calls to a service to;
Obtain a profile of temperature at various layers in the tank.
Allow a user to work with a timer (like an immersion timer) custom control to set times to turn on/off heating elements.
So, as a first cut, I have two tabs in a nav controller;
1. A graphic, showing a picture of the tank, graduated to show temperature.
2. A custom control, like commercial domestic timers, with two concentric rings, allowing the user to point, and drag 30 minute slots to set times for heating elements.
I have both custom views working well. I'm afraid to say I'm stuck on a very simple point - even after reading all of Apple's ios docs, and would love some help.
The data server (A BeagleBone running embedded Linux) implements 2 sets of RESTful APIs, one dealing with tank temps, and the other set to read and to update timers.
Is it best to start the App with a view controller which instantiates a model, who issues the APIs, and displays "Loading...", then populate a single application wide data model.
Have each view controller (The image of the tank, and the timer controller) to populate their own (separate) model?
And the big question for me (despite years of working in Smalltalk, C++, Java...) what is the recommended way to ;
instantiate a view controller
load / display a view with a "busy"spinner if the model hasn't loaded
My app isn't complicated enough for GCD, or indeed KVO. This is basically, a "show a view, call a web service to read the data", "modify the data", "call a web service to replace the data"
Basically, I believe my question is, when a view comes on screen, what is best practice to determine that the controller has a model, or has a model which is still loading data?
Sorry for the long winded question.
You asked a very good question, about a very common task in an mobile app. My suggestions are
Since your two tabs really have no common data, the problem simply boils down to "how to initialize a view controller with remote data".
If user can't use your app without remote data,
simply show a loading view in [vc viewDidLoad], and start fetching data from server asynchronously; your vc should implement NSURLConnectionDelegate to listen to call back
when data is successfully fetched from server, dismiss the loading view, and render the data to user
if data fetch failed (due to bad network or server downtime), show alert view to user, and retry the data fetching; if fetching fails for several times, tell user to try again later
you can make the loading view more beautiful, to provide a better user experience; note the loading view should cover all the buttons/controls, so user can't mess up your app state during data fetching
If user can use your app without remote data, it's another story. You shouldn't use a loading view in that case, and should silently fetch data in background. Since this does not seem to be your case, I will not complicate the answer by this case.
Related
I have a project where I have 4 tab bars, and when i switched tabs, my api to get the API request to update my view is in the method viewDidAppear().
However this will make a very bad UX experience for user as whenever they switch tab, a loading icon and some times it load for 0.5 seconds but that instant appearance and disappearance of the loading icon is really bad for UX perspective. How can i implement a view where whenever the database value change, my view is automatically updating itself?
I have thought of using Timer() to implement where it calls the api every second but this is not practical as it will be constantly calling the API every second.
Does anyone have any suggestion? Thank you
There's a lot to unpack here, but I'll try to provide a generalized answer and point to several possible solutions to the UX problem.
There's several aspects that can play a role here:
Do you control the backend the data comes from, i.e. is it possible to follow paiv's comment and implement push notification? Or perhaps use some other means than pure HTTP(S) requests, like a websocket?
How frequently does data change? Is it realistic that it does so in between switching from one tab to another?
On that note, what do you do when data changes while your user is on one tab? Does that get refreshed in any way? Is it a problem if they don't get informed about this immediately? Is there a refresh button?
Do you persist the data beyond a view's on-screen time at all? Is it scrapped when a user re-visits a tab, i.e. does a tab always start "empty" until the API response is handled or do users see the values from the last time they visited that tab?
What best to do depends a lot on the exact answers to these questions. If you cannot enable the backend to do anything besides serving HTTP requests AND have to keep all data up-to-date constantly then obviously you have to do something like the polling you described. The update-interval depends on how important this is for your users. Even if it is 1 second, be aware that this means they could visit a tab before the next update has been fetched.
If you can adapt the backend and up-to-date data is a must, then push notifications or perhaps websockets are a way to go (keep in mind that websockets mean one open-connection per active user then!).
Push notifications would probably break down when the update interval is too high (i.e. if data gets changed in the backend very quickly and you keep spamming them), but would work nicely otherwise, plus they would update views that are already on screen if used correctly.
In 90 % of the cases I've seen, however, going with regular HTTP requests and a properly designed UI is the best choice. Properly decoupling the UI code from the data loading code and the code that displays the data is also very important. Here's how I would handle this:
Properly persist the data in some way in the app that does not require on views owning the data alone. If you want to persist beyond app runtime that means CoreData (or an equivalent), otherwise an in memory storage may be fine (that is kind of a global state then).
In your UI, show any data you currently have, even if it is "outdated" (in your case: "left over from the last time the user was on this tab"). Design the UI so that this is clearly apparent (grey it out, put tiny activity indicators next to the data while it is being loaded anew/updated, or something similar).
As said in 2. do show if the app is loading data. Initiating the loading when the users go to a tab is okay, but don't make a UI that is completely dominated by it (like one giant, empty view with a spinner on it).
Functionally, the loading should not directly update any views, instead it writes new data to your storage and just informs views and view controllers that they need to update themselves (if you're using CoreData and/or Combine you get a lot of this logic basically for free, especially for a SwiftUI app).
Such a design also lends itself to later being adapted for push notifications: While at first the re-loading is triggered by a tab switch (or a "refresh" button, for example), once (silent) PNs are available, they trigger the reloading. Even websockets can be integrated into this more or less, though there's probably a bit more to do that.
In any way, as said it all depends on your use-case and also available resources, but the gist of it is probably "If the current loading icon you have destroys the UX, redesign it properly". My gut feeling here would be that anything else might be over-engineering for your needs...
I have 4 viewControllers that all have different WKWebViews. When the user starts up the app and sees the first viewController, I want the WKWebViews of the other viewControllers to load in the background so that the app will seem more seamless when switching from viewController to viewController.
Is this possible? Or would I have to put all the webViews in one viewController?
Nearly all of the delay that you're trying to avoid is due to downloading the data; instantiating a web view once the data is available is fast enough that users won't notice any delay. Given that, what you should really be looking to do is to download the data, not instantiate more web views than you need. The question that BJHStudios suggested in a comment provides a couple options for this, but you should look at NSURLSession rather than NSURLConnection; NSURLSession is very easy to use and offers a lot of flexibility. For example, you can configure a session to download data when the app is inactive.
If your app makes use of a data model, you could incorporate the web resources required by each of the four view controllers into the model. When your app starts up, it would naturally instantiate the model, and the model would then retrieve the necessary resources so that they'd be immediately available for display when needed.
I'm not sure this question is strictly limited to iOS, as it's more of a general application-design question on data integrity. But at the moment, I'm getting into iOS and have hit a "best practice" wall.
If we use the Twitter app as an example. We have one tab for our timeline and one tab for our profile. So (to keep things very simple) each one is a View Controller, backed by an object (model). I tweet on the timeline VC, then I head on over to my profile tab. Both viewDidLoad methods have already ran, so the data that was loaded to draw the UI is currently stale. The "count" of my tweets is now out of date. In the iOS world, what is the best methods/approaches to keeping the VC model data synced with the backend?
Is it on a time interval? Or network requests in the viewWillAppear method? Is it event driven, ie. when I tweet in one VC, and it's been saved in the backend web service, I notify any VCs that care that there's a new tweet
I'm not 100% sure this question will have an "answer" in the SO sense, but I'm just trying to understand what's the done thing in the iOS world (as someone who comes from the web development world).
Here's a loose sketch of some things our team has done on a networking application that might give you insight for your own architecture.
At the level of the VC (in viewWillAppear) we typically have multiple NSNotificationCenter observers that will call whatever update methods you need to run after a network update call.
Firing off these notifications is some network listener that lives on a background thread. It's job is to wait for responses from the backend server (typically JSON blobs) that contain updates to the data model. I believe ours has a time interval that will periodically phone home and check to see if we have new things to update.
Note you will necessarily have to devise an asynchronous solution for network calls as the passing/receiving of data packs can be unreliable, and take time, and thus should be computed in the background. You'll also need a way to handle data loss and other errors between server and device. And of course in order to see UI updates, you'll need to switch back to the main thread when updating the VC.
Assuming a some change of state (i.e. new message), then it makes the call to get the new data, which then fires off an NSNotification (with a new payload of info), that goes to the observer on the VC.
As you mentioned with viewWillAppear, UIViewControllers are a core building block of iOS apps. They have a lifecycle to follow to help with things like this scenario.
You have many options, but the best practices would to make calls on the viewWillAppear method and then reload your table views, collection views, etc.
Another thing you can add is push notifications on data change. You can now send a push notification with the "content-available" option that is silent and sent to your app when data changes on the backend. Then your app can refresh data only when needed.
I would stay away from timers. They will keep making network request and keep the radio from going into a battery saving idle mode.
I have a model which can download data from a server and thus an activity indicator needs to be displayed (both in the status bar and on screen).
But I also have a UIWebView which is displaying content, the content has links, some are local and some are remote, if a remote link needs to be downloaded then an activity indicator needs to be displayed again.
What are design options for accomplishing this?
The app delegate could have methods to start and stop the activity indicators as directed by the models and controllers. But somehow this doesn't seem quite clean to me - its starting to use the app delegate just as a bucket for dumping miscellaneous functionality.
I'm thinking the model should deal with its own activity indication and the controllers should deal with their own, i.e. separate them. However if I did that wouldn't we then have the situation where a model is doing some UI related stuff (even if minimal)?
Is there a clean recommended solution?
IMO, Activity indicator is a View component independent of a specific view therefore it should be managed by your application delegate.
The way I tackled it is I created an ActivityManager class and I create and instance in my application delegate. It takes an instance of UIWindow and from that, I can determine how to center and display the indicator appropriately. All other areas of my application interact with this class through the observer pattern. They post notifications when the network state has changed and my activity manager evaluates the overall state of the application to see if it needs to either show or hide the indicator. Here is a sample of what you might see in my app:
ActivityManager *activityManager = [[ActivityManager alloc] initWithWindow:self.window];
self.activityManager = activityManager;
[activityManager release];
[self.activityManager addNotification:kNetworkStatusDidChangeNotification];
[self.activityManager addAsyncActivity:[HttpManager defaultManager]];
[self.activityManager startObserving];
I've added the ability to add new notifications so that way, when the manager observes these notification is can evaluate what needs to happen. AsyncActivity is just a protocol for singletons I can check to see if they are processing data. Then I just tell my manager to start observing. When I close my app I simply call:
[self.activityManager stopObserving];
to remove all observers and free up any memory. That should get you started in the right direction, I'm sure there are other ways to do it but that seems like the least invasive way to handle it to me. It's also very portable.
It sounds like there could be multiple network events occurring simultaneously that all need to report activity accurately. Perhaps a separate class that is only responsible for tracking network operations and communicating with your instance of UIApplication is in order.
Imagine an object that uses a simple counter to keep track of the number of network operations. Adding an event increments the counter and removing one decrements the counter. While the counter is > 0, the spinner is visible. This may be a reasonable approach to encapsulating communication between the application and the model layer.
I'm building a fairly complex business application on the iPad IOS 4.2: 4 tabs, with potentially deep navigational paths on each tab.
In the opinion of some of your more experienced IOS developers, what would the general expectation of a user be with respect to saving application state between launches (i.e. after an app's been fully terminated and subsequently restarted)? I'm using Core Data and have all the data issues covered, but I'm concerned about the application's navigational tree. If the user had left the 1st tab on screen 3, the 2nd tab on screen 4, the third on screen 2, where he left the entry of a new record half-complete, and was, at the time of the app entering the background, working on the 4th tab on screen 3...do you think the average user would expect the application to remember all that the next time it launched? (My gut says yes, though I'm unsure for how long.)
If the answer is yes, can you suggest a general strategy for handling this (and, again, I'm talking about the navigational tree here, not Core Data stuff)? For example, if navigational controllers were used as the root view controller for each tab, it would be simple enough to record enough info about their navigational stacks to be able to restore them later. But what about things like popovers, alert/action sheets, or modal VCs created on the fly? Should each view controller record the state of its UI objects and, if so, what is the recommended way to do this?
I know a lot of this is up to the user, but I'm asking for the general perspective on these issues, i.e. the voice of experience.
It's pretty simple in principle, but it can get quite complex in practice, to go through your navigation hierarchy and storing stuff that can't be derived from the data model.
There's an open source implementation of this called DTResurectionKit. I also documented how I do it in my apps on my website. It's similar to (but simpler than) DTResurectionKit.
In the opinion of some of your more experienced IOS developers, what would the general expectation of a user be with respect to saving application state between launches?
The best way to think about this is to make sure the user never has to figure out why or how they got to where they are when the app first opens.
This depends completely on the type of app you have and the length of time since the last open. It sounds like you have a fairly complex drill-down app so I think it is definitely best to remember the navigation stack, within a pre-determined time frame. I use the three20 framework which does this automatically for me, but if you were to implement it, it would be something like this:
If the user opens up in the past 24 hours, open to the exact spot the left off
If the user opens within a week, open to the main "section" or area of the app they were in
If the user opens after a week has past, open to the root screen.
Now of course, these values will differ based on your apps function and use cases, but you get the idea. By you making some broad assumptions about how people are using your app and when, you can increase the user experience by not shoving them so deep in your app when they won't remember how they got there.
As for implementation, it is all just data.. You dont need to serialize live objects to store the stack, just implement the data needed to recreate the state on the next launch. What you need to store is highly dependent on your own setup... mileage will vary. I use NSUserDefaults to store all instance vars and the navigational stack through Three20. Check out TTNavigator for a great implementation.
I would suggest keeping the state of each tab view. Only at the "page" level. Don't worry about popovers or incomplete data entry (hopefully there's not too much interim state before you're saving it to your core data store.)
Like you said, it's easy enough to remember what tab you're on, and what controller you're navigated to in each tab. More isn't necessary.
It sounds like you've got it under control, but for the benefit of others: 1) when you change tabs, save "active tab", 2) when you navigate within a tab, save "active controller in tab", 3) when you launch the app, set the "active tab", 4) when you change tabs, set/confirm the "active controller in tab".
The reason for 4) is that the view/controllers for the tabs will be delayed in their loading, or perhaps never loaded. You don't want to set the "active controller in tab" for a tab that is not visible and may never be loaded into the app, it would just cause unnecessary loading. It will often happen (after the app has been loaded) that you don't need to change it because it's already in the correct state.
I think your time is better spent elsewhere. Of course, your app might be perfectly suited for this, but in our case data was partly online, could have gone stale, influenced view state in different navigation views in different tabs simultaneously, etc. etc. It's not an insurmountable challenge, but definitely hard and a huge time-sink.
We decided to spend our time on fixing the bugs and improving functionality. If you have to make the same kind of choice, I'm pretty sure which option your users would prefer. Particularly now that your app will survive a phone call in the background.