Won't update UILabel from Parse data in swift - ios

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.

Related

how to prevent button showing up for split second when view loads

So my goal is to smoothly load the viewController with no split second bugs. I have a function that is used to determine what buttons to show when the view loads based off a field in a Firestore document. Here is the function:
func determinePurchasedStatusVerification() {
db.collection("student_users/\(user?.uid)/events_bought").whereField("event_name", isEqualTo: selectedEventName!).whereField("isEventPurchased", isEqualTo: true).getDocuments { (querySnapshot, error) in
if let error = error {
print("\(error)")
} else {
guard let querySnap = querySnapshot?.isEmpty else { return }
if querySnap == true {
self.purchaseTicketButton.isHidden = false
self.viewPurchaseButton.isHidden = true
self.cancelPurchaseButton.isHidden = true
} else {
self.purchaseTicketButton.isHidden = true
self.viewPurchaseButton.isHidden = false
self.cancelPurchaseButton.isHidden = false
}
}
}
}
I call this function in the viewWillAppear() of the vc but when I instantiate to that vc, this is the result...
The extra purchase ticket button shows up for a split second. Even though it's very quick, you can still see it and it's just not something a user would need to see. It's also the other way around when you click on a cell that's not purchased, the two bottom buttons show up for a split second. I just want to know how I can prevent this quick bug and be able to display a smooth segue with no delays in the button hiding. Thanks.
getDocuments is an asynchronous function, meaning it doesn't call its callback function immediately -- it calls it when it gets data back from the server. It may seem like a split second just because your internet connection is fast and the Firebase servers are definitely fast, but it's a non-zero time for sure. And, someone with a slower connection might experience much more of a delay.
Unless your callback is getting called twice with different results (which seems doubtful), the only solution here is to make sure that your initial state has all of the buttons hidden (and maybe a loading indicator) and then show the buttons that you want once you get the data back (as you are right now). My guess is, though, that you have an initial state where the buttons are visible, which causes the flicker.

do network call from current ViewController or parent ViewController?

I'm writing an app that contains network call in every other screen. The result of calls would be the dataSource for a specific screen.
The question is, should I do network call in the parent viewController and inject the data before pushing current viewController or push currentViewController and do network call on viewDidLoad()/viewWillAppear()?
Both the methods makes sense to me.
Where you make the request to network should actually make no difference. You are requesting some data which you will have to wait for and present it. Your question is where should you wait for the data to be received.
As #Scriptable already mentioned you can do either of the two. And which to use depends on what kind of user experience you wish to have. This varies from situation to situation but in general when we create a resource we usually wait for it on current screen and when we are reading resources we wait for it on the next screen:
For instance if you are creating a new user (sign up) after you will enter a new username and password an indicator will appear and once the request is complete you will either navigate to next screen "enter your personal data" or you will receive a message like "User already exists".
When you then for instance press "My friends" you will be navigated to the list first where you will see activity indicator. Then the list appears or usually some screen like "We could not load your data, try again."
There are still other things to consider because for the 2nd situation you can add more features like data caching. A lot of messaging applications will for instance have your chats saved locally and once you press on some chat thread you will be navigated directly to seeing whatever is cached and you may see after a bit new messages are loaded and shown.
So using all of this if we get back to where you should "call" the request it seem you best do it before you show the new controller or at the same time. At the same time I mean call it the load on previous view controller but load the new view controller before you receive the new data.
How to do this best is having a data model. Consider something like this:
class UsersModel {
private(set) var users: [User]?
}
For users all we need is a list of them so all I did was wrapped an array. So in your case we should have an option to load these users:
extension UsersModel {
func fetchUsers() {
User.fetchAll { users, error in
self.users = users
self.error = error // A new property needed
}
}
}
Now a method is added that loads users and assigns them to internal property. And this is enough for what we need in the first view controller:
func goToUsers() {
let controller = UserListViewController()
let model = UserModel()
controller.model = model
model.fetchUsers()
navigationController.push(controller...
}
Now at this point all we need is to establish the communication inside the second view controller. Obviously we need to refresh on viewDidLoad or even on view will appear. But we would also want some delegate (or other type of connections) so our view controller is notified of changes made:
func viewDidLoad() {
super.viewDidLoad()
self.refreshList()
self.model.delegate = self
}
And in refresh we should now have all the data needed:
func refreshList() {
guard let model = model else {
// TODO: no model? This looks like a developer bug
return
}
if let users = model.users {
self.users = users
tableView?.reloadData()
if users.count.isEmpty {
if let error = model.error {
// TODO: show error screen
} else {
// TODO: show no data screen
}
}
} else {
// TODO: show loading indicator screen
}
}
Now all that needs to be done here is complete the model with delegate:
extension UsersModel {
func fetchUsers() {
User.fetchAll { users, error in
self.users = users
self.error = error // A new property needed
self.delegate?.usersModel(self, didUpdateUsers: self.users)
}
}
}
And the view controller simply implements:
func usersModel(_ sender: UserModel, didUpdateUsers users: [User]?) {
refreshList()
}
Now I hope you can imagine the beauty of such a system that your model could for instance first asynchronously load users from some local cache or database and call the delegate and then call the request to server and call the delegate again while your view controller would show appropriate data for any situation.

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.

Parse array seems to store locally to my app session

I have a weird bug in my app that I haven't seen before. Basically I have two views - User Profile and Edit Profile. In the User Profile view, I retrieve two things from Parse:
An array of strings telling the order of the user's profile photos (e.g. ["pic1", "pic3", "pic2"])
The photo files themselves (up to 6)
In the Edit Profile View, the user is able to upload new photos as well as rearrange the order array. The view also has a button to close the view and discard any changes as seen here:
#IBAction func cancelTapped(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
The problem is that if I make any changes to photos order array such as rearranging it or appending to it, then hit the "Cancel" button, the view dismisses as it should. However, when I go back into the Edit Profile view or even if I close the User Profile then reopen that, both views show the new order array that wasn't supposed to be saved because I hit the "Cancel" button. The only way I can fix is by closing the app and reopening.
Is the array being saved locally to the iPhone? Please let me know if you need any more info or to see more code!
Edit: Forgot to mention, when I look in Parse the values aren't stored on there, but the Xcode logs say the array is changed when reloading the view.
Edit 2: Here is the instantiation of self.photoOrder:
var photoOrder:NSMutableArray = []
currentuser instantiation from Constants.swift
var currentuser = PFUser.currentUser()
I don't know how your reorder method looks like and if your calling the save method on the PFUser object when reording is done. It seems like you're overwritting the photoOrder attribute in your edit profile view somehow.
Solution 1
A simple solution would be to backup the current value when you load the view:
var photoOrderBackup:NSMutableArray = []
// viewDidLoad
self.photoOrder = currentuser.objectForKey("photoOrder") as! NSMutableArray
self.photoOrderBackup = currentuser.objectForKey("photoOrder") as! NSMutableArray
Then you got the chance to apply the backup values when the user cancels the operations.
#IBAction func cancelTapped(sender: AnyObject) {
currentUser.photoOrder = self.photoOrderBackup; // Apply backup value
// Perhaps add the save method here after you've overwritten the photoOrder value
self.dismissViewControllerAnimated(true, completion: nil)
}
Solution 2
If the changes aren't saved to the currentuser object yet, refreshing the object could help to dismiss pending changes.
#IBAction func cancelTapped(sender: AnyObject) {
currentUser.refresh() // Refresh object with local data
self.dismissViewControllerAnimated(true, completion: nil)
}

ios swift parse: methods with async results

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.

Resources