Where is the correct place to populate UITableView? - ios

I have a UITableView in my ViewController. To populate it, I have to make an async request that may take up to a second to complete. Where should I put it?
When I tried to make ViewDidLoad async and make a call from there, ViewWillLayoutSubviews throws an error, because it tries to position some elements that weren't assigned yet - due to the fact that not all code from ViewDidLoad was executed yet (I think).

Before awaiting anything in ViewDidLoad you need to setup all your view logic. Otherwise your view initialization will not be finished when ViewDidLoad method returns. That could be a potential cause for ViewWillLayoutSubviews to fail. If it still fails, use a try/catch to make sure your service is working:
public override async void ViewDidLoad()
{
base.ViewDidLoad();
// setup all the view elements here (before the async call)
try
{
var results = await MakeYourAsyncRequest();
InvokeOnMainThread(() =>
{
_tableView.Source = ...; // do something with the results
_tableView.ReloadData();
});
}
catch(Exception ex)
{
// do something with the exception
}
}

Try putting the tableView.ReloadData(); method inside
dispatch_async(dispatch_get_main_queue(), ^{} this might solve your issue.
-:As a general rule, you should try to make sure that all of your UI interaction happens on the main thread. And your data fetching task will work in background. It looked like you were calling reload Data from your
background thread, which seems risky.

Depending on the data I would put the call in the AppDelegate. When the app launches the data should be fetched and saved.
When your UITableview appears it will already have the data ready or maybe an error message since you already know the result of the fetch.
The data may change thats why I would also put the fetch call in viewWillAppear() of your ViewController with the UITableview.
ViewDidLoad() is a method that gets called only once. Also it is called as the first method of the ViewController lifecycle.
It would be good if you read a bit about it VC lifecycle.
You can help yourself by trying it in code with printf("method name").

Related

How can I recall awakeFromNib

I want to recall awakeFromNib when the tableView reload it's cells, to re-execuse the code I am executing there and newly load a pageViewController who is inside the cell, is that possible?
I believe awakeFromNib is called on an object when it is initialized from a nib file. So you can not call it manually.
Read this thread for object loading process from nib file.
See the documentation from apple about awakeFromNIb method
After all objects have been instantiated and initialized, the
nib-loading code reestablishes the outlet and action connections for
all of those objects. It then calls the awakeFromNib method of the
objects.
So, calling it manually looks not possible. Moreover I suggest you to put the lines of code in awakeFromNib into a seperate method and call that method. It will work for you.
As the other respondents point out, you should not call any of the "lifecycle" methods yourself. However, there is no reason on earth why you can't do something like:
override func awakeFromNib() {
super.awakeFromNib()
self.doSetupStuffINeedToRepeat()
}
func doSetupStuffINeedToRepeat() {
//...
}
Then you can simply call doSetupStuffINeedToRepeat() anytime you want. The reason that you shouldn't call awakeFromNib yourself is that, if done properly, it should call super.awakeFromNib, and doing this in the middle of execution may (will) really mess things up...

Best place to make network calls

Network Call :-
static func getProfile(parameters:[String:AnyObject], onComplete:[String:AnyObject]->()) {
var requiredData:[String:AnyObject] = [:]
Alamofire.request(.GET,API.getProfile,parameters: parameters).validate().responseJSON { (response) in
if let responseData = response.result.value {
if let jsonData = responseData as? [String:AnyObject] {
requiredData["UserName"] = jsonData["UName"]
requiredData["UserEmail"] = jsonData["UEmail"]
requiredData["UserMobileNo"] = jsonData["UPhone"]
requiredData["UserAddress"] = jsonData["UAddress"]
requiredData["UserCity"] = jsonData["UCity"]
}// Inner If
} // Main if
onComplete(requiredData)
}// Alamofire Closed
}// Func closed
Network Call within required VC :-
override func viewDidLoad() {
super.viewDidLoad()
let parameters:[String:AnyObject] = [
"WebKey": API.WebKey.value.rawValue,
"UId":NSUserDefaults.standardUserDefaults().integerForKey("UserId")
]
NetworkInterface.getProfile(parameters) { (responseDictionary) in
//print("Passed Data \(responseDictionary["UserName"])")
self.userData = responseDictionary
self.updateUI()
}
}
As far as i know, VC Lifecycle is somewhat as follows :-
init(coder aDecoder:NSCoder) -> viewDidLoad -> viewWillAppear -> viewWillDisappear
However, Even after view appears it takes few seconds for user Information to be displayed in those textfields. I thought viewDidLoad is the best place to make network calls.
I understand that network calls are async so it will take time to fetch required data from network and respond. However, network call was made in viewDidLoad so by the time view will appear, it should already have required data ? Should it not ?
So can anyone explain me which is the best place to make network calls and why? I want textfields to be updated with user Info as soon as view Appears.
Requests need to be fired in the viewWillAppear:, only this method notifies you that the screen is about to be shown. If you don't want to send requests every time the screen is shown, consider caching the data once you have it.
viewDidLoad is not the best candidate. It has nothing to do with the appearance of the screen. It's called right after a view controller's view is requested for the first time, not when the screen is showing up.
For example, if the screen was destroyed (by popping from a navigation controller), you'll receive viewDidLoad when you show it again (by pushing the screen to the navigation controller). Or if the app receives a memory warning, a current view is unloaded and loaded again, which ends up sending the view controller viewDidLoad.
viewDidLoad is tricky.
If you think that viewDidLoad will save you from fetching the data from the server multiple times: sometimes it will, sometimes it won't. Anyway, it's not the right tool to optimize networking, caching is!
Since remote requests are expensive (they take time and traffic), you want to understand when are they sent. viewWillAppear: gives you understanding. And in conjunction with caching you can make it optimal.
UPDATE
In most cases, it's not a good idea to send requests from the view controller directly. I would suggest creating a separate networking layer.
I think viewDidLoad is the correct place to make the network call if it fits that screen's need. i.e. you don't have to re-request the data at some point. For example if profile data has changed since the view was loaded.
As for network requests taking time, it's possible that your view appears before the network request is done. I suggest adding some loading indicator that you hide after the request completed.
Also, keep in mind that network requests can fail so you should deal with that by retrying the request or displaying an error message.

How to clean up a UIViewController in Xamarin.iOS?

Consider we have a custom UIViewContoller.
Now we have to make some clean up on UIViewController unload. For example, if we use UIWebView as a subview in our view controller, it's recommended to set its delegate to null and call StopLoading() method on unload.
Some sources say it's not recommended to make clean up overriding Dispose() method, because it concerns only managed object lifecycle.
Other sources say it's not recommended to use ViewDidDissappear() for these purposes because it can be called twice.
How to handle it right?
ViewDidDisappear() and ViewWillDisappear() will not be called multiple times; at least not without a balanced call to ViewDidAppear() and ViewWillAppear(). If you see more calls to the disappear methods than to the appear methods, you have a bug in your code. If you want to make sure, your cleanup happens only once (for sanity), but isn't the solution something as simple as this:
bool myCleanupIsDone = false;
public override void ViewDidDisappear()
{
if(myCleanupIsDone)
{
return;
}
myCleanupIsDone = true;
CleanUpWhateverNeedsToBeCleanedUp();
}
You should almost never need to override Dispose() unless you are dealing with unmanaged resources. Xamarin.iOS does that a lot internally but for your code and in your case it is not relevant.
Normally you would create your required objects in ViewWillAppear() and clean them up in the disappear methods. That way you would not need to check if something has already been cleaned up.
ViewWillDisappear and ViewDidDisappear can actually be called multiple times, on iPhone for example every time when a new controller is presented or pushed. But I think it's still the right place to do all the necessary cleanups.
Krumelur's proposal would end in a null delegate of your UIWebView once your controller has been disappeared for the first time – if the user comes back, it would probably crash.
So instead of setting a flag you could check, if the controller you want to clean up is beeing popped or dismissed – if that's the case, you can safely do all the work.
Here's some code I found in one of my projects (seems I've been through this before ;)):
public override void ViewDidDisappear(bool animated)
{
if ((NavigationController == null && IsMovingFromParentViewController) ||
(ParentViewController != null && ParentViewController.IsBeingDismissed))
{
CleanUpAfterDisappearing();
}
base.ViewDidDisappear(animated);
}
Maybe you can just cleanup resources when the operating system instructs your app to do so via overriding the DidReceiveMemoryWarning() method in your custom view controllers: http://iosapi.xamarin.com/index.aspx?link=M%3AMonoTouch.UIKit.UIViewController.DidReceiveMemoryWarning

