how to change UITabBarItem's Title dynamically - swift

I have a TabBarController in my App, and the 2nd item should have title Login or Profile depending on whether the user is logged in or not.
in TabBarController:
import UIKit
class TabNavigationBarVC: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
self.delegate = self
if UserDefaults.standard.bool(forKey: "isUserLoggedIn") {
tabBar.items?[1].title = "Prof"
tabBar.items?[1].image = UIImage(named: "user_male")
} else {
tabBar.items?[1].title = "Log"
tabBar.items?[1].image = UIImage(named: "user_male")
}
}
}
and 2nd TabBarItem is connected with RouterVC:
import UIKit
class RouterViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
let profile = storyboard?.instantiateViewController(withIdentifier: "ComponentUserProfile") as? UserProfileViewController
let login = storyboard?.instantiateViewController(withIdentifier: "ComponentLogin") as? LoginViewController
if UserDefaults.standard.bool(forKey: "isUserLoggedIn") {
//profile?.tabBarItem.title = "prr" - no effect
//profile?.tabBarItem = UITabBarItem(title: "PROFILE", image: UIImage(named: "user_male"), tag: 0) - no effect
viewControllers = [profile] as! [UIViewController]
} else {
//login?.tabBarItem.title = "logg"- no effect
//login?.tabBarItem = UITabBarItem(title: "LOGIN", image: UIImage(named: "user_male"), tag: 0)- no effect
viewControllers = [login] as! [UIViewController]
}
}
}
all works correctly but only when the user launches the app. Later, after the user has logged out, I would like the 2nd title in the tabBar to change from Profile to login, or - when the user is successfully logged in - from Login to Profile. I tried something like commented lines of code in RouterVC, but nothing changed.
And in LoginVC this lines not working too:
override func viewWillAppear(_ animated: Bool) {
self.tabBarItem.title = "loggggg"
}
How can I make this change dynamically?
Maybe I should write extension for TabBarController to track if UserDefaults.standard.bool(forKey: "isUserLoggedIn") is changed and display correct title of the tabbarItem?..
p.s.navigation between views, login/logout works good, the point is only in title
p.p.s - find the answer how get access to the title from view - add self.tabBarController?.tabBar.items?[1].title = "profile", but still looking for some common decision, like tracking UserDefaults key..

you can update the selected Item title using
self.tabBarController?.tabBar.selectedItem?.title = "demo"

Related

Open specific UITabBarController / ViewController from SceneDelegate.swift

I have a Today Extension which has a UITableView.
When the user taps one of the items in the UITableView, it opens the containing app with a URL Scheme and passes some data so that the containing app can open a specific view for the item the user tapped.
The problem is, from SceneDelegate.swift I can handle the URL Scheme but it seems I can't make a specific UITabBarController show and open the detail view for the item tapped by the user.
Please note that I'm not trying to create them; they're already created from the storyboard and working. I just want it to 'Navigate' automatically as if the user had tapped the tableview item.
The below code is what I've tried:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
// This part, I'm handling URL Scheme which is working fine
let url = URLContexts.first!.url
let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)
let items = urlComponents?.queryItems
var deepLinkArray: [URLQueryItem] = []
deepLinkArray = items!
// From here I will use datas I got from URL
if let statusValue = deepLinkArray[0].value, let indexPathSection = deepLinkArray[1].value, let indexPathRow = deepLinkArray[2].value {
todoStatusValueURL = Int(statusValue)!
indexPathSectionURL = Int(indexPathSection)!
indexPathRowURL = Int(indexPathRow)!
// Here I want to show one of UITabBarController
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let snoozedVC = storyboard.instantiateViewController(withIdentifier: "SnoozedViewController") as! SnoozedViewController
snoozedVC.self.tabBarController?.selectedIndex = todoStatusValueURL! // And this is not showing what I want
// Additionally I want to show ItemDetailViewController as if user tapped the item in the tableview, and below code is also not working
let detailVC = storyboard.instantiateViewController(withIdentifier: "DetailVC") as! DetailVC
snoozedVC.navigationController?.pushViewController(detailVC, animated: true)
}
First check if snoozedVC and TabBarController for that specific snoozedVC exist.. it will give you idea whats wrong with your code ... then move forward according to its result to check which part of your code is not working
if let snoozedVC = storyboard.instantiateViewController(withIdentifier: "SnoozedViewController") as? SnoozedViewController {
if let tabBarController = snoozedVC.tabBarController {
tabBarController.selectedIndex = todoStatusValueURL!
} else {
print("tab controller not found")
}
} else {
print("SnoozedViewController not found")
}
}

How to write func..(for sequels: UIStoryboardSeque, sender: Any?) programmatically

I have these codes when I use storyboard:
override func prepare (for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == ProfilePhotoViewController.identifier {
guard let username = usernameTextField.text, let email = emailTextField.text, let password = passwordTextField.text else { return }
let profilePhotoVC = segue.destination as! ProfilePhotoViewController
profilePhotoVC.email = email
profilePhotoVC.username = username
profilePhotoVC.password = password
}
}
How do I write these code if I don't use storyboard and write them programmatically in order to pass the info from current controller to ProfilePhotoViewController?
EDIT:
After doing research I understand that doing this programmatically is through delegate. However, I don't know how I can complete the code:
Need to pass username, email and password from SignUpController ---> profilePhotoViewController
in SignUpController:
protocol SignUpControllerDelegate {
func handleSignUp(//what should I write here?)
}
var delegate: SignUpControllerDelegate?
#objc func handleSignUp() {
//...other code..//
delegate?.handleSignUp(//??)
}
In ProfileViewController:
what should I write to receive the username, email and password info from SignUpController?
We can create a view controller with/without a NIB file.
With NIB, you use init(nibName: String?, bundle: Bundle?) method. In this case, view will be defined using Interface Builder.
// bundle = nil denote the main bundle
let viewController = MyViewController(nibName:"MyViewController", bundle: nil)
Also you can define a custom UIViewController subclass without a NIB file and implement loadView() method.
override func loadView() {
self.view = UIView(...)
}
After that, we need to add the view into the view hierarchy.
self.view.addSubview(viewController.view);
// or
self.present(viewController, animated: false, completion: nil)
// or if we have UINavigationController
self.navigationController?.pushViewController(viewController, animated: false)
Try like this
guard let username = usernameTextField.text, let email = emailTextField.text, let password = passwordTextField.text else { return }
let profileVC = ProfilePhotoViewController()
profileVC.email = email

Change ViewController from Tabbar

I have a UITabBarController with 3 tabs and a SettingsVC where you can choose the appearance of MainVC (There are 2 different ViewControllers which based on what user prefers should be shown as the MainVC).
I made the application and it works but it's really buggy, cause I was just pushing the ViewControllers on top of each other, and in some certain conditions you see bugs while switching tabs.
Main.storyboard
Here is some part of my code(after trying a lot of stuff) which changes the VC's based on UserDefaults
CheckSettingsStatus.swift
//
// Check for Double Column Settings and navigate to Double Column VC
//
func checkMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 1{
//let appDel = UIApplication.shared.delegate as! AppDelegate
//appDel.goToDoubleMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "DoubleColumnMainVC") as! DoubleColumnMainVC
self.navigationController?.viewControllers[0] = mainVC
//navigationController?.pushViewController(mainVC, animated: false)
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
self.navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
//
// same as function above but goes to MainVC
//
func checkDoubleColumnMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 0{
// let appDel = UIApplication.shared.delegate as! AppDelegate
// appDel.goToMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "MainVC") as! MainVC
self.navigationController?.viewControllers[0] = mainVC
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
With this in ViewWillAppear of both this Controllers I call these functions to switch accordingly.
Any ideas what I'm doing wrong? Can I push controllers from UITabBarController and not NavigationController? something like:
tabBarController.pushViewController(mainVC, animated: false)
instead of:
navigationController.pushViewController(mainVC, animated: false)
Thank you in advance
UPDATE:
In my best case when everything works nicely, on first launch of the app (to the second mainVC) the Buttons in NavBar doesn't work.
EDIT:
I just realised that the NavigationBar that I'm seeing above my SecondMainVC is the NavigationBar from MainVC, that is why the buttons are not working. how is that possible?
I faced same issue while trying to develop a tab bar controller in storyboard , for this reason I found that the best way is to implement it by code.
so you can make your code look like this :
1) create a a sub class of UITabController and let it conform to UITabBarControllerDelegate
class TabBarVC: UITabBarController , UITabBarControllerDelegate {
2)then write this method in your class to initialize the tabs in your project:
private func settingUpTabBarAndInitializingViewControllers(){
//self delagting
self.delegate = self
//home tab
let homeStoryboard = UIStoryboard(name: Constants.StoryBoards.homeStoryboard, bundle: nil)
let homeVC = homeStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.homeScreen) as! HomeVC
// shop tab
let shopStoybaord = UIStoryboard(name: Constants.StoryBoards.shopStoryboard, bundle: nil)
let shopVC = shopStoybaord.instantiateViewController(withIdentifier: Constants.ControllersIDs.shopScreen) as! ShopVC
//offers tab
let offersStoryboard = UIStoryboard(name: Constants.StoryBoards.offersStoryboard, bundle: nil)
let offersVC = offersStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.offersScreen) as! OffersVC
//more tab
let moreStoryboard = UIStoryboard(name: Constants.StoryBoards.MoreScreenStoryboard, bundle: nil)
let moreVC = moreStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.moreVCScreen) as! MoreOptionsTVC
//setting VCs
self.viewControllers = [homeVC , shopVC , offersVC , moreVC]
//setting buttons
//buttons images
let homeImage = UIImage(named: "TabHome")
let shopImage = UIImage(named : "TabShop")
let offerImage = UIImage(named: "TabOffers")
let moreImage = UIImage(named: "TabMenu")
// buttons shakhsiyan
let homeButton = UITabBarItem(title: homeText, image: homeImage, selectedImage: homeTappedImage)
let shopButton = UITabBarItem(title: shopText, image: shopImage, selectedImage: shopTappedImage)
let offersButton = UITabBarItem(title: offersText, image: offerImage, selectedImage: offersTappedImage)
let moreButton = UITabBarItem(title: moreText, image: moreImage, selectedImage: moreTappedImage)
homeVC.tabBarItem = homeButton
shopVC.tabBarItem = shopButton
offersVC.tabBarItem = offersButton
moreVC.tabBarItem = moreButton
}
P.S. you can add tabs as much as you want and while scrolling in them you wont face any bug,
make sure to have only one navigation controller before your tab bar controller.
Check this hope it helps
Ok you can create a DEMO to see how thing works.After that you can implement it in your project.I know it may not look good to you but read it once and see image attached i am sure will get your answer.
1.I have created two UIViewControllers with the UITabBarController.
2.I have created class ItemFirstVC,ItemTwoVC and assigned to UIViewControllers in storyboard
3.Each controller is embedded in UINavigationController
4.Create a custom class of UITabBarController
5.Assign it to UITabBarController in storyboard
6.Now Each UIViewController has a button named as Firs Button,Second Button
7.From each button i have connected Action Segue to one more controller as you can see in image.
8.Now i have created a third UIViewController which is separate from UITabBarController.
9.Now we will load that Third UIViewController in UITabBarController
10.Create one more class ItemThirdVC and assign it to third UIViewController.
11.It also has a button named as third button action segued to one more UIViewController
12.Now switch to custom class of UITabBarContoller
// variable required to see work in action
var count = 0
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// remember to assign delegate
delegate = self
}
// this delegate indicates whether this tab should be selected or not
extension CustomTabBarController : UITabBarControllerDelegate{
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// switch to UIViewController tabs multiple times
count = count + 1
if(count == 5){
// when count reaches 5 a new controller will load
let navigationController = viewController as? UINavigationController
guard navigationController != nil else {return true}
let viewController = storyboard?.instantiateViewController(withIdentifier: "ItemThreeVC")
guard let vc = viewController else {return true}
navigationController?.setViewControllers([vc], animated: false)
}
return true
}
}

setup navigation controller View Controller as a CNContactViewController black screen

I have a collection view with some cells representing a contact (their data has a phone number and name) and I am trying to add the contact to the iPhone contacts. I have created a segue from a button called "add contact" that is inside the CollectionViewCell to a navigation controller, and set its identifier as "ADD_CONTACT".
In the storyboard, my segue has a navigation controller with no root view controller.
in prepareToSegue of the view controller that delegates my UICollectionView I wrote this code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == ADD_CONTACT {
let dest = segue.destination as! UINavigationController
if let cell = sender as? SBInstructionCell {
if cell.isContact {
let newContact = CNMutableContact()
if let phone = cell.instructionBean?.contactAttachment?.phoneNumber{
newContact.phoneNumbers.append(CNLabeledValue(label: "home", value: CNPhoneNumber(stringValue: phone)))
}
if let name = cell.instructionBean?.contactAttachment?.contactName {
newContact.givenName.append(name)
}
let contactVC = CNContactViewController(forNewContact: newContact)
contactVC.contactStore = CNContactStore()
contactVC.delegate = self
dest.setViewControllers([contactVC], animated: false)
}
}
}
}
this results with a black screen.
How can this be fixed? I want to see the CNContactViewController
Eventually I solved this in a different approach using Closures.
In my UICollectionViewCell
I added this var:
var closureForContact: (()->())? = nil
Now on my button's action in the same cell I have this func:
#IBAction func addContactTapped(_ sender: UIButton) {
if closureForContact != nil{
closureForContact!()
}
}
Which calls the function.
In my CollectionView in cell for item at index path, I set the closure like this:
cell.closureForContact = {
if cell.isContact {
let newContact = CNMutableContact()
if let phone = cell.instructionBean?.contactAttachment?.phoneNumber{
newContact.phoneNumbers.append(CNLabeledValue(label: "home", value: CNPhoneNumber(stringValue: phone)))
}
if let name = cell.instructionBean?.contactAttachment?.contactName {
newContact.givenName.append(name)
}
let contactVC = CNContactViewController(forNewContact: newContact)
contactVC.contactStore = CNContactStore()
contactVC.delegate = self
contactVC.allowsEditing = true
contactVC.allowsActions = true
if let nav = self.navigationController {
nav.navigationBar.isTranslucent = false
nav.pushViewController(contactVC, animated: true)
}
}
}
This worked perfectly. I learned that for navigating from a cell, it is best to use closures.

JSQMessages Are Being Shown Under Navigation Bar From Show Segue

I have a ChatViewController that extends JSQMessagesViewController.
final class ChatViewController: JSQMessagesViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
title = "Messages"
collectionView?.collectionViewLayout.incomingAvatarViewSize = .zero
collectionView?.collectionViewLayout.outgoingAvatarViewSize = .zero
automaticallyScrollsToMostRecentMessage = true
collectionView?.reloadData()
collectionView?.layoutIfNeeded()
collectionView?.collectionViewLayout.springinessEnabled = true
// REMOVE the attachment button for now
self.inputToolbar.contentView?.leftBarButtonItem = nil
self.inputToolbar.contentView?.textView?.placeHolder = "Compose a message to your driver"
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
observeMessages()
}
private func observeMessages() {
messageRef = channelRef!.child("messages")
let messageQuery = messageRef.queryLimited(toLast:25)
newMessageRefHandle = messageQuery.observe(.childAdded, with: { (snapshot) -> Void in
let messageData = snapshot.value as! Dictionary<String, String>
if let id = messageData["senderId"] as String!, let name = messageData["senderName"] as String!, let text = messageData["text"] as String!, text.characters.count > 0 {
self.addMessage(withId: id, name: name, text: text)
self.finishReceivingMessage()
} else {
print("Error! Could not decode message data")
}
})
}
}
This view controller is called by a push segue that has a navigation controller. Within my storyboard, I have a simple segue from a bar item button that pushes to a View Controller with ChatViewController set as the custom class. Within the storyboard, the navigation bar is inferred (shown on top).
However, when I go into ChatViewController, the messages are shown under the navigation bar. Once I click into the message text box, the messages are correctly positioned. How do I fix this so the user does not have to click into the text box? Is there a command I'm missing that correctly lays out the new messages?
This issue has been addressed here and here, actually. You can simply set the top inset to account for the bar's height. Jesse Squires actually made a special member for such cases called topContentAdditionalInset, which you can use like this:
override func viewDidLoad() {
super.viewDidLoad()
self.topContentAdditionalInset = myBarsHeight // in CGFloat
}