ios swift parse: methods with async results - ios

When I go to a viewController I call within my viewDidAppear Method a function:
override func viewDidAppear(animated: Bool) {
getLessons()
}
This methods loads from parse.com a list of data I want to use in a pickerView.
The function itself:
func getLessons(){
var query = PFQuery(className:"Lesson")
query.orderByAscending("name")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
var name = object["name"] as String
self.languagePickerKeys.append(object.objectId)
self.languagePickerValues.append(name)
self.selectedLanguage.text = self.languagePickerValues.first // set the first lessons name into the text field
self.selectedLessonObjectId = self.languagePickerKeys.first // set the first objectId for the lesson
self.languagePicker?.reloadAllComponents()
}
} else {
// Log details of the failure
println("\(error.userInfo)")
}
}
println("getLessons done")
}
The thing is, that the textfield is empty, as the getLesson() gets the data async and the data is not available to the textfield.
I also tried to put the getLesson into the viewDidAppear method, but this doesn't help me, the textfield is empty anyway.
What can I do, to have the data from the getLessons() method ready and loaded its first value into my textfield when the view is shown to the user?

You certainly have to get the data from asyncTask before setting it to pickerView.
Here's the ViewController lifecycle after instantiation:
Preparation if being segued to.
Outlet setting
Appearing and Disappearing.
So, you have two options:
Load the data in previous ViewController and then perform the segue. You need to follow these steps for it.
a. Create a segue from previous ViewController to your ViewController.
b. Call the function when you want to go next ViewController which fetches the data, and the end (after getting the data) call performSegueWithIdentifier which will lead to your ViewController.
c. Set the data in prepareForSegue
let navigationController = segue.destinationViewController as UINavigationController
navigationController.data = yourData //you got from async call
Now when you reach your ViewController, you are sure that your data is present, and you can set it to your pickerView.
If you want to do it in the same ViewController: here's is the lifeCycle of ViewController:so you need to call your function in viewDidLoad, and always set your pickerView after completion of the async network call.

Make sure that you initiate all changes to the UI from the main thread e.g. like so:
dispatch_async(dispatch_get_main_queue(), {
selectedLanguage.text = languagePickerValues.first
self.languagePicker?.reloadAllComponents()
})

The problem is that findObjectsInBackgroundWithBlock is an asynchronous method, so even if you fire it in the ViewDidLoad you will never know when you will receive the response data and you can't be sure that the data will be ready by the time you view appear.
I think you have just 2 possibility:
The first one is to load the data in the previous view controller and then just pass the data that got ready to you view controller.
The second is to use a synchronous method (the findobject method maybe?) and put the call in a method that is fired BEFORE the view appear (like the viewWillAppear: method). But your view will stuck for a moment (I think) while the data is retreiving... However this second solution probably resolve your problem but using synchronous method to retrieve data from a slower data source is usually bad design solution.
D.

Related

Load UITable Data from another View Controller

I have two Views:
UITableViewController (View A)
UIViewController (View B)
I was wondering, if it's possible to load and setup the table from View B and then segue to View A, when the loading is done. I need this, since the Table View loads Data from Core Data and that takes some time; I would then show a Loading Animation or something. I have a function called loadData() in View A, which fetches all Elements from Core Data and then calls tableView.reloadData().
Does anyone know, how I could implement this? Or should I somehow show the loading View directly from View A with a SubView or something?
Remember to not think about the specifics but instead, think generally:
You want to move from one VC to another and you have some data that needs to be fetched asynchronically. Let's assume you can't know how long it will take.
My suggestion is to contain all data fetching related to a VC inside that VC itself (or services/facades related to it). So basically you should present the UITableViewController and then have it fetch the data while showing skeleton-cells/spinner/etc.
You want to have separation of concerns which means you don't want your ViewController to handle data related to another view controller.
Think about the following use-case: if you have code to fetch data in the previous VC, before presenting the TVC, what happens when you need to re-fetch the data or refresh something? You will have to duplicate the code in both the VC and the TVC.
That's why it's suggested to keep data fetching inside the view controller that needs it.
If, for some reason, you still want to have your answer for this specific question:
You can have the initial VC create the TVC, but not present it yet, call its methods to fetch the data, and have it send a callback (closure/delegate/etc) when it's done fetching. When the fetching is done, present the TVC.
Here is a quick example:
class MyTableVC: UITableViewController {
private var myData: [Int] = []
public func fetchData(completion: () -> Void) {
//Fetch data asyncly
myData = [1, 2 ,3]
completion()
}
}
class MyVC: ViewController {
private func loadTableVC() {
let tableVC = MyTableVC()
tableVC.fetchData { [weak self] in
self?.present(tableVC, animated: true, completion: nil)
}
}
}
Again, I wouldn't use this due to having tight coupling between the 2 view controllers, but it's always up to you to decide how to design your code.

