How to passes data from ViewController to UITabBarController -> UINavigationController -> ViewController - ios

I am try to send data from ViewController to UITabBarController -> UINavigationController -> ViewController and try lots of way but fail to Passes data. After click in button i have asynchronous task and then need to load UITabBarController-ViewController with some data. I already almost try all example but fail to find working code.
func AftergettingAsynchronousData(forUserID userID: String?) {
guard let tabBarController = tabBarController else { return }
let navController = tabBarController.viewControllers?[0] as! UINavigationController
let exploreViewController = navController.topViewController as! ExploreViewController
exploreViewController.userID = userID
}
receiving data
class ExploreViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
public var userID: String?
override func viewDidLoad() {
super.viewDidLoad()
print("userID \(String(describing: userID))");
}
}

Try changing navController.topViewController to navController.rootViewController

Related

Nil when transferring data from TabbarController to ViewController

I can’t understand what the mistake is,
an ordinary user should register, provided that everything is successful, the current user will show on TabBarController, and if you want him to convert to print("\(currentUser)"), then everything is fine. this user's protocol
Here I output to the console, everything is fine
import UIKit
class MainTabBarViewController: UITabBarController {
var currentUser: MUser = MUser(username: "fdff",
usersurname: "dfdf",
phone: "dffd",
sex: "dfb",
avatarStringURL: "fgf",
id: "gf",
bithDate: "fggf")
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let messageVC = storyboard.instantiateViewController(identifier: "MessageVC") as! MessageVC
messageVC.currentUser = currentUser
print("\(currentUser)")
}
}
but you see, I pass it on and in this controller the nil issues
import UIKit
import FirebaseFirestore
class MessageVC: UITableViewController {
var chat = [MChat]()
var currentUser: MUser!
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(chat[indexPath.row])
print("\(currentUser)") // return nil
let chatsVC = ChatsViewController(user: currentUser, chat: chat[indexPath.row])
navigationController?.pushViewController(chatsVC, animated: true)
}
}
Can anyone explain what I'm doing wrong?
Solution
Try to instantiate the MessageVC from the storyboard and then pass the data to it from the TabBarController.
Hold the reference of MessageVC globally outside ViewDidLoad and use it for navigation.
Example
class MainTabBarViewController: UITabBarController {
var currentUser: MUser = MUser(username: "fdff",
usersurname: "dfdf",
phone: "dffd",
sex: "dfb",
avatarStringURL: "fgf",
id: "gf",
bithDate: "fggf")
// Declare Message VC instance
var messageVC : MessageVC!
override func viewDidLoad() {
super.viewDidLoad()
// Instantiate from Storyboard
let storyboard = UIStoryboard(name: "Main", bundle: nil)
messageVC = storyboard.instantiateViewController(identifier: "VCIdentifier")
messageVC.currentUser = currentUser
print("\(currentUser)")
}
}
Update: Access Child ViewController from TabBarController
func accessMessageVC() {
// Use your viewcontroller index.
let vc = self.viewControllers![2] as! MessageVC
messageVC.currentUser = currentUser
}
Call this method from TabBarController's ViewDidLoad

Protocols and delegation in swift 3 not sending values between viewcontrollers

I have two viewcontrollers(SignInViewcontroller.swift and ProfilePage.swift)
I want to pass the string from SignInViewcontroller to ProfilePage viewcontroller.
I created a protocol in SignInViewcontroller.And I delegate the method in ProfilePage controller.When I send the string through protocols I didn't receive that string in ProfilePage viewcontroller Where I am wrong.please help me to solve.
Here is my code:
SignInViewController.swift
protocol sendTokenDelegate: class {
func sendToken(login:String)
}
class SignInViewController: UIViewController {
weak var delegateToken:sendTokenDelegate?
func loginAzure(email: String, password: String) {
token = "abcdefgh"
self.delegateToken?.sendToken(login: token)
}
}
ProfilePage.swift
class ProfilePage: UIViewController, UITableViewDelegate, UITableViewDataSource, sendTokenDelegate {
override func viewDidLoad() {
let signInVC = SignInViewController()
signInVC.delegateToken = self
}
func sendToken(login: String) {
self.logInToken = login
print("Login Token in Profile Page is \(login)")
}
}
In that case, you need object that will store token from SignInViewController, until ProfilePage is requesting it.
class TokenStorage {
static let shared = TokenStorage()
public var token: String = ""
}
then you receive token call:
TokenStorage.shared.token = receivedToken
and in ProfilePage request it:
print(TokenStorage.shared.token)
If you are coming from SignUpViewController to ProfilePageViewController, you can pass the string values upon navigation after getting the singIn token you want from your logingAzure() I assume:
If you navigate using segues -> self.performSegue(withIdentifier: "signUpToProfile", sender: self)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "signUpToProfile" {
if let profileVC : ProfilePageViewController =
segue.destination as? ProfilePageViewController {
profileVC.loginToken = token
}
}
}
If you are using self.navigationController?.pushViewController
let storyboard = UIStoryboard(name: "Profile", bundle: Bundle.main)
if let profileVC = storyboard.instantiateViewController(withIdentifier:
"ProfilePageViewController") as? ProfilePageViewController {
profileVC.loginToken = token
}
EDIT
If you are not going to profilePage directly from SignUpViewController,
then just save the token in your Keychain OR UserDefaults.
Do this by creating a SessionManager singleton to handle tokens and other resources when logging in or signing up
Just use "prepare for segue"
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ProfileSegue" {
if let vc = segue.destination as? ProfilePage {
vc.token = self.token
}
}
}
When you are going to NextVC that doesn't exist in the navigation controller then you have to bind data with the instance of the class when you are pushing just like that:
let vc = UIStoryboard(name: "ProfilePage", bundle: nil).instantiateInitialViewController() as! ProfilePage
vc. logInToken = "abcdefgh"
self.navigationController?.pushViewController(vc, animated: true)
class ProfilePage: UIViewController {
var logInToken = "" // you will receive the token in this variable.
}
In you case, it seems like ProfilePage doesn't exist.
NOTE:- Delegate will use just opposite case when you want to pass the value from ProfilePage to SignInViewController.
OR
If your all API wants the token so you can declare at the class level or save it to UserDefauls:
1)
var logInToken = "" // your variable visible the entire application classes
class ProfilePage: UIViewController {
}
func loginAzure(email: String, password: String) {
logInToken = "abcdefgh" //Just assign and use it
}
2)
UserDefaults.standard.set("aasdfa", forKey: "token")
let token = UserDefaults.standard.value(forKey: "token"
Also, you are doing the bad coding you have to understand the OOP's
let signInVC = SignInViewController()
signInVC.delegateToken = self
This will reperensent the seprate instance in the memory and every
object has its own properties and behavior.
Try using:
protocol sendTokenDelegate: class {
func sendToken(login:String)
}
class SignInViewController: UIViewController {
weak var delegateToken:sendTokenDelegate?
func loginAzure(email: String, password: String) {
token = "abcdefgh"
if self.delegateToken != nil{
self.delegateToken?.sendToken(login: token)
}
}
}
class ProfilePage: UIViewController, UITableViewDelegate, UITableViewDataSource, sendTokenDelegate {
override func viewDidLoad() {
//get your instantiateViewController from storyboard
let signInVC = self.storyboard?.instantiateViewController(withIdentifier: "SignInViewControllerIdentifire") as! SignInViewController
signInVC.delegateToken = self
}
func sendToken(login: String) {
self.logInToken = login
print("Login Token in Profile Page is \(login)")
}
}

How to pass data to another controller on dismiss ViewController?

I want to pass my data from one ViewController to another VC on VC dismiss. Is it possible?
I've tried the next method and no success:
On button click:
self.dismiss(animated: true) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "EditViewController") as! EditViewController
controller.segueArray = [values]
}
when EditViewController appears again, my segueArray is nil there.
How can I pass my data from my ViewController to the EditViewController on dismiss?
The best way to pass data back to the previous view controller is through delegates... when going from ViewController A to B, pass view controller A as a delegate and on the viewWillDisappear method for ViewController B, call the delegate method in ViewController A.. Protocols would help define the delegate and the required methods to be implemented by previous VC. Here's a quick example:
Protocol for passing data:
protocol isAbleToReceiveData {
func pass(data: String) //data: string is an example parameter
}
Viewcontroller A:
class viewControllerA: UIViewController, isAbleToReceiveData {
func pass(data: String) { //conforms to protocol
// implement your own implementation
}
prepare(for: Segue) {
/** code for passing data **/
let vc2 = ViewCOntrollerB() /
vc2.delegate = self //sets the delegate in the new viewcontroller
//before displaying
present(vc2)
}
}
Dismissing viewcontroller:
class viewControllerB: UIViewController {
var delegate: isAbleToReceiveData
viewWillDisappear {
delegate.pass(data: "someData") //call the func in the previous vc
}
}
In the dismiss completion block, you create a new instance of the EditViewController. I assume that another EditViewController instance exists back in the navigation stack, you need to find that instance & set the segueArray to values.
That you can achieve by iterating through your navigation stack's viewcontrollers like:
viewController.navigationController?.viewControllers.forEach({ (vc) in
if let editVC = vc as? EditViewController {
editVC.segueArray = ....
}
})
But I would recommend to use the delegate pattern, like:
protocol EditViewControllerDelegate: class {
func setSegueArray(segues: [UIStoryboardSegue])
}
In the viewcontroller (call it just ViewController) where the dismiss block is, declare a delegate property:
class ViewController: UIViewController {
weak var delegate: EditViewControllerDelegate?
....
}
Then on presenting the instance of (I assume from EditViewController) ViewController set the delegate like:
...
if let vc = presentingViewController as? ViewController {
vc.delegate = self
}
And conform the EditViewController to the delegate protocol like:
extension EditViewController: EditViewControllerDelegate {
func setSegueArray(segues: [UIStoryboardSegue]) {
// Do the data setting here eg. self.segues = segues
}
}
To detect when the back button is pressed on a view controller, I just use:
override func didMove(toParentViewController parent: UIViewController?) {
guard parent == nil else { return } // Back button pressed
... // Pass on the info as shown in you example
} // didMoveToParentViewController
A generic solution: (🔸 Swift 5.1 )
/**
* Returns a ViewController of a class Kind
* ## Examples:
* UIView.vc(vcKind: CustomViewController.self) // ref to an instance of CustomViewController
*/
public static func vc<T: UIViewController>(vcKind: T.Type? = nil) -> T? {
guard let appDelegate = UIApplication.shared.delegate, let window = appDelegate.window else { return nil }
if let vc = window?.rootViewController as? T {
return vc
} else if let vc = window?.rootViewController?.presentedViewController as? T {
return vc
} else if let vc = window?.rootViewController?.children {
return vc.lazy.compactMap { $0 as? T }.first
}
return nil
}

Open ViewController (which is a navigationController within a tabBarController) and run function

I want to allow the user to open an image up in my app from their email. I have set up functions in the app delegate as follows to navigate to my settingsTableViewController. The settingsTableViewController is a navigationController and that is a tabBarViewController.
func application(app: UIApplication, openURL url: NSURL,
options: [String : AnyObject]) -> Bool {
self.window!.visibleViewController
goToSecond()
return true
}
func goToSecond() {
let tabBarController: UITabBarController = (self.window?.rootViewController as? UITabBarController)!
tabBarController.selectedIndex = 3
}
The extension I used to make the above work was:
public extension UIWindow {
public var visibleViewController: UIViewController? {
return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
}
public static func getVisibleViewControllerFrom(vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
}
The above works until I add my code in to pass the url to the settingsTableViewController as follows:
let vc = self.window?.rootViewController as! SettingsTableViewController
vc.displayImage(url)
I get the following error:
Could not cast value of type '.TabBarController' (0x100161c00) to '.SettingsTableViewController'
Any suggestions?
Your error tells you exactly what the problem is. The RVC is actually a TabBarController, not your SettingsTableViewController. This means you need to access the TabBarController's viewController array until you arrive at your SettingsTableViewController

Swift - Proper usage of AnyObject

I am new to swift. I am using storyboard and according to logged in information, I am changing the UIViewController to load. Below is the snippet.
class func viewControllerWithName(name: String) -> UIViewController?
{
let storyboard = mainStoryboard()
let viewController: AnyObject! = storyboard.instantiateViewControllerWithIdentifier(name)
return viewController as? UIViewController
}
func setUpController {
let viewController: AnyObject!
if self.user() == "admin" {
viewController:AdminViewController = self.viewControllerWithName("admin") !as! AdminViewController
} else {
viewController:UserViewController = self.viewControllerWithName("user") !as! UserViewController
}
self.addChildViewController(viewController)
viewController.view.frame = container.frame
self.view.addSubview(viewController.view)
viewController.didMoveToParentViewController(self)
}
I tried many ways, but, I am unable to typecast viewController. I get error, saying
Cannot invoke 'addChildViewController' with an argument list of type '(AnyObject!)'
What is the right format to achieve this?
Why do you declare it as AnyObject when you know its going to be a view controller? declare it as UIViewController and the error you've mentioned should go away.
You don't need to do viewController:AdminViewController in the if-else section. You are anyways force typecasting the output of viewControllerWithName method.
In this case you are indicating that viewController is AnyObject type:
let viewController: AnyObject!
To pass a UIVIewController to addChildViewController you can try something like:
let viewController = self.viewControllerWithName("admin") as! AdminViewController
self.addChildViewController(viewController)
In swift you cant change a let or var type at runtime
Update your code and change AnyObject to UIViewController:
class func viewControllerWithName(name: String) -> UIViewController?
{
let storyboard = mainStoryboard()
let viewController = storyboard.instantiateViewControllerWithIdentifier(name) as? UIViewController
return viewController
}
func setUpController {
let viewController: UIViewController!
if self.user() == "admin" {
viewController = self.viewControllerWithName("admin")!
} else {
viewController = self.viewControllerWithName("user")!
}
self.addChildViewController(viewController)
viewController.view.frame = container.frame
self.view.addSubview(viewController.view)
viewController.didMoveToParentViewController(self)
}

Resources