I am working on an iOS application that is built around a Tab View Controller. I have created a "Contacts" tab, where a user can find and select a contact from a list. When the user selects the contact, it takes the contact's name and passes it to a different tab. That function is being done like so:
func passName(name: String) {
let navTab = self.tabBarController!.viewControllers![2] as! UINavigationController
let homeTab = navTab.viewControllers[0] as! MainController
homeTab.passedName = name
tabBarController?.selectedIndex = 2
}
Everything works as it should so far (name is loaded into text field). My issue is that the value seems to keep coming back every time I change tabs and then go back to my Home tab. For example, if I select "John" from my contacts, it will take me to the Home Tab and put John's name in a textfield. Let's say I delete the last two letters of the name, so now it is "Jo". If I load a different tab and come back, the name field has been reset to "John". It's as if the value gets re-passed every time I open the Home Tab. Also, every time I load the Home Tab after passing a name, my console prints: "Name Passed: John", so it shows that this is being processed every single time the tab appears. Here is my code for processing the name:
var passedName: String!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//Checks if name was passed to controller
if let validName = passedName {
print("Name passed: \(validName)")
nameTextField.text = validName
}
}
Am I passing the data incorrectly? I was thinking it might be because I have the above code being called in the viewWillAppear method, but that doesn't make sense, as essentially the data is only being passed one time from the Contacts tab. Thanks!
The problem is that you're not actually passing the value back to the original view. Apple's recommendation for passing information between classes is to use the delegate pattern. This allows the modal view to call the delegate class's function, which changes the name local to the original view because that function is declared in the original view's viewController. You can read more about the pattern in this tutorial, but I've also included a brief example relevant to your use case below.
mainViewController:
class namesTableViewController: UITableViewController, editNameDetailsViewControllerDelegate {
var name : String
#IBAction func editButtonPressed(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "editPerson", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editPerson" { //Modal segue
let navController = segue.destination as! UINavigationController
let controller = navController.topViewController as! editNameViewController
controller.delegate = self
if let person = sender as? Person {
print("Sending person to edit")
controller.personToEdit = person
}
} else {
super.prepare(for: segue, sender: sender)
}
}
//Protocol function
func changeName(n: String, controller: UIViewController) {
name = n
dismiss(animated: true, completion: nil)
}
}
editNameViewController:
class editNameViewController: UIViewController {
#IBOutlet weak var personNameTextField: UITextField!
var personToEdit : Person?
weak var delegate : PersonTableViewController?
override func viewDidLoad() {
super.viewDidLoad()
if personToEdit != nil {
personNameTextField.text = personToEdit?.name
}
}
// Button Actions
#IBAction func saveButtonPressed(_ sender: UIBarButtonItem) {
delegate?.personDetailsView(n: personNameTextField.text, controller: self)
}
}
Finally, the protocol class :
protocol editNameDetailsViewControllerDelegate : class {
func personDetailsView(n: String, controller: UIViewController)
}
Hope this helps.
The problem is "passedName" variable doesn't changed its value every time you edit it in your UITextField. Keep in mind that every time you change tabs, the UIViewController will call viewWillAppear and viewDidAppear. So your UITextField will always show passedName value once you select other tab and return.
I suggest that every time you edit the textfield you should update passedName value.
Sorry for my bad english.
Related
I am trying to transfer my object from HockeyDetailVC to my FavouritesVC using a button but my object is nil when I reach my second VC FavouritesVC. Why is it like that when I set the variable in my firstVC with my func transferObj()?
HockeyDetailVC
var item: CurrentPlayers?
override func viewDidLoad() {
super.viewDidLoad()
gonnaLoadView()
tableV.bounces = false
tableV.alwaysBounceVertical = false
favButton.layer.cornerRadius = 10
print(item) *//prints my current players object*
}
func transferObj() {
let otherVC = FavouritesVC()
otherVC.currentFav = item
print(item). *//prints my current player object*
}
#IBAction func addToFav(_ sender: Any) {
transferObj()
print("Favourite button Pressed")
}
FavouritesVC
var currentFav: CurrentPlayers?
override func viewDidLoad() {
super.viewDidLoad()
if currentFav == nil {
//display nil
self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
print(favArr) *//prints empty array*
print(currentFav) *//nil*
} else {
favArr.append(currentFav!)
print(favArr)
}
}
As #Martin stated, let otherVC = FavouritesVC() creates a new instance of the controller, but it is not the instance that you will eventually display. So you are effectively setting the currentFav of a random FavouritesVC that will never actually be displayed, while the one you eventually do navigate to has it's currentFav property still unset.
To set the appropriate FavouritesVC instance, you need to access it in one of several ways (depending on how you present it). If it is through a segue, then you can reference it in the prepare(for segue: sender:) method. (When you create a Cocoa Touch Class file, the below method template is pre-populated. As it states, reference the new view controller using segue.destination.)
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
Alternatively, if you create and present the new view controller programmatically with something like
// 1.
let otherVC = storyboard?.instantiateViewController(withIdentifier: "yourFavouritesVCIdentifier")
// 2.
// 3.
self.show(otherVC, sender: self)
you can insert your otherVC.currentFav = item at line // 2..
I am making a quiz app and have three view controllers and I am also making use of a navigation controller. The controllers are: HomeController, LevelsController, PlayController.
The levels controller has 100 levels on it as buttons and all of them except level 1 are greyed out initially. As I complete levels, the other level buttons are unlocked and become clickable.
MY ISSUE:
This only works if I go back to the HomeController and press on the then navigate to the LevelsController again, it doesn't update instantly when I simply go back from the PlayController to the LevelsController.
This is my code in the levels controller:
import UIKit
class LevelsViewController: UIViewController {
var levels = [String]()
override func viewDidLoad() {
super.viewDidLoad()
for k in globalScore...99 {
// print("this is \(k)")
// print(levelButton)
levelButton[k].isEnabled = false
print("Have the levels updated??")
}
}
override func viewWillAppear(_ animated: Bool) {
for k in globalScore...99 {
levelButton[k].isEnabled = false
}
}
#IBOutlet var levelButton: [UIButton]!
#IBAction func levelSelect(_ sender: UIButton) {
self.level = sender.tag
performSegue(withIdentifier: "LevelToPlaySegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("my name is jacobson")
var vc2 = segue.destination as! PlayViewController
vc2.enteredLevel = self.level
}
}
My PlayController has a keyboard and an image that changes to a new image if the correct answer is input into a text field. I also have a score in the top right of the PlayController which is assigned to globalScore. as the global score increases, the levels page should have more levels available to the user when they go backwards from the PlayController to the LevelsController.
What you need to do is to change your logic and instead of using it on viewDidLoad, you should also add ViewDidAppear and ViewWillAppear, in order to call these method every time you open the viewController instead of doing it only the first time.
Jasc24 has answered this already but I'm writing exactly what worked for me.
I wrote this in my ViewDidLoad to make all levels except level 1 disabled:
for k in 1...99 {
levelButton[k].isEnabled = false
}
I wrote this after the ViewDidLoad to enable specific levels only:
override func viewWillAppear(_ animated: Bool) {
for k in 0...globalScore-1 {
levelButton[k].isEnabled = true
}
}
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 }
}
I am creating an application in which there are 6 view controller in storyboard. The thing is that data is shared between the default view controller and the first one ( say A and B) which i added. i am using the prepareforseque method for passing data. the problem started when i added two more view controller. lets say C and D i created two new swift files and changed the two view controller class name. i created a textbox and button in C and label in D. when i pressed the button, the value of the text field is not passing into the D view controller although i used the same methods and code which i used for A and B. do i have to do anything else when i want to pass data between two newly added view controller.
first viewcontroller in which when a button is pressed value 1 needed to be passed:
class PlaySelectMenu: UIViewController {
var value = Int()
#IBAction func twotofive(sender: AnyObject) {
value = 1
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextView : PlayGameView = segue.destinationViewController as! PlayGameView
nextView.x = value
}
}
the second view controller which receive the value and print it
import Foundation
import UIKit
class PlayGameView: UIViewController{
var x = Int()
override func viewDidLoad() {
super.viewDidLoad()
print(x)
}
}
here i have added both the view controller from the object library and not working with the default one which is present in storyboard by default. i dont know why these two viewcontroller are not working. please help.
Regards Dev
One solution would be to write the data out to NSUserDefaults and then read it back from NSUserDefaults in the other view controller. Probably not the proper or correct way to share data between two view controllers, but it's been a reliable work around for me.
Other than that, you'd need to share your code so that we can see what's occurring.
Can you post also the code in your controllers C & D. And also if you have copy/paste the code inside your first two controllers into the two others, are you sure that in your prepareForSegue method you have changed the name of the destination segue ?
Assuming you have created the segue in Storyboard:
All you need is to do is put all of needed updates in prepareForSegue because twotofive is called after prepareForSegue.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
value = 1
let nextView : PlayGameView = segue.destinationViewController as! PlayGameView
nextView.x = value
}
Since you have connected your segue from button click to view controller, when you press button segue is automatically called. Instead of connecting segue from button to VC, connect VC to VC. Then in button click method at the last add below line:
#IBAction func twotofive(sender: AnyObject) {
value = 1
self.performSegueWithIdentifier("<Name of the segue identifier>", sender: self)
}
This will call your prepareForSegue. If you are calling more then one VC using segue from a VC then you can use segue.identifier to check which VC was called as below
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if segue.identifier == "CVC" {
}
I'm working on an app that consists of forms, the structure of which goes as follows:
MainForm (named: CorrosionRate)
MainForm Continuation (with MainTable - named: CorrosionERMeasurement)
MainTable Form (with SubTable - named: CorrosionSampleTreatment)
SubTable Form
The idea, basically, is a table, within a table. Each table has an identifier, the "parent". MainTable's parent is MainForm, and SubTable's parent is MainTable.
We pass this between the forms via prepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "CorrosionAddERMeasurementSegue" {
let vc : CorrosionAddERMeasurementFormViewController = segue.destinationViewController as! CorrosionAddERMeasurementFormViewController
vc.selectedCorrosionRateID = self.selectedCorrosionRateID
}
}
For the first part, it works fine. I can pass MainForm's ID to MainTable Form, ensuring that no matter what MainTable's actual ID is, it will always belong to MainForm.
Here's where things get dicey:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "AddCorrosionSampleTreatmentSegue" {
let vc : CorrosionAddSampleTreatmentFormViewController = segue.destinationViewController as! CorrosionAddSampleTreatmentFormViewController
vc.selectedCorrosionErMeasurementID = self.selectedCorrosionErMeasurementID
}
}
When the next xib loads, I immediately print the value of selectedCorrosionErMeasurementID which returns nil.
This is the code for switching to the next form:
func addSampleButtonPressed() {
print("Add Button Pressed. Selected Corrosion Measurements: \(self.selectedCorrosionErMeasurementID)")
self.performSegueWithIdentifier("CorrosionAddSampleTreatmentSegue", sender: self)
}
And that does print out the correct parent ID value. Here's the opening statements of the SubTable Form:
class CorrosionAddSampleTreatmentFormViewController: BaseViewController, SampleTreatmentFormViewDelegate, UIPickerViewDataSource,UIPickerViewDelegate,UICollectionViewDataSource, UICollectionViewDelegate {
var corrosionAddSampleFormView : SurfaceThermalSamplingAddSamplingView!
var selectedCorrosionErMeasurementID : String!
override func viewDidLoad() {
print("Selected ID from FormCorrosionER is: \(self.selectedCorrosionErMeasurementID)")
super.viewDidLoad()
}
}
Nothing to suggest that the value is being erased.
Is there anything I need to check? Perhaps there's a limit to how deep I can go into segues? Any suggestions?
Assuming I understood correctly, I believe your segue identifiers are mis-matched
self.performSegueWithIdentifier("CorrosionAddSampleTreatmentSegue", sender: self)
vs
if segue.identifier == "AddCorrosionSampleTreatmentSegue" {
If the first line is supposed to lead to the second, the identifiers need to be identical.