Update Tab Item Badge after getting data (Swift) - 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 }

Related

Tableview not loading data after popping View Controller

When I pop the view controller stack, I need a table view in the first view controller to reload. I am using viewWillAppear (I already tried viewDidAppear and it didn't work).
override func viewWillAppear(_ animated: Bool) {
print("will appear")
loadData()
}
Once the view controller has re-appeared, I need to query the API again which I am doing in another service class with a completion handler of course and then reloading the table view:
#objc func loadData() {
guard let userEmail = userEmail else { return }
apiRequest(userId: userEmail) { (queriedArticles, error) in
if let error = error {
print("error in API query: \(error)")
} else {
guard let articles = queriedArticles else { return }
self.articlesArray.removeAll()
self.articleTableView.reloadData()
self.articlesArray.append(contentsOf: articles)
DispatchQueue.main.async {
self.articleTableView.reloadData()
}
}
}
}
What happens is that I am able to pop the stack and see the first view controller BUT it has the same data as it did before. I expect there to be one more cell with new data and it doesn't appear. I have to manually refresh (using refresh control) to be able to query and load the new data.
Any idea what I am doing wrong?

JSQMessagesviewcontroller can't perform segues

so I created a chat view controller using the JSQMessagesViewController following this tutorial here: https://learnappmaking.com/chat-app-ios-firebase-swift-xcode/#comment-1930 my code is more or less the same, I didn't tweak anything significant in it, the tutorial is only for a single view controller so I added another view controllers for the app but every time it perform segues, I get the error SIGABRT, no matter if I segues with performSegue or with the back button in navigation bar, it keeps giving signal SIGABRT. any help would be appreciated.
this is my viewdidload:
override func viewDidLoad() {
super.viewDidLoad()
senderId = "1111"
senderDisplayName = "Bob"
title = "Steve"
inputToolbar.contentView.leftBarButtonItem = nil
collectionView.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
let query = Constants.refs.databaseChats.queryLimited(toLast: 10)
_ = query.observe(.childAdded, with: { [weak self] snapshot in
if let data = snapshot.value as? [String: String],
let id = data["sender_id"],
let name = data["name"],
let text = data["text"],
!text.isEmpty
{
if let message = JSQMessage(senderId: id, displayName: name, text: text)
{
self?.messages.append(message)
self?.finishReceivingMessage()
}
}
})
// Do any additional setup after loading the view.
}
SIGABRT (signal abort) is typically from a referencing error in your storyboard. Did you ever change the name of a class or make a connection from a button of one view controller to another and then delete it? If you changed the name of a class you must make the sure the name in the code of the class matches that. If you deleted a button connection between view controllers, click on the controller itself and under the connections tab you must delete it.

Updating navigation in Viewcontrollers - Coredats

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.

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.

Remove/Hide UITabBarItem in Swift

I have looked really hard for this solution in Swift but am not coming up with one that works for me. I am trying to hide my "Admin" TabBarItem based on the permissions of the person that logs in to the app. I can disable it but it still shows up on the bar. I want to be able to show it for certain people and hide it for others. Also, when I print self.tabBarController?.viewControllers I get nil.
class TabBarMenuController: UITabBarController {
let ref = Firebase(url: "")
var position = ""
func getPosition() {
let userRef = ref.childByAppendingPath("users/\(ref.authData.uid)")
userRef.observeSingleEventOfType(.Value, withBlock: {snapshot in
if snapshot.value["position"] as! String != "Staff" {
self.position = snapshot.value["position"] as! String
}
})
}
override func viewWillAppear(animated: Bool) {
getPosition()
print(self.tabBarController?.viewControllers)
if position != "Staff" {
if let tabBarController = self.tabBarController {
let indexToRemove = 3
if indexToRemove < tabBarController.viewControllers?.count {
var viewControllers = tabBarController.viewControllers
viewControllers?.removeAtIndex(indexToRemove)
tabBarController.setViewControllers(viewControllers, animated: true)
}
}
}
}
Also, I keep reading that this is against Apple's intended use. Is that true still? Is there a better workflow to accomplish that type of functionality?
I would create a tab that opens up the user's account and have a button in the user VC tab that opens up a page for admins only. you can show and hide the button as needed using adminButton.hidden = true or adminButton.hidden = false.