How to read in data just the once when using a tabcontroller

I feel like I'm missing something and this should not be too hard.
I'm reading in some data in the initial scene in my app.
I've got a singleton and I make the call in viewDidLoad to singleton.getData().
This initial scene is part of a tab controller. And while I thought viewDidLoad would only get called once for each scene I'm pretty sure it's being called a few times during the lifecycle of my app.
So just wondering if there is a way to ensure a function call to retrieve some data only happens once.
viewDidLoad will be called when selected tab is changed, you can change the place you call getData.
If you want to call getData in viewDidLoad and be sure it won't be called multiple times you can create a flag and check, if it is previously called or not.
class Singleton {
static let sharedInstance = Singleton()
private static var getDataCalled = false
func getData() {
if Singleton.getDataCalled {
return
}
Singleton.getDataCalled = true
// request data
print("data requested")
}
}
Singleton.sharedInstance.getData()
Singleton.sharedInstance.getData()
Calling getData multiple times print data requested only once.

Swift: NSUserDefaults not setting straight away

What I have
I have a view controller (SelectNameViewController) with a UITableView on with a list of names. When the user clicks a name, it goes to a ViewController (ResultController) which has a container view that loads 3 ViewControllers (vc1,vc2,vc3) allowing the user to scroll through.
On ResultController I have an alamofire request that connects to a JSON file and downloads information based on the name selected:
Alamofire.request(.GET, sendyURLSet, parameters: ["name": passedName, "api_key": api_key])
.responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
self.age = json["age"].stringValue
self.userInfo.setValue(self.age, forKey: "age")
}
case .Failure(let error):
print(error)
}
}
On VC1 I have this in my viewDidAppear
if let a = userInfo.stringForKey("age") {
print("Age: \(a)")
} else {
print("nope")
}
Problem
I am having trouble loading data on a PageViewController child view after the content has been loaded from an Alamofire request.
When the user clicks a name and is taken to the ResultController the 3 views load into the container view. The first screen to appear is vc1.
However, in the console I just get:
Nope
If I swipe to vc2 and then back to vc1 I get:
Age 32
in the console.
Question
Can someone tell me why the value is not showing up on the first presentation of the ViewController (vc1) and tell me how to fix it?
It is driving me crazy
looks like vc1 is being loaded quicker than the data fetch is completing.
Either wait for the data to be loaded before you load the new controllers, or set the label directly when the data has completed
Likely, the callback to your Alamofire.request is called after viewDidAppear, and that explains why you do not see the value you set in the callback.
What you should want is that your code
if let a = userInfo.stringForKey("age") {
print("Age: \(a)")
} else {
print("nope")
}
is called after the callback is executed.
You have several means to this:
resultsController calls into VC1 to make it update its UI;
load resultsController from the callback (actually, you make sure you call its view method for the first time in the callback, i.e., when you add it as a subview to the parent);
or, you use KVO to make VC1 observe any changes to userInfo.age. This approach requires customising NSUserDefaults by way of deriving a new class from it.
Try following one. I think it may help you.
On completion of your network call (in completion handler block) set values to UI elements like label, using outlets. And call - (void)setNeedsLayout method.This method to indicate that the layout of a layer’s sublayers has changed and must be updated. The system typically calls this method automatically when the layer’s bounds change.

Wait for closure result for doing the return

I got a book reader coded in Swift, the first chapter is OK, but when I try to load the second from the webservice (the books come from a server chapter by chapter), the data source method of the pageviewcontroller has to return a viewcontroller, and it doesn't wait to the closure which gets the new chapter, it always return nil. I've tried using dispatch_semaphore_t, dispatch_group_t, etc, and I don't get it. The method is the following:
// MARK: - PageViewControllerDataSource
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
if self.currentPageInSpineIndex! + 1 < self.currentChapterPages {
self.currentPageInSpineIndex!++
} else if self.selectedChapter + 1 < self.tale?.chapters.count {
self.selectedChapter++
self.currentPageInSpineIndex = 0
}
self.pageContentViewControllerAtIndex(self.currentPageInSpineIndex!, onCompletePage: { (let pageContentViewController: PageContentViewController) -> Void in
return pageContentViewController
})
return nil
}
The pageContentViewControllerAtIndex method checks into de DB if it has the text of the chapter, and if not, it asks the server for it, then create a pagecontentviewcontroller with the text in it, and then returns it on the onCompletePage block, the thing is, that it always reachs the return nil line before the closure ends, so it doesn't work. I want to wait for the closure, have the pagecontentviewcontroller, and then return that result. Some ideas?
The pageViewController:viewControllerAfterViewController: method expects a UIViewController (or subclass) to be returned, you won't be able to use asynchronous code within this method to create and return a view controller.
Suggestions based on waiting for the asynchronous method to complete are not appropriate. This is because the UIPageViewController delegate methods are invoked on the main thread and introducing any waiting mechanism will block the UI resulting in an unresponsive feel and potentially causing the system to terminate the app.
What I would recommend doing is creating a view controller, return it, and load the content from that view controller. Most likely you will want to display some kind of loading indicator on that view until the content has been retrieved.
Have a look at my Swift library which has a bunch of functions to convert between asynchronous and synchronous functions: https://github.com/Kametrixom/Swift-SyncAsync There's a bunch of examples on the website, as well as a very detailed playground
You'd be able to do something this:
return toSync(pageContentViewControllerAtIndex)(currentPageInSpineIndex!)
EDIT: You're doing a web request -> asynchronous!

Won't update UILabel from Parse data in swift

I am trying to update my UILabel to a String from my Parse database.
My problem is the label will not update my firstnameLabel when I first sign in. But it WILL update, when i sign in (nothing happens), push the stop button in Xcode and then launch it again (still logged in) and then it updates the label.
How can I do this faster??
Here is my code:
var currentUser = PFUser.currentUser()
if currentUser != nil {
var query = PFQuery(className:"_User")
query.getObjectInBackgroundWithId(currentUser.objectId) {
(bruger: PFObject!, error: NSError!) -> Void in
if error == nil && bruger != nil {
var firstName: String = bruger["firstname"] as String
self.usernameLabel.text = firstName
} else {
println("Error")
}
}
} else {
self.performSegueWithIdentifier("goto_login", sender: self)
}
Hope you can help me!
Rather than trying to load the user object again by the id, try just doing a fetch instead.
[currentUser fetchIfNeededInBackgroundWithBlock: ... ];
By trying to load the object, you might be getting a cached version.
I read somewhere that it could be because of the fact that the could was put inside the ViewDidLoad. I tried to put it outside of that and it worked!
It sounds like you put it in the ViewDidLoad method. You should put it in the ViewWillAppear method instead. Here's an example.
1) ViewDidLoad - Whenever I'm adding controls to a view that should appear together with the view, right away, I put it in the ViewDidLoad method. Basically this method is called whenever the view was loaded into memory. So for example, if my view is a form with 3 labels, I would add the labels here; the view will never exist without those forms.
2) ViewWillAppear: I use ViewWillAppear usually just to update the data on the form. So, for the example above, I would use this to actually load the data from my domain into the form. Creation of UIViews is fairly expensive, and you should avoid as much as possible doing that on the ViewWillAppear method, becuase when this gets called, it means that the iPhone is already ready to show the UIView to the user, and anything heavy you do here will impact performance in a very visible manner (like animations being delayed, etc).
3) ViewDidAppear: Finally, I use the ViewDidAppear to start off new threads to things that would take a long time to execute, like for example doing a webservice call to get extra data for the form above.The good thing is that because the view already exists and is being displayed to the user, you can show a nice "Waiting" message to the user while you get the data.

Resources