Can't change an instance of view controller's property - ios

I have this button in my first view controller, when you press it, it
triggers 2 action:
a function iterateItems
a segue which animates to ResultViewController
I need to set dataSource of ResultViewController to bestComposition in order to complete its view, and the value of this property can only be obtained after iterateItems is finished, I put this code in iterateItems:
print(bestComposition.count)
let resultVC = ResultViewController()
resultVC.dataSource = bestComposition
However, in the viewDidAppear method of ResultViewController, value of dataSource is not changed, here's the ResultViewController:
var dataSource = [WeaponItems]()
override func viewDidAppear(_ animated: Bool) {
print(dataSource.count)
...
}
The value of dataSource is 3 in ViewController, but is always 0 in ResultViewController, why doesn't it change?
Edit (Code for prepare for segue):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondVC = segue.destination as! ResultViewController
secondVC.transitioningDelegate = self
secondVC.modalPresentationStyle = .custom
}

resultVC is AN instance of ResultViewController, newly created by this line:
let resultVC = ResultViewController()
It's not THE instance that is created automatically as part of the segue. resultVC is never displayed, so it's viewDidAppear never runs, and so the print statement is never executed. Meanwhile, the instance created by the segue is presented, so it's viewDidAppear does run - but the value for its dataSource is unchanged. Try:
let resultVC = segue.destinationViewController as! ResultViewController

Try this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondVC = segue.destination as! ResultViewController
secondVC.transitioningDelegate = self
secondVC.modalPresentationStyle = .custom
secondVC.dataSource = bestComposition
}

Related

How to pass data from ViewController to a ViewController in a tab bar iOS?

I'm trying to pass data from my main ViewController to another ViewController in a Tab Bar.
I have tried using the following code , and got an error Could not cast value of type 'Test.FirstViewController' to 'Test.ViewController'
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
let tab1Controller = self.tabBarController?.viewControllers?.first as! ViewController
print(tab1Controller.test)
}
I just used the following code which just worked fine for me on Xcode 9 with swift 4.0. The following method is declared in the View Controller class which just presents the First View Controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "sendingToTabBar" {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabVC = storyboard.instantiateViewController(withIdentifier: "tabVC") as! UITabBarController
self.present(tabVC, animated: true, completion: {
let vc = tabVC.selectedViewController as! FirstViewController
vc.dataLBL.text = self.dataTF.text!
})
}
}
You can access the tab bar controllers in your ViewController prepare method and set your values.
Prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let barViewControllers = segue.destination as! UITabBarController
let destinationViewController = barViewControllers.viewControllers?[0] as! FirstViewController
destinationViewController.test = "Hello TabBar 1"
// access the second tab bar
let secondDes = barViewControllers.viewControllers?[1] as! SecondViewController
secondDes.test = "Hello TabBar 2"
}
Then in your tab bar ViewControllers declare variables, you want to set the values to.
#IBOutlet weak var label: UILabel!
var test: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
label.text = test
}
FirstViewController
SecondViewController
You could do a number of things, what I would do is to just make a global variable so that both view controllers can access it. Another option is to give each view controller a separate global variable, and when the view is loaded, the variable is set to self, then make a variable that can be set by the other view controller.
example:
var data:Any?
viewDidLoad() {
viewControllerA = self
}

Unwind method doesn't work when using default backward button

I have three view controllers like below
I wrote the unwind method in viewcontroller1, and try to receive some data from viewcontroller2 and viewcontroller3 when they unwind to viewcontroller1.
#IBAction func unwindToViewController1(segue: UIStoryboardSegue) {
print("1")
main_content = (segue.source as! MainContentViewController).main_content
}
#IBAction func unwindToViewController2(segue: UIStoryboardSegue) {
print("2")
detailed_content = (segue.source as! SupplementContentViewController).supplement
}
And set the exit unwind segue for both controller 2 and 3 already.
But why the unwindToViewController methods never get called correctly? I think they should be called when I click the button automatically created by the system.
I solve this problem by using delegate instead of unwind. Delegate pattenr is a more explicit way to solve this problem.
By creating a protocol called myDelegate
protocol myDelegate {
func updateMaincontent(main_content : String)
func updateSupplement(supplement: String)
}
And create a delegate instance inside the second view controller
var delegate: myDelegate?
Then in the first view controller, make the class extend myDelegate and set this delegate of second view controller to self in the func prepare(for segue: UIStoryboardSegue, sender: Any?) method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? MainContentViewController {
destinationViewController.main_content = self.main_content
destinationViewController.delegate = self
}
else if let destinationViewController = segue.destination as? SupplementContentViewController {
destinationViewController.supplement = self.detailed_content
destinationViewController.delegate = self
}
}
Finally, go back to second view controller, and set the value of delegate to what you want in viewWillDisappear method.
func viewWillDisappear(_ animated: Bool) {
self.main_content = contentTextView.text
delegate?.updateMaincontent(main_content: contentTextView.text)
}

How to cast the first view of a uinavigationview controller

I have been following a firebase tutorial to create my own chat application, I am trying to access a chatviewcontroller from the uiviewcontroller containing a table view. The Viewcontroller is embedded in a navigation controller. How can I bring up the chatviewcontroller when a cell is selected?
Here is my code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
//retrieve the destination view controller
let navVc = segue.destination as! UINavigationController
let channelVc = navVc.viewControllers.chatViewController as! ViewController
// set the senderDisplayName in Viewcontroller to the email provided in the email field by the user
channelVc.senderDisplayName = userEmailTextField?.text
}
Here is the error msg
Assuming your ChatViewController is the root view controller of your navigation controller you should change your code to this:
let channelVc = navVc.viewControllers.first as! ChatViewController
navVC.viewControllers is an array of the UIViewControllers managed by that UINavigationController, so that array would not have a property called chatViewController unless you implemented something custom.
EDIT: To put it all together, and also to demonstrate how to safely unwrap these optionals, your prepareForSegue should look like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let navVc = segue.destination as? UINavigationController, let channelVc = navVc.viewControllers.first as? ChatViewController {
//Now you have a reference to your ChatVC, and you can set the DisplayName
channelVc.senderDisplayName = userEmailTextField?.text
}
}
You have to get the first view controller from the UINavigationController:
let channelVc = navVc.viewControllers.first as! ChatViewController

Getting error while linking two ViewControllers

I'm using a custom circular animation in my VC to segue to a new VC and it works fine and this is the code
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
let secondVC = segue.destination as! SecondViewController
secondVC.transitioningDelegate = self
secondVC.modalPresentationStyle = .custom
}
But when I use another button on the same VC to segue to some another VC, I get an error saying Could not cast value of type 'I_Need.ReminderTableViewController' (0x1020e6e98) to 'I_Need.SecondViewController (0x1020e6a08).
You need to do if let instead of let, because your other VC is not of type SecondViewController:
if let secondVC = segue.destination as? SecondViewController{
secondVC.transitioningDelegate = self
secondVC.modalPresentationStyle = .custom
}

Set data in `prepareForSegue` with navigation controller

I am developing an iOS application in Swift.
I want to send data from a view to an other one, using the prepareForSegue function.
However, my target view is preceded by a navigation controller, so it doesn't work. How can I set data on the VC contained within the navigation controller?
In prepareForSegue access the target navigation controller, and then its top:
let destinationNavigationController = segue.destination as! UINavigationController
let targetController = destinationNavigationController.topViewController
From the target controller you can access its view and pass data.
In old - now obsolete - versions of Swift and UIKit the code was slightly different:
let destinationNavigationController = segue.destinationViewController as UINavigationController
let targetController = destinationNavigationController.topViewController
Prepare the segue in the SendViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueShowNavigation" {
if let destVC = segue.destination as? UINavigationController,
let targetController = destVC.topViewController as? ReceiveViewController {
targetController.data = "hello from ReceiveVC !"
}
}
}
Edit the identifier segue to "showNavigationController"
In your ReceiveViewController add
this
var data : String = ""
override func viewDidLoad() {
super.viewDidLoad()
print("data from ReceiveViewController is \(data)")
}
Of course you can send any other type of data (int, Bool, JSON ...)
Complete answer using optional binding and Swift 3 & 4:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let navigationVC = segue.destination as? UINavigationController, let myViewController = navigationVC.topViewController as? MyViewControllerClass {
myViewController.yourProperty = myProperty
}
}
Here is the answer for Swift 3:
let svc = segue.destination as? UINavigationController
let controller: MyController = svc?.topViewController as! MyController
controller.myProperty = "Hi there"
A one liner in Swift 3:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination.childViewControllers[0] as? FooController {
vc.variable = localvariable
}
}
In Swift 5
If you must not only segue from a SourceViewController to a DestinationViewController embedded in a UINavigationController, but also to a new Storyboard also, then do the following...
Place a "Storyboard Reference" object from your Object Library next to your source ViewController in Interface Builder, and then drag a segue to it (from a button on the SourceViewController view, for instance). Name the segue identifier "ToOtherStoryboard", for example.
Go to NavigationViewController and give it a Storyboard ID using the Identity Inspector. "DestinationNavVC" would do.
Click the Storyboard Reference icon you created in step 1, and in its attribute inspector's 'Referenced ID' field, enter the Storyboard ID you wrote for the UINavigationController in step 2. This creates the segue from source to the DestinationViewController no matter what you write in source file of the source ViewController. This is because seguing to a NaviationController will automatically show the root ViewController (the first one) of the UINavigationController.
(OPTIONAL) If you need to attach data along with your segue and send it to properties within the DestinationViewController, you would write the following code inside a Prepare-For-Segue method in your SourceViewController file:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ToOtherStoryboard" {
let destinationNavVC = segue.destination as! UINavigationController
let destinationVC = destinationNavVC.topController as! DestinationViewController
destinationVC.name = nameTextField.text // for example
destinationVC.occupation = occupationTextField.text
}
}
You do not NEED to have a PrepareForSegue if you're simply trying to move from one ViewController to another, the methods above will work (w/o step 3)
In your IBAction Outlet method for your button you used to initiate the segue, you would write:
performSegue(withIdentifer: "ToOtherStoryboard", sender: self)
Set the identifier name in the segue arrow property in order to use in the the performeSegue.
Like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc: ProfileViewController = segue.destination as? ProfileViewController {
//do any setting to the next screen
}
}
and then:
performSegue(withIdentifier: "yourIdentifierOfViewProfile", sender: indexPath.row)
I hope it helps.
It's a good idea to skip the check for UINavigationController as there may be multiple segues that use a navigationController and so will go into that check for every segue that uses a navigationController. A better way is to check the first viewController of the children and cast it as the viewController you are looking for.
if let destVC = segue.destination.children.first as? MyViewController {
destVC.hideBottomBar = true
}

Resources