Best Practice: ListView - DetailView. Load more results - ios

I am having a basic question for like best practise.
The setup:
ListViewController:
UiTableView with ManagedObjects. The objects will be loaded from a server.
First load 20 objects. Scroll to the end of the table, the next 20 objects will be loaded.
Selecting a cell will load the DetailViewController.
I have a ListObject with an array of the items (and other properties with informations about the list, not relevant for the DetailVC)
DetailViewController:
The details of the selected object are shown. The VC also has 2 buttons to show the next object details or the previous object.
Now e.g. there are 20 objects loaded in the ListVC and I select row 10, the
DetailVC with Object at index 10 will show.
Then I click trough the next objects until object number 20.
Now when i click the 'next' Button, the next 20 objects have to be loaded from the server.
(Show object #21 but there are only 20 objects loaded).
Is there a best practice to load the next objects?
I have a class for data loading.
- (void)loadDataWithRequest:completionHandler:
When the NSUrlSession DownloadTask finishes, the completionHandler gets called and this calls the class ApiParser for json parsing and adds the result to the List Object.
So i have to call this in the DetailVC for the next 20 objects.
Now what is the best practice to do this? Or is there a better way to implement the data loading?
I could pass a ListVC reference to the DetailVC and call [listvc loadDataWithRequest:completionhandler] and load the detail from [listvc.listobject.items objectAtIndex]
Or i just could pass the ListObjectItems to the detailVC. Somehow load the new objects and
have a KVO to the ListObjectItems count.
Other methods would be delegates or NotificationCenter.
But i guess the best practice is not to put the loadData Method in the ListVC, but somewhere else.
How about to put the dataLoad method as instance method in the ListObject class and listen for the KVO in ListVC and DetailVC?
So many possibilities. But what is a good way?

When the "next" or "previous" buttons are pressed you could inform the "master table view" of the action. This would then load the next batch if necessary before presenting the detail view. Don't block the UI though, show a spinner or something in the blank detail view while waiting for the next batch to come back.
In summary: don't put any loading logic in your detail view. It should only be responsible for displaying the "details" and possibly a loading spinner if you tell it to display nil (as in, not finished loading yet).

Ideally, you would handle the data to be diplayed, in this case the ListObject, seperate from the view controller which is displaying the data, in this case the ListVC or DetailVC.
Each VC should then keep a reference to the data source, which could be a class holding the ListObject, called ListDataSource. This class should hold the methods for loading data from the server. Then, each VC can tell the ListDataSource to load 20 more objects.
This delegation of responsibilities is a pretty good example of a common programming paradigm called MVC. More on that here.

Sorry for the late response and thanks for all the replies.
I now have a ListObject method to load the next data.
And in the ListVC and the DetailVC I use KVO to listen to changes to the ListObject.
Works good so far...

Related

Is loading data in viewDidLoad too late to display it in a UIViewController?

Where do you usually load data when using a UIViewController?
When I say "loading data" I mean: API calls to fetch it and its manipulation.
Do you load it in the view controller initializer, or load it before to initialize it and pass it? Or do you add the code to viewDidLoad?
Also, what about the data for the rootViewController? The one that initialises from storyboard when you launch the app?
Do you load it in the view controller initializer, or load it before to initialize it and pass it? Or do you add the code to viewDidLoad?
Usually this done inisde viewDidLoad e.x in MVC you call the controller to load the data and refresh the contents of the vc like table/collection , regarding sending data case this known as dependency injection
Also, what about the data for the rootViewController?
rootViewController is same as any other vc
First of all let me suggest not to have data loading code in ViewController if your app is not very very simple. It would be a better practice to have it in a separate class. Check out MVP or VIPER design patterns for more info.
As for when to load your data (after view loaded or before) is more of a UX decision.
If you start loading after, of course, screen might be empty for a while, it would be good to have some loading indicator on the screen so that users do not get confused. Whether users will see empty screens often or not depends on whether you store your loaded data permanently (in a local DB), have some run time cache, or non of that. If you have some caching, empty screen will be visible just once, because if data is loaded and cached you will show that and update the screen after new data is received.
You can also start loading data before view is loaded (in loadView()) or may load some minimal data for the views which are not visible (so that they are not completely empty when opened) and then load full data after view is loaded.
Any approach you choose is highly dependent on what kind of user experience do you want to provide, also on how long it takes to load your data.
Answer in one line is viewididload but below is the actual structural method.
So for this you can have multiple answer the most popular answer for these days would be using MVC/MVVM. So let's discus about MVVM with delegate/protocol
Model - your data.
View model - here you initialize/modify/handle your data.
Viewcontroller - viewdidload - here you can initialize your view model and interact with the data that can be provided by view model with the help of delegate-protocol mechanism.

When to load data from Firebase

I'm using a cocoa pod called ACTabScrollView. In the demo code, it has a View Controller for the UI part which scrolls from side to side and has different tabs, and a view controller that has a table view (a "content view controller"). Each tab displays different information on each table view, but the information is sourced from a single array and is divided via an assigned enum. If this is confusing, there is a gif on the ACTabScrollView readme. In my "content view controller," I can hard code an array and it works flawlessly. The class for this content view controller has a variable declaration that I've never seen before as I'm new to iOS programming.
var category: TeamCategory? {
didSet {
for team in Team.teamArray {
if (team.category == category || category == .all) {
teamArray.append(team)
}
}
}
}
A project sample with this snippet is located here.
In the above snippet, Team is a model of teams, and the class has a hard coded array of teams (which are structs).
Obviously, I don't want to hard code the array of Teams in the app because I want to be able to update the array and have it update in real time. I'm using Firebase and I've tried loading the array in the App Delegate, in the UI view controller, and in the content view controller, but the tableView does not populate. I've confirmed that the data is arriving.
When/Where should I load the data from Firebase? The content view controller needs the array loaded before the view controller is loaded. Any tips for this would be much appreciated.
When you should load the data from Firebase is a design decision. You said that the content view controller needs to have the array loaded before the view controller is loaded, so you should request that data from Firebase at some point in your app flow where you can be guaranteed that the request will have come back with data before your TableView is attempting to load that data.
You may be running into a race condition where you are requesting the data, and then going to the content view controller before the request finished.
In general you don't want to have to rely on a guarantee that a request will finish in time, so I recommend doing what #rmaddy suggested, where you load the data before hand, and then have some sort of completion handler / notification / delegate call that will call reloadData on your tableView once the array from which you are pulling your data is updated.
The project sample you are referring to does not handle an asynchronous case, as you are handling. The didSet in the property declaration is just a closure that gets executed when you set a value for that property. In the case of the project sample, the news category is set at the time of the view controller instantiation, and then the didSet executes once and attaches the appropriate news array for the tableView to access. Again, in your case, as long as you are updating your array that contains your tableview's source of data after the data returns from Firebase, calling reloadData on your tableView should achieve the result you are looking for. If it doesn't, then that means you aren't properly updating that teamArray.

Get the topVC or Visible VC and check if a specific VC is in the stack

A user adds an item to their bag. As soon as the item is added to the bag, we make a network call to make sure that item is still available.The network call takes a few second to complete in the background.
When an item is added to the cart, it is stored in a singleton that can be accessed from anywhere in the app. Like this:
static let shared = Cart()
var products = [Product]()
When the network call returns and the product is unavailable, we remove it from the singleton. This causes an issue if the cart VC was opened during the network call because the table view needs to be reloaded. For that reason, we need to check if the VC that is visible is the cart VC and reload the table view. I would also like to check if the CartVC is in memory and reload the table view. Because if the cart VC is in memory and below another VC then it will also have bad data and when the user closes the VC on top, they will see bad data. How would I do that?
Before this is marked as a duplicate, I did check other posts and none of them work very well. There is also a lot of methods to do this and I would like to know which is best in swift 3.
You can use notifications to do it. Imagine the case where the item is not available anymore and the VC is open:
1 - Subscribe to a notification like "ItemNotAvailableNotification".
2 - The network call returned and the item is not available anymore.
3 - Post a notification "ItemNotAvailableNotification".
4 - In your VC handle the notification.
Also this approach allows you to handle the "bad data" in your "CartVC" and your "PreviousVC" where the user were lead to believe that the item was available.
When the cart is down in the stack make use of viewWillAppear and reload the table then. That way you don't reload the table more times than is needed. (If the cart is in the stack and four different network calls come back you only reload the table right before it appears rather than four times when it is hidden.) Table views are often reloaded in viewWillAppear specifically to deal with potentially stale data.
For the case when the cart is on screen your singleton can send a notification whenever a product comes back as unavailable and the cart can register for that notification in viewWillAppear and deregister in viewWillDisappear (or viewDidDisappear). The notification could either trigger a complete reload of the data or you could include which product isn't available and let the user know what happened (rather than something suddenly disappearing from the cart with no explanation).
This way the singleton doesn't need to know anything about the cart view controller allowing it to be more reusable.

What's the best way to load data for a ViewController before navigating to it?

In my application there are two view controllers that navigate to a DetailsViewController.
Right now, when the DetailsViewController appears, I fetch data from the server and display it on the UI. I dislike this because the UI is blank while the network request is going on. What I want is that the data be loaded in the previous view controllers and then passed to DetailsViewController.
Now the problem is that I have the exact same "load-data-and-then-push" code in two view controllers and I'm not sure what the most sensible way is to remove the repetition.
One idea is to have the two view controllers inherit from a common superclass which contains the loading/pushing method. I don't like this strategy because, supposing I have more ViewControllers like DetailsViewController down the line, I wouldn't like to write a loading superclass for each one.
Another idea would be to define a static method in the DetailsViewController which the two view controllers can invoke but this method contains UI related code (specifically, code to show an HUD Progressbar and a UIAlertView in case network fetch fails) which makes me uncomfortable.
I am very new to iOS and Objective-C so I might be missing something simple and obvious.
My favorite would be in this case to create a new class which handles the loading of the data (like http-request, etc.) and to create a delegate protocol for this class. This delegate callback might then be implemented in your two viewControllers which would then perform the push segue to your DetailsViewController when called. Delegation is a very nice and powerful feature, check out the documentation here: Delegation
Well, I'd better write it in the comments, but I have no reputation for that.
Imagine you are reading a json with several students information (name, year, etc.)
you can create a student object with the property that will be read and an object that will have a method that will run in the background that will be responsible for accessing your WS (JSON or whatever it is) and record this information to the student object. So if you have 10 students you will have an NSArray containing 10 students. This array is what you will for your next viewcontroller.
It is a lot of code, but you think examples easily.
If you use Storyboards you can use prepareForSegue: sender: to pass your data/model class to your DetailsViewController. If you use xib's you can do the same after instantiating the DetailsViewController and before pushing it.
If you need to load subsequent data from your server you should write a class that does this network stuff for you.
If DetailsViewController needs to load some additional data, you can use something like a loading view like Andy suggested. This is a widely used method.

iOS viewController and detailViewController flow/design

Where/how/when ought I initialize my second parser?
What is the best configuration of views/viewControllers for what I describe?
Here's the deal:
I have a tableViewController. I have it populated with data from a parsing class that initiates in the appDelegate method, applicationDidFinishLaunching... This class runs through an XML file I created.
Each item populating the tableViewController are associated with a URL, which will be parsed as a row is selected inside the tableViewController's didSelectRowAtIndexPath method (is this the right thing to do?).
The results of the second parsing are to populate the detail view with titles of the items from the selected url of the second parsing. I would not be writing this question if it were that simple. I need to be able to pick an item populating the detailview and drill to another detailview showing the details of that item.
Im using nibs
This is not a discussion question:
Where/how/when ought I initialize my second parser?
What is the best configuration of views/viewControllers for what I describe?
Is there anything glaring I must know?
There are two options
1.)parse data as soon as tableviewcell is selected then once finished load detail view with results. (con can slow interface down)
2.) load detail view and then parse data and populate details as data becomes available (con loads details view with no data and makes user wait)
So both options make the user wait but only one slows the interface down...
I think option 2 is the choice everyone will recommend.

Resources