Background Fetch and asynchronous UICollectionView

I'm trying to implement the iOS Background Fetch API in an app. The app downloads JSON from a server, calls -[UICollectionView reloadData], and for every cell, and image is downloaded asynchronously in -collectionView:cellForItemAtIndexPath:.
In my initial implementation, I would call the completion handler passed by the system into application:performFetchWithCompletionHandler after I called reloadData. The app snapshot in the multitasking view would then display empty cells, because the images wouldn't have been downloaded yet. To solve that, I removed the completion handler call after reloadData, wrote a little structure keeping track of which cells' images have been downloaded, and only after a certain number have been downloaded, I would call the completion handler.
I did this using a completionBlock property on the view controller that reloads the images. The app delegate sets that property, then calls a reload method on that view controller, which then calls its completion handler property. I looks like this:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
WSViewController *viewController = (WSViewController *)navController.topViewController;
viewController.completionBlock = ^(BOOL success, BOOL newData) {
if (!success) {
completionHandler(UIBackgroundFetchResultFailed);
} else if (success) {
if (newData) {
completionHandler(UIBackgroundFetchResultNewData);
} else {
completionHandler(UIBackgroundFetchResultNoData);
}
}
};
[viewController reload];
}
During testing, I found that performing a background fetch in relatively rapid succession would cause the completion handler not to be called. That's easily explained, because the completionBlock property is overwritten and the old one won't get called.
So, as advised in WWDC 2013 Session 204 "What's New With Multitasking", I removed the property and decided to pass the completion handler all the way through my code. -reload is now -reloadWithCompletionBlock: etc.
But now I'm stuck on how to implement that in the App Delegate. The View Controller has a delegate, and one of its methods, - (void)didFinishDownloadingImages, is implemented by the App Delegate. That's the point where I want to call the completion handler. But I can't, since there is no way to get to the completion handler without storing it in a property, defeating why I was doing it this way in the first place.
Any thoughts on how to solve this?
use NSOperationQueue and move your blocks of code into a subclass of NSOperation -- this will help you handle situations like blocking, discarding multiple requests and otherwise handling all those types of situations.
So, rather than write inline blocks, I'd move to an operation queue which seems like some lifting, but actually makes this much easier to handle properly.

MonoTouch event handler in ViewDidLoad not working sometimes?

There are several ways to handle event in MonoTouch. It looks to me that mapping the event in IB is the most reliable way to do. What I don't understand is why sometimes the event mapped in ViewDidLoad doesn't work. For example, I have a UITextField (called tfCode). If it's mapped in IB for EditingDidEnd to tfCodeChanged, it works:
partial void tfCodeChanged(NSObject sender)
{
...
}
However, in ViewDidLoad, if I put in the following code, it doesn't get hit:
tfCode.EditingDidEnd += delegate {
...
};
But in general I'm doing a lot of event handling in ViewDidLoad and they mostly work.
So, I'm confused. Can anybody explain why?
The events are triggered as long as you do not override the internal handlers by assigning to either the Delegate or WeakDelegate properties.

Resources