Updating navigation in Viewcontrollers - Coredats - swift

I am a new programmer using Swift.
In my project I am using coredata, and a lot of view controllers.
I have this view controllers:
Viewcontroller1 has my home Viewcontroller (VC1).
Viewcontroller2 (VC2), with a list of items reloaded from my coredata (in a table view).
Viewcontroller2 (VC3), lists the attributes of the selected item in VC2.
Viewcontroller2 (VC4), makes the user edit the attributes of selected item in VC2.
SO this is my navigation: VC1 -> VC2 -> VC3 -> VC4.
The problem:
Lets say I am in VC1 and go to VC2.
I now choose an item from view controller and it takes me to VC3 (I push VC3).
I am now in the item characteristics. which are listed from coredata.
To edit them I made a button, with a segue, VC4, where I made a view where the user can change the values of the choosen item. Once the user introduces any changes in the text fields, I do a NSFetchRequest, and update the values like this:
#IBAction func saveButton(_ sender: UIBarButtonItem) {
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Simulator")
do {
let results = try context.fetch(request)
if results.count > 0 {
for result in results as! [NSManagedObject] {
result.setValue(designLabel.text, forKey: "designation")
result.setValue(typeLabel.text, forKey: "type")
result.setValue(localLabel.text, forKey: "local")
do {
try context.save()
} catch {
print("Error updating")
}
}
}
} catch {
print ("Error")
}
_ = self.navigationController?.popViewController(animated: true)
}
So now, by pressing saveButton I update local, designation and type atributes, and than pop VC3.
Now in VC3 I expected to receive the update values. Instead I am receiving the old values. This is what I have in VC3:
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
map.delegate = self
let fetchRequest:NSFetchRequest<Simulator> = Simulator.fetchRequest()
do{
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
for result in searchResults as [Simulator]{
if (String(describing: result.objectID) == choosenID)
{
self.title = result.designation
localLabel.text = "Local: \(result.local!)"
typeLabel.text = "Type: \(result.type!)
print("I found it") //to check if I go inside this = and I go inside (true)
{
}
}
catch{
print("Error: \(error)")
}
}
}
Now I press the back button and I go to VC2: some thing. The cell, which have the designation on a label still don't updated it.
So I go to VC! using back button.
Now If I move forward to VC2 or VC3 everything Is updated.
What it's happening? Why isn't it updating when I pop from VC4 to VC3?

When you go back you are going back to the view as it was, when you go forward you are reloading it. What you can do though is reload the data when you go back.

Related

Update Tab Item Badge after getting data (Swift)

I'd like to update a Badge on a Custom Tab Bar Item when I receive some data. I am able to update the badge on the initial viewDidLoad() but then when I try to call viewDidLoad again later with the data, my tab items are nil. Here is how I have set it up...
class CustomTabBar: UITabBarController {
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
print("this prints correctly every time I call reload with the updated count: " + count)
if let tabItems = self.tabBar.items {
let tabItem = tabItems[0]
tabItem.badgeValue = String(count)
}else{
print("tab items nil")
}
}
func reload(count: Int){
self.count = count
viewDidLoad()
}
}
I'm calling reload() from another view controller after I get the data I need.
func updateBadge(){
let tabBar = CustomTabBar()
let username = UserUtil.username
let db = Firestore.firestore()
let upcomingContestsRef = db
.collection("NBAContests")
.whereField("EnteredUsers", arrayContains: username)
.whereField("Stage", isEqualTo: 2)
upcomingContestsRef.getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
print("count is " + String(querySnapshot!.count))
tabBar.reload(count: querySnapshot!.count)
}
}
}
I've check that viewDidLoad is getting called each time in custom tab bar controller, but after the initial load I don't have access to change the tab items anymore.
Does anyone know whats going on?
I've check out these similar questions
Reload / Refresh tab bar items in a ViewController ?
Setting badge value in UITabBarItem in UIViewController
This creates
let tabBar = CustomTabBar()
a new instance instead you need
guard let tabBar = self.tabBarController as? CustomTabBar else { print("returned") ; return }

Userdefaults Boolean for button

I am fairly new to Swift programming. Using Userdefaults I was trying to customize user behaviour. Below image is of my initial controller. I require to save userdefaults so that App remembers the user selection of button, (i.e. A or B). Can you assist to provide me a function that I use in viewDidLoad and it remembers the button selection and segues to its respective ViewController.
My code to perfrom segue if Button A or B is selected is
let parent = self.storyboard?.instantiateViewController(withIdentifier: "DashboardVC") as! DashboardVC
self.navigationController?.pushViewController(parent!, animated: true)
Yet it doesnt segue. It keeps loading my initial viewcontroller.
do like
set the tag for each button and create the common method for handle the function , for e.g
#IBAction func handle_Action(_ sender: UIButton) {
defaultName.set(sender.tag, forKey: "yourKeyName")
}
and in your class
class ViewController: UIViewController {
let defaultName = UserDefaults.standard
// finally access the integer in your Viewload
override func viewDidLoad() {
super.viewDidLoad()
let getVal = defaultName.integer(forKey: "yourKeyName") as Int
if getVal == 1{ //called by A
}else if getVal == 2{
//called by B
}else{ // not interactwithButton action }
}

How to detect if a pushed viewcontroller appears again?

assuming I have a viewcontroller (vcA) that pushes QRCodeScannerViewcontroller (vcB). When (vcB) scanned something, It will push ResultviewController (vcC).
-Those 3 views is connected to a UInavigation controller
-the user clicks on the back button on (vcC)
my question is:
1)how can I know if (vcB) is visible without changing code on (vcB)? (vcB) is a pod
2)where will I put this code? I can only access (vcA)
i tried adding this code on (vcA) but nothing happened
override func viewDidDisappear(_ animated: Bool) {
if (vcB.isViewLoaded && (vcB.view.window != nil)){
print("vcb did appear!")
}
}
To know if an instance of cvB's class exists in the navigation stack, you could use this piece of code:
let result = self.navigationController?.viewControllers.filter({
if let vcB = $0 as? UIViewController { // Replace UIViewController with your class, for example ViewControllerB
return true
}
return false
})
if result.isEmpty {
print("An instance of vcB's class hasn't been pused before")
} else {
print("An instance of vcB's class has been pused before")
}

Trying to pass information between controllers fails, yet works from AppDelegate to controller

I have two UIViewControllers, vc1, and vc2. vc1 is embedded in a UIViewController which is embedded in a UITabBarController, but vc2 is not embedded in either.
How do I pass information from vc2 to vc1? After a user performs an action the data is saved and vc2 simply closes, so there isn't a segue to pass information. Obviously I can't reference vc1 through the Navigation stack or the TabController.
I could save to the AppDelegate, but I've read this isn't a good practice.
This is the code I use to pass information from AppDelegate to vc1 I tried it in vc2, but obviously it failed.:
let tabBarController = window!.rootViewController as! UITabBarController
if let tabBarViewControllers = tabBarController.viewControllers {
let navPostViewController = tabBarViewControllers[0] as! UINavigationController
let user = User(context: managedObjectContext)
if user.userID != nil {
print("User is loggedIn")
isUserLoggedIn = true
} else {
print("User is not loggedIn")
isUserLoggedIn = false
}
let postViewController = navPostViewController.topViewController as! PostViewController
postViewController.managedObjectContext = managedObjectContext
}
First off, I've never got into the habit of using segue to pass information. What i would recommend is that you implement the delegate pattern whenever you need to pass data between two objects. Its a lot cleaner.
For instance lets say you wanted to pass data between LoginViewController and PostViewController:
protocol LoginViewControllerDelegate:NSObjectProtocol{
func userDidLogin(data:String)
}
class LoginViewController:UIViewController {
weak var delegate:LoginViewControllerDelegate?
...
#IBAction func loginButtonPressed(sender:UIButton) {
//Perform login logic here
//If successful, tell the other controller or the 'delegate'
self.delegate?.userDidLogin(data:"Some data....")
}
}
class PostViewController:UIViewController, LoginViewControllerDelegate {
func userDidLogin(data:String) {
print("Got data from login controller: \(data)")
}
}
//How you might use this
loginViewController.delegate = postViewController
One caveat to remember is to never try to have strong references between two objects i.e. do not have the objects hold onto each other or this will cause a memory leak.

Segue async Firebase data through NavigationController (Swift)

I have been chasing this for two days but yet I am still not sure why my variable isn't being passed in my segue from my login view controller to the chat view controller via the navigation view controller.
I have a button that queries Firebase, checks if the user exists and returns a Firebase query reference for the user. I then want to pass this Firebase query reference when it finishes to the navigation controller's top view controller for use.
Inside my IBAction login button, I have:
var tempUserRef: FIRDatabaseReference?
channelRef.queryOrdered(byChild: "uid").queryEqual(toValue: uid).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
print("uid exist with \(snapshot.childrenCount) number of children")
for s in snapshot.children.allObjects as! [FIRDataSnapshot] {
tempUserRef = self.channelRef.child(s.key)
}
} else {
print("uid didn't exist")
if let name = self.nameField?.text { // 1
tempUserRef = self.channelRef.childByAutoId()
let channelItem = [
"name": name,
"uid": self.uid
]
tempUserRef?.setValue(channelItem)
}
}
self.userRef = tempUserRef
DispatchQueue.main.async {
self.performSegue(withIdentifier: "LoginToChat", sender: tempUserRef)
print("passsed \(self.userRef)")
}
})
Here is my segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if "LoginToChat" == segue.identifier {
if let navVc = segue.destination as? UINavigationController {
if let chatVc = navVc.topViewController as? ChatViewController {
chatVc.senderDisplayName = nameField?.text
if let userRef = sender as? FIRDatabaseReference {
chatVc.userRef = userRef
print("passsing \(self.userRef) to \(chatVc)")
}
}
}
}
super.prepare(for: segue, sender: sender)
}
The print statements all look good on my login controller but when I get to the chat view controller, the userRef is still nil. So my sense is that I have the right segue inputs and handoffs but that the async nature of the data is somehow out of step with my segue.
The chat view controller is using the JSQMessages library if that makes a difference.
Thanks for your help.
EDIT:
Based off feedback I've moved the super.prepare but userRef is still not being set consistently.
SECOND EDIT:
Following paulvs' suggestion, I removed my button segue. However, I did have to create another segue that connected view controller to view controller like this SO question.
Place the call to super.prepare at the end of the function. Otherwise, the segue is performed before you set your variables.