Downloading Initial App Data - ios

When a user opens the app, it needs to download information stored on a MySQL database, save it to Core Data, then display it in a table view controller. The download and saving of the data works, but not presenting it to the user. The data doesn't present itself the first time the view controller is displayed; it presents itself only after switching to a different view and returning. I have tried putting the code which loads the data in the viewWillAppear function (where I think it belongs) and the viewDidLoad function -- both with the same, previously described outcome. Can someone help spot what I may be doing wrong? Maybe I have the statements executing in the wrong order?
Also, another weird thing I see is when I run it in the debugger or with breakpoints (aka when I give the app more time to load), it works fine. It's only on a normal run when I have these problems.
View Did Load
override func viewDidLoad() {
/*This is where I would put the code to load the data. See the first
part of viewWillAppear too see what the code is.*/
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"viewWillAppear:", name:
UIApplicationWillEnterForegroundNotification, object: nil)
}
View Will Appear
override func viewWillAppear(animated: Bool) {
if(!defaults.boolForKey("objectivesDownloaded")) {
downloadObjectives()
defaults.setBool(true, forKey: "objectivesDownloaded")
}
defaults.synchronize()
fetchObjectives()
super.viewWillAppear(animated)
}
Fetch Objectives
func fetchObjectives() {
let fetchRequest = NSFetchRequest(entityName: "Objective")
let sortDescriptor = NSSortDescriptor(key: "logIndex", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
var error: NSError?
let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as [NSManagedObject]?
if var results = fetchedResults {
objectives = results
} else {
println("Could not fetch \(error), \(error?.userInfo)")
}
tableView.reloadData()
}

you probably aren't telling your table to reload data after you fetch your data. (it would initially load with no data when your view loads)
i.e. in obj-c
[yourTable reloadData]
swift
yourTable.reloadData()

The user has to login before seeing the main view, so I added the download code in the login script after it finds the login successful, but before the view controller is presented. The data now displays on start.
if(loginSucceeded) {
self.downloadObjectives()
self.defaults.setBool(true, forKey: "objectivesDownloaded")
let tabBarController = self.storyboard!.instantiateViewControllerWithIdentifier("tabBarController") as UIViewController
dispatch_async(dispatch_get_main_queue(), {() -> Void in
self.presentViewController(tabBarController, animated: true, completion: nil)
})
}

Related

viewWillAppear delay in update table from webservices

Is viewWillAppear the best place in the lifecycle to import my data from a webservice? This relates to a small exchange rate app.
In a tableview from viewwillappear, we go to http://api.fixer.io to update an array called rates, and all of the returned data in a class RatesData. If the Internet connection fails we either use the data we already have, or look to a file on the phone file system.
The time it takes to import the data means that I run cellForRowAt indexPath before my data array is populated; meaning that the data appears after a perceptible delay (I've default cells to load) before being updated with exchange rates.
I will implement coredata next as a better solution but the first time the app runs we would still get this undesired effect.
override func viewWillAppear(_ animated: Bool) {
searchForRates()
importCountriessync()
}
private func searchForRates(){
Request.fetchRates(withurl: APIConstants.eurURL) {[weak self] (newData:RatesData, error:Error?)->Void in
DispatchQueue.main.async {
//update table on the main queue
//returns array of rates
guard (error == nil) else {
print ("did not recieve data - getting from file if not already existing")
if ( self?.rates == nil)
{
self?.searchForFileRates()
}
return
}
self?.rates = newData.rates
let newData = RatesData(base: newData.base, date: Date(), rates: newData.rates)
self?.ratesFullData = newData
self?.tableView.reloadData()
}
}
}
func searchForFileRates(){
print ("file rates")
Request.fetchRates(withfile: "latest.json") { [weak self] (newData: RatesData)->Void in
DispatchQueue.main.async {
//update table on the main queue
//returns array of rates
self?.rates = newData.rates
let newData = RatesData(base: newData.base, date: Date(), rates: newData.rates)
self?.ratesFullData = newData
self?.tableView.reloadData()
}
}
}
Yes viewWillAppear is fine as long as the fetch is asynchronous.
Just remember it will be fired every time the view appears. Example when this view controller is hidden by another modal view controller and the modal view controller is dismissed, viewWillAppear will be called. If you want it to be called only once you could invoke it in viewDidLoad
Summary
viewWillAppear - Invoked every time view appears
viewDidLoad - Invoked once when the view first loads
Choose what meets your needs.

How to not perform query on viewDidAppear once a picture has JUST been selected? [duplicate]

This question already has answers here:
How to do some stuff in viewDidAppear only once?
(9 answers)
Closed 6 years ago.
I have a set up for a picture selection as follows:
#IBOutlet weak var imageHolder: UIImageView!
#IBAction func addImage(_ sender: UIButton) {
let pickerController = UIImagePickerController()
pickerController.delegate = self
pickerController.sourceType = UIImagePickerControllerSourceType.photoLibrary
pickerController.allowsEditing = true
self.present(pickerController, animated: true, completion: nil)
}
var imageSaved = UIImage()
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
self.dismiss(animated: true, completion: nil)
self.imageHolder.image = image
imageSaved = image
}
Selecting the picker brings up all of the pictures on the simulator, then the user can select them. Once it is selected the view returns to the original view and displays the picture in the imageView. It is not saved, though, until a "Save" button is hit in this view. This all works fine. The save button is merely an action that saves the image file to the DB.
I have the viewDidAppear set up to query the DB and display the image that has been saved as soon as the user moves into that view... this also works well. However, since the viewDidAppear is run multiple times, there becomes an issue when a new image is first selected to replace the old one. The "Save" button deletes the old object and replaces it, so once it has been replaced everything works fine. However, after selecting the image in the picker (and before hitting "Save"... and therefore before that file is in the DB) the view briefly displays the newly selected the image, but the viewDidAppear runs again and sends it back to the image currently in the DB. Its not technically an error, but it is ugly and I would like it to be fixed but I am not sure how.
This is the viewDidAppear code also:
override func viewDidAppear(_ animated: Bool) {
let query = PFQuery(className: "ProfilePictures")
query.whereKey("user", equalTo: (PFUser.current()?.objectId!)! as String)
query.findObjectsInBackground { (objects, error) in
if let objectss = objects{
if objectss.count != 0{
for object in objectss{
let imagePulled = object["ProfilePicture"] as! PFFile
imagePulled.getDataInBackground(block: { (data, error) in
if let downloadedImage = UIImage(data: data!){
self.imageHolder.image = downloadedImage
} else{
print(error)
}
})
}
}
}
}
}
Essentially, how do I get the image to only show the newly selected image in that space between selecting it and hitting save?
You should move your initial query in viewDidLoad(). That only gets called once, so it won't replace your image each time the view appears.
Otherwise, if, for some reason, you need to have it in view did viewDidAppear(), you could use dispatch_once to make sure your query only runs the first time the view appears.
Rough example of using dispatch_once:
var token: dispatch_once_t = 0
dispatch_once(&token) { () -> Void in
// your query goes here
}
If you are using Swift 3, please see this other response on how to get dispatch_once like functionality.

