Using TabViewController, When saving data, TableView not updating - ios

My current setup: TabViewController that is connected to two TableViewControllers embedded in navigation controllers. I am able to update the tableview no problem, on app load and when switching back between the different tabs.
My problem comes when I open another tableviewcontroller to edit. When I hit save from this view controller, it updates the data and saves everything just fine however, My tableView will not update no matter what I try unless I switch between the different tabs or close and reopen the app.
I have tried using delegates to trigger the update, I have tried use NSNotificationCenter, and no dice.
Has anybody else had this issue?
Here is my save function:
func saveDose(completion: (_ finished: Bool) -> ()) {
if searchName == "" {
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let dose = Dose(context: managedContext)
let doseNumberString = numberDosesText.text
let doseNumberInt = Int64(doseNumberString!) ?? 0
dose.name = nameText.text
dose.script = scriptText.text
dose.dosage = dosageText.text
// dose.doseInterval = doseInterval
dose.firstDose = datePicker.date
dose.numberDoses = doseNumberInt
dose.doseReminder = remindersSwitch.isOn
do {
try managedContext.save()
print("Data Saved")
completion(true)
} catch {
print("Failed to save data: ", error.localizedDescription)
completion(false)
}
} else {
update(name:searchName, firstDose: searchDate)
completion(true)
}
}
And here is where I call it and load back to my other tableview.
#IBAction func saveBtnPressed(_ sender: UIBarButtonItem) {
saveDose { (done) in
if done {
print("We need to return now")
navigationController?.popViewController(animated: true)
self.dismiss(animated: true, completion: nil)
} else {
print("Try again")
}
}
}
And here is where I reload my tableview when the view appears
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("View has been loaded back from other view")
self.fetchData()
self.tableView.reloadData()
print("View will appear")
}
And here is my fetchData and loadData functions
func fetchData() {
loadData { (done) in
if done {
setEmptyView(Array: logArray.count)
}
}
}
func loadData(completion: (_ complete: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Log")
do {
logArray = try managedContext.fetch(request) as! [Log]
print("Data Fetched No Issues")
completion(true)
} catch {
print("Unable to fetch data: ", error.localizedDescription)
completion(false)
}
}
Again I have tried delegates and have used this code in other applications that have worked fine. Is it something to do with the tab bar controller?
The data is obviously saving fine, I just can't get the tableView to update properly.
It seems like it is not calling the viewWillAppear function after the save. I have tried using delegates as well to force the update of the tableview but nothing has been working.

Basically you need to call tableView.reloadData(). after you have done saving.
Moreover you can use beginUpdates, endUpdates on tableView to perform animations on specific rows. for more information on this you may refer to This Link

Reload your tableview in custom delegates method your problem will be solved.

Related

Segue is not being performed

I've created a method for auto login via Firebase but somehow my segue is not being performed..
I've this code and in my viewDidLoad I'm calling the method (ofc)
override func viewDidLoad() {
super.viewDidLoad()
//Login user automatically
autoLogin()
}
func autoLogin(){
if Auth.auth().currentUser?.email != nil{
print("not nil") //for test
self.performSegue(withIdentifier: "toRootVc", sender: self)
print("not nil1") //for test
}
else{
print("nil") //for test
}
}
The app prints both "not nil" and "not nil1" but it still does not
performing the segue.
I also have a login button which works.
func handleLogin(){
Auth.auth().signIn(withEmail: email.text!, password: password.text!) { (result, err) in
if let err = err{
print("Error logging in:", err.localizedDescription)
}
else{
self.databaseHandler.retrieveData(email: self.email.text!){
self.performSegue(withIdentifier: "toRootVc", sender: self)
}
}
}
}
But the autoLogin doesn't actually performing the segue. (Ignores the step)
Any input would be much appreciated.
Segues won't work inside viewDidLoad as it's too early try in viewWillAppear or better do this check before presenting that vc say inside didFinishLaunchingWithOptions of AppDelegate
have you tried to put this autoLogin() on viewDidAppear()

Skip Button doesn't work for google pre roll on iOS

On iOS app with GoogleInteractiveMediaAds integrated "Skip Ad" button doesn't work. Meanwhile manual call adsManager.skip() works perfectly. The button itself reacts to the tuches because it changes bounds and seems highlighted. Unfortunately, I haven't found anything according to handle tap manually, so maybe somebody has already been in this situation and could help with it.
guard
let adInformation = delegate?.latestAdInformation(), let url = adInformation.urlForIMA,
let adContainer = delegate?.videoAdDisplayContainerView()
else { return }
switch adInformation.adsType {
case .interstitials, .none:
self.play(ignoreAds: true)
return
case .prerolls, .all:
fallthrough
#unknown default:
break
}
let adDisplayContainer = IMAAdDisplayContainer(adContainer: adContainer, companionSlots: nil)
let request = IMAAdsRequest(
adTagUrl: url,
adDisplayContainer: adDisplayContainer,
contentPlayhead: contentPlayhead,
userContext: nil)
adsLoader.requestAds(with: request)
func adsLoader(_ loader: IMAAdsLoader!, adsLoadedWith adsLoadedData: IMAAdsLoadedData!) {
// Grab the instance of the IMAAdsManager and set ourselves as the delegate
adsManager = adsLoadedData.adsManager
adsManager?.delegate = self
// Create ads rendering settings and tell the SDK to use the in-app browser.
let adsRenderingSettings = IMAAdsRenderingSettings()
if let vc = delegate?.adWebControllerPreferredOpenViewController() {
adsRenderingSettings.webOpenerPresentingController = vc
}
// Initialize the ads manager.
adsManager?.initialize(with: adsRenderingSettings)
}
func adsLoader(_ loader: IMAAdsLoader!, failedWith adErrorData: IMAAdLoadingErrorData!) {
self.play(ignoreAds: true)
}
func adsManager(_ adsManager: IMAAdsManager!, didReceive event: IMAAdEvent!) {
if event.type == IMAAdEventType.LOADED {
adsManager.start()
}
}
func adsManager(_ adsManager: IMAAdsManager!, didReceive error: IMAAdError!) {
self.play(ignoreAds: true)
}
func adsManagerDidRequestContentPause(_ adsManager: IMAAdsManager!) {
self.pause(ignoreAds: true)
}
func adsManagerDidRequestContentResume(_ adsManager: IMAAdsManager!) {
self.play(ignoreAds: true)
}

How do I tell when the table view can be reloaded so that it shows JSON data I've downloaded and parsed?

I have an app that downloads some information from a URL (ie. author name, story title, and the cover image). I'm able to download and parse the JSON from the server properly, but I'm stuck at one point.
The app consists of one View Controller (called ViewController.swift) and one class file (called GetStories.swift). Once the app has finished download and parsing the JSON from the server, I want the table view in the View Controller to reload itself (self.tableView.reloadData()).
I've set up a chain of completion blocks in GetStories.swift that accomplish the following steps in order:
1) Download the JSON
2) Parse the JSON
3) Save it to disk
func updateUI(){
saveDownloadedAndParsedJSONToDisk {
}
}
func saveDownloadedAndParsedJSONToDisk(completionHandler: #escaping RefreshTableView){
parseJSON {
self.saveDataToJSON()
completionHandler()
}
}
func parseJSON(completionHandler: #escaping ReadyToSave){
downloadJSON { jsonPayload, error in
do {
if let data = jsonPayload {
self.stories = try JSONDecoder().decode(Stories.self, from: data)
if let stories = self.stories {
self.stories = stories
completionHandler()
} else {
print("An error occurred while decoding JSON.")
}
} else if let error = error {
print("Error retrieving data: \(error)")
}
} catch {
print(error.localizedDescription)
}
}
}
func downloadJSON(completionHandler: #escaping NetworkResponse){
let storiesAPIURL = URL(string: "\(wattpadAPIURL)")
var wattpadAPIRequest = URLRequest(url: storiesAPIURL!)
wattpadAPIRequest.httpMethod = "GET"
let session = URLSession.shared
let dataTask = session.dataTask(with: wattpadAPIRequest) { (data : Data?, response : URLResponse?, error : Error?) in
if let data = data {
completionHandler(data, nil)
} else if let error = error {
completionHandler(nil, error)
print(error.localizedDescription)
}
}
dataTask.resume()
}
In ViewController.swift, I am calling updateUI(). Then, I'm calling the delegate method in the protocol:
self.storyResults?.delegate?.didFinishFetchingAndParsingData(finished: true)
The delegate method is doing this:
func didFinishFetchingAndParsingData(finished: Bool) {
guard finished else {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.tableView.reloadData()
SVProgressHUD.dismiss()
}
}
So, as you see, I'm using a 0.5 second delay to reload the table view because I'm not sure how to tell when exactly the downloading, parsing and saving has all fully finished.
If I don't use the delay of 0.5 seconds in the delegate method, the table view gets reloaded at an inappropriate time and there are no results displayed as a result. So, executing the reload this way doesn't work:
DispatchQueue.main.async {
self.tableView.reloadData()
}
What is the proper way to do this?
Thanks in advance!
Move the delegate call to the completionHandler inside updateUI, then it will be called at the right moment. Right now you have an empty completionHandler there.
func updateUI() {
saveDownloadedAndParsedJSONToDisk {
DispatchQueue.main.async {
self.storyResults?.delegate?.didFinishFetchingAndParsingData(finished: true)
}
}
}
...
func didFinishFetchingAndParsingData(finished: Bool) {
guard finished else {
return
}
self.tableView.reloadData()
SVProgressHUD.dismiss()
}

IOS navigationController?.popToRootViewController has wrong

#IBAction func addInformation(_ sender: UIBarButtonItem) {
// check rateHourly has value
if let editedRateHourly = rateHourly.text {
print("editedRateHourly condition is \(editedRateHourly)")
if editedRateHourly != ""{
print("not nil")
// check edit value is number?
let num = Int(editedRateHourly)
if num != nil {
print("is num")
// add to database
UserDefaults.standard.set(editedRateHourly, forKey: "\(findDate())")
UserDefaults.standard.synchronize()
// back to last viewController
navigationController?.popToRootViewController(animated: true)
}else{
print("not num")
print("error alert push!!")
popErrorAlert()
}
}else {
print("nil")
print("editedRateHourly condition is nil")
popErrorAlert()
}
}
}
#IBAction func cannelInformationPage(_ sender: UIBarButtonItem) {
navigationController?.popToRootViewController(animated: true)
}
I want to create a new simple edit page. It's two problem for me that when I finish edition if-else will check the condition is correct or not and then save the data popToRootViewControlle. When I finish edition I click on "addInformation" BarButtonItem and I get UI wrong. the Other wrong is when I click on editField but I don't enter any condition. And then I click on "cannelInformationPage" UIBarButtonItem. It also get wrong.
It's what I get wrong
wrong information
Because it returns Bool
_ = navigationController?.popToRootViewController(animated: true)
_ = self.navigationController?.popViewController(animated: true)
If you want to come back to last view controller than you can try above line, hope its work for you.

UIDocument not saving to file despite indicating success

I'm trying to open, modify, and save a file in iCloud Drive using UIDocument. When I call save(to:for:completionHandler:) with the file location and using .forOverwriting for the UIDocumentSaveOperation, it completes with a status of success = true. However, the iCloud file (as seen in both desktop and iOS file browser) does not update, and when reopening the file, the changes are not shown. I've verified that contents(forType:) returns the correct (modified) file contents when saving.
(Note: I've already looked at this question, but it wasn't very helpful 😕)
Here are the relevant sections of code:
MainViewController.swift:
var saveFile: SBDocument?
#IBAction func bbiOpen_pressed(_ sender: UIBarButtonItem) {
if saveFile == nil {
let importMenu = UIDocumentMenuViewController(documentTypes: self.UTIs, in: .import)
importMenu.delegate = self
importMenu.popoverPresentationController?.barButtonItem = bbiOpen
self.present(importMenu, animated: true, completion: nil)
} else {
willClose()
}
}
func willClose(_ action: UIAlertAction?) {
if saveFile!.hasUnsavedChanges {
dlgYesNoCancel(self, title: "Save Changes?", message: "Would you like to save the changes to your document before closing?", onYes: doSaveAndClose, onNo: doClose, onCancel: nil)
} else {
doSaveAndClose(action)
}
}
func doSaveAndClose(_ action: UIAlertAction?) {
saveFile?.save(to: saveFileURL!, for: .forOverwriting, completionHandler: { Void in
self.saveFile?.close(completionHandler: self.didClose)
})
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
saveFile = SBDocument(fileURL: url)
saveFile!.open(completionHandler: { success in self.finishOpen(didCompleteSuccessfully: success) })
}
func finishOpen(didCompleteSuccessfully result: Bool) {
if result {
print(saveFile!.localizedName)
saveFileURL = saveFile!.fileURL
saveFileName = saveFile!.localizedName
self.navTitleBar.prompt = saveFileName
bbiOpen.title = NSLocalizedString("titleClose", comment: "Close")
bbiOpen.style = .plain
} else {
saveFile = nil
}
}
#IBAction func bbiSave_pressed(_ sender: UIBarButtonItem) {
self.saveFile!.save(to: self.saveFileURL!, for: .forOverwriting, completionHandler: self.didSave)
}
func didSave(_ success: Bool) {
guard success else {
print("Error saving soundboard file to \(String(describing: saveFileURL))")
return
}
print("File saved successfully")
}
SBDocument.swift:
class SBDocument: UIDocument {
override var fileType: String? { get { return "com.whitehatenterprises.SoundBoardFX.sbd" } }
override var savingFileType: String? { get { return "com.whitehatenterprises.SoundBoardFX.sbd" } }
override init(fileURL url: URL) {
super.init(fileURL: url)
}
override func contents(forType typeName: String) throws -> Any {
let arr = NSArray(array: SoundEffects)
let data: NSData = NSKeyedArchiver.archivedData(withRootObject: arr) as NSData
return data
}
}
Update:
I really need help with this, and I've tried everything I can think of to fix this. Any assistance you could give me would be greatly appreciated.
The way the initial file generation works for me is:
let doc = YourUIDocumentClass(fileURL: fileURL)
doc.save(to: fileURL, for: .forCreating) { success in
...
}
Then modify the file and then do:
doc.save(to: fileURL, for: .forOverwriting) { success in
...
}
when done. And subsequent accesses to the file are done by:
doc.open() { success in
...
}
doc.close() { success in
...
}
You might also need to do a:
doc.updateChangeCount(.done)
while the file is open to tell the document there are unsaved changes. Just setting this will cause a save after a few seconds. You don't even need the close to do that.
The ... means that you either have to nest all these or make sure there is enough time between them so they are completed.
In addition to the above answers, another cause of this can be that there's an error during the save process unrelated to contents(forType:).
For example, if you implement fileAttributesToWrite(to:for:) and throw an error, then this can cause a UIDocumentState.savingError even though contents(forType:) returns the correct data.
So according to
https://developer.apple.com/reference/uikit/uidocument
It looks like the save function isn't actually for saving a document. My understanding from reading it is that save is only for creating a new document. I understand that you are using the .forOverwriting to just save over it but there may be something in iCloud that wont let the complete overwrite happen.
In your doSaveAndClose method try calling
self.saveFile?.close(completionHandler: self.didClose)
by itself. You may have to do some type of if query where you check if the file exist. If it doesn't then call the .save(), else call the .close function. It seems that no matter what when the document it closed it saves changes.

Resources