Refreshing table view with a UIRefreshControl in swift

I am working on an iOS app in which I have a UITableView which needs to be refreshed on command. I have added a UIRefreshControl object and hooked up a function to it so that whenever I drag down on the table view, my refresh code is supposed to take place. The code fragment is below:
#IBAction func refresh(sender: UIRefreshControl?) {
self.tableView.setContentOffset(CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl!.frame.size.height), animated: true)
sender?.beginRefreshing()
if (profileActivated) {
self.matches = []
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
let queryExpression = AWSDynamoDBScanExpression()
queryExpression.limit = 100;
// retrieving everything
dynamoDBObjectMapper.scan(DDBMatch.self, expression: queryExpression).continueWithBlock({ (task:AWSTask!) -> AnyObject! in
if task.result != nil {
let paginatedOutput = task.result as! AWSDynamoDBPaginatedOutput
//adding all to the matches array
for item in paginatedOutput.items as! [DDBMatch] {
self.matches.append(item)
}
//code to filter and sort the matches array...
self.tableView.reloadData()
}
return nil
})
self.tableView.reloadData()
}
sender?.endRefreshing()
self.tableView.setContentOffset(CGPointMake(0, self.tableView.contentOffset.y - (0.5*self.refreshControl!.frame.size.height)), animated: true)
}
Unfortunately, this code does not quite work right. Whenever I drag down on the refresh control, my table populates but then immediately goes away, and then refreshes about 10-15 seconds later. I inserted some print statements, and the data is all there, it just does not appear for a long time, and I am having trouble making it appear as soon as it is retrieved. I am new to iOS programming so any help would be appreciated.
Thanks in advance

Refresh Core Data in ViewController when Modal View (2nd view) is Dismissed - Swift

I'm trying to figure out how to reload my UIViewController after I dismiss a Modal View. What's happening is I segue from View 1 (my UIVIewController) to a Modal View where I make an update to Core Data. Upon completion, I save the Core Data and dismiss the Modal View, sending the user back to View 1 (the UIViewController). Problem is the UIViewController is not pulling the updated change to the Core Data (but instead is presenting the old information, because it has not been refreshed).
This was the closest answer I think that could work, but I'm having trouble translating from Objective-C to Swift.
Any ideas? Thanks in advance for the help.
Here is quick NSFetchedResultsController example
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
do {
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
}
private lazy var fetchedResultsController: NSFetchedResultsController = {
let animalsFetchRequest = NSFetchRequest(entityName: "Animal")
let sortDescriptor = NSSortDescriptor(key: "classification.order", ascending: true)
animalsFetchRequest.sortDescriptors = [sortDescriptor]
let frc = NSFetchedResultsController(
fetchRequest: animalsFetchRequest,
managedObjectContext: self.context,
sectionNameKeyPath: nil,
cacheName: nil)
frc.delegate = self
return frc
}()
// delegate method
func controllerDidChangeContent(controller: NSFetchedResultsController) {
// update UI
}
My suggestion for this issue is to create delegate that will notify View 1.
For instance:
in presented view controller create delegate:
protocol NotifyReloadCoreData
func notifyDelegate()
end
create property of view controller:
var delegate: NotifyReloadCoreData?
when press save or something like that :
delegate.notifyDelegate()
in your View 1
class UIViewController1: UIViewController, NotifyReloadCoreData
and implement function
func notifyDelegate(){
// reload core data here
}

How to update managed object data?

I have started my first core data application. I am working with one entity right now called 'Folder'.
The first view controller displays all the Folders in a tableview, which I can add to and it reloads the data. This works fine because It uses the fetch request to populate the table.
override func viewWillAppear(animated: Bool) {
var error: NSError?
let request = NSFetchRequest(entityName: "Folder")
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
self.events = moc?.executeFetchRequest(request, error: &error) as! [Folder]
self.UITable.reloadData()
}
However when segueing to another view controller via the table cell I pass on the selected Folder data to the controller using the index path. e.g.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showDetails" {
let destinationVC = segue.destinationViewController as! FolderDetailsViewController
let indexPath = UITable.indexPathForSelectedRow()
let selectedFolder = folders[indexPath!.row]
destinationVC.selectedFolder = selectedFolder
}
}
My second view controller uses the data passed from the first table view to display in textfields:
var selectedFolder: Folder!
folderNameLabel.text = selectedFolder?.title
folderDetailsLabel.text = selectedFolder?.details
folderDateLabel.text = displayDate
I then have a modal to edit/save the folder data in a modal appearing from the second controller:
//Edit and save event
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
//Error
var error: NSError?
//Storing Data from fields
SelectedFolder!.title = FolderName.text
SelectedFolder!.details = FolderDetails.text
SelectedFolder!.date = FolderDate.date
context?.save(&error)
self.dismissViewControllerAnimated(true, completion: {});
When dismissing the modulate data is not updated, I have to go back to the first controller to reload the data and segue again.
I think this is because I have no NSFetchRequest (or NSFetchResultsController) to get the most recent changes.
What is the best method to reload the data of the selectedFolder when I make the changes in the modal ?
You can refresh your second view in viewWillAppera() if your modal view is presented in full screen.
override func viewWillAppear(animated: Bool) {
{
folderNameLabel.text = selectedFolder?.title
folderDetailsLabel.text = selectedFolder?.details
folderDateLabel.text = displayDate
}
It seems like you would want to call moc.refreshObject(folder, mergeChanges:true)
See the documentation here.

Resources