How can I pass a textfield value from one ViewController to a second ViewController (MacOS Swift)? - swift

I'm working on a project and my ViewController file is starting to get very long. Hence, I want to define all my functions in a second ViewController so I can delete some code from my first ViewController. This works, except for when I try to refer to a value from a textField defined in my FirstViewController, it returns nil. I am new to MacOS development so I would greatly appreciate simple/specific feedback.
This is an example of my first ViewController (it initializes variables and uses functions):
class FirstViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var firstName: NSTextField!
let lastName = "Smith"
#IBAction func buttonPressed(_ sender: Any) {
SecondViewController().printUserName()
}
}
This is my second ViewController (it defines functions):
class SecondViewController: NSViewController, NSTextFieldDelegate {
func printUserName() {
print(FirstViewController().firstName!) // this returns nil :(
print(FirstViewController().lastName) // this returns "Smith" :)
}
}

If you use the UINavigationController to move to the second VC, you can simply define a second view and then access the variables in that view.
example, FirstViewController
class FirstViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var firstName: NSTextField!
let lastName = "Smith"
#IBAction func buttonPressed(_ sender: Any) {
guard let secondView = self.storyboard?.instantiateViewController(identifier: "SecondViewController") as? SecondViewController else {
return
}
self.navigationController?.pushViewController(secondView, animated: false)
secondView.textValue = firstName.text
}
}
SecondViewController
class SecondViewController: NSViewController, NSTextFieldDelegate {
var textValue: String?
override func viewDidLoad() {
printUserName()
}
func printUserName() {
if let text = textValue {
print(text)
}
}
}

Related

reloadData() from another viewController Swift

I have two viewControllers: the first one has a tableView in it and the second one has a textField with an action, if there is a specific text inserted in the textFiled, I want to call loadData1() function which has orderTable.reloadData() to reload the tableView from the logInviewController, but it returns nil when I call it.
tableViewController code :
import UIKit
import FirebaseFirestore
import Firebase
import FirebaseAuth
class orderTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet var orderTable: UITableView!
var db: Firestore!
var firstName = [String]()
var lastName = [String]()
override func viewDidLoad() {
super.viewDidLoad()
orderTable.register(UINib(nibName: "Order1TableViewCell", bundle: nil) , forCellReuseIdentifier: "orderCell")
}
func loadData1() {
Firestore.firestore().collection("hola").getDocuments() { [self]
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
for document in querySnapshot!.documents {
self.firstName.append(document.get("firstname") as? String ?? "")
self.lastName.append(document.get("lastname") as? String ?? "")
}
}
orderTable.reloadData() // from here i got Unexpectedly found nil while unwrapping an Optional value:
}
}
}
}
logInViewController code :
import UIKit
import Firebase
import FirebaseAuth
class logInViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var userNameField: UITextField!
#IBOutlet var passwordField: UITextField!
#IBOutlet var logInButton: UIButton!
var db: Firestore!
var order: orderTableViewController!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func textfieldDidChange(_ sender: Any) {
print(userNameField?.text ?? "")
if userNameField.text == "v#v.com" {
let i = orderTableViewController()
i.loadData1()
}
}
}
Where you have let i = orderTableViewController(), you are not referencing your existing table view controller, but rather are creating a new one, except this time it is not instantiated in conjunction with the storyboard scene, and thus all of your #IBOutlet references will be nil. Attempts to reference those #IBOutlet references will fail.
To fix this, you should pass a reference for the first view controller to the second one, using a protocol rather than an explicit class name, and then the second view controller can call a method in the first. Thus:
Create class protocol, e.g. LoginViewControllerDelegate:
protocol LoginViewControllerDelegate: class { }
Give that protocol one method requirement, loadData1:
protocol LoginViewControllerDelegate: class {
func loadData1()
}
Make your first view controller conform to that protocol:
extension OrderTableViewController: LoginViewControllerDelegate {
func loadData1() {
... your implementation here ...
}
}
Create a property in the second view controller, that LoginViewController, for this delegate-protocol reference, e.g.:
weak var delegate: LoginViewControllerDelegate?
When first view controller instantiates second, set this delegate property (e.g. if doing segues, it would be in prepareForSegue):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? LoginViewController {
destination.delegate = self
}
}
The second view controller then would call delegate?.loadData1() rather than i.loadData1().
If you do what I understand then you can do this. But you should use delegate or closure callback to do that.
#IBAction func textfieldDidChange(_ sender: Any) {
print(userNameField?.text ?? "")
if userNameField.text == "v#v.com" {
if let i = order {
i.loadData1()
}
}
}
}

How to execute func present in a ViewController from another?

I am a beginner in Swift, and I do not yet understand all the elements.
I am trying to execute a function present in a ViewController (ProjectTabBarController) from another ViewController (ProjectInfosViewController). I end up with an error when I execute the function from the second.
For the context, it is for a navigation button of 3 UIViewController belonging to a UITabBarViewController, itself embedded in a UINavigationController
Thank you in advance ! (Sorry for my bad English)
import UIKit
//MARK:-TAB CONTROLLER
class ProjectTabBarController: UITabBarController {
#IBOutlet weak var ui_saveButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func saveAction(_ sender: Any) {
// code
disableSaveButton() // ALL IT'S FINE HERE
}
func enableSaveButton() {
ui_saveButton.title = "Save"
ui_saveButton.isEnabled = true
}
func disableSaveButton() {
ui_saveButton.title = "Saved"
ui_saveButton.isEnabled = false
} }
//MARK:-PROJECT INFORMATIONS
class ProjectInfosViewController: UIViewController, UITextFieldDelegate {
let superController = ProjectTabBarController()
override func viewDidLoad() {
super.viewDidLoad()
}
func textFieldDidChangeSelection(_ textField: UITextField) {
superController.enableSaveButton() // BUT HERE, DOESN'T
} }
//MARK:-PROJECT FIXTURES
class ProjectFixturesViewController: UIViewController, UITableViewDataSource {
}
//MARK:-PROJECT CONTACT
class ProjectContactViewController: UIViewController {
}
This
let superController = ProjectTabBarController()
is a new vc that's not presented , if the vc is inside the tabController then do
let res = self.tabBarController as! ProjectTabBarController
res.......// call what you need

Is it possible to modify the properties of a subclass from a parent class in Swift 4?

Via a method or closure, perhaps?
I created a subclass view controller of my superclass/parent view controller and placed labels with placeholder text in that subclass view controller.
I want to set the labels' values to blank strings from the superclass/parent view controller, or, specifically, from an IBAction function that causes the subclass view controller to appear.
Here is the code, first from the parent class, then from the subclass...
'''
class ViewController: UIViewController {
#IBAction func leavingView(){
self.EntryViewController.entryDateLabel.text = ""
self.EntryViewController.entryLabel.text = ""
self.dismiss(animated: true, completion: nil)
}
'''
then from the subclass...
'''
class EntryViewController: ViewController {
#IBOutlet var entryDateLabel: UILabel!
#IBOutlet var entryLabel: UILabel!
}
'''
I have come up with 2 solutions to this problem, without having the parent view controller know about its subclass.
In the first example the parent sets properties on itself that the child listens to (via the didSet method, it then updates its view accordingly. However, this isn't ideal because the entryDate and entry string fields are useless on their own, almost redundant in the parent.
class ParentViewController: UIViewController {
var entryDate: String?
var entry: String?
#IBAction func leavingView(){
self.entryDate = ""
self.entry = ""
self.dismiss(animated: true, completion: nil)
}
}
class ChildViewController: ParentViewController {
#IBOutlet var entryDateLabel: UILabel!
#IBOutlet var entryLabel: UILabel!
override var entryDate: String? {
didSet {
guard isViewLoaded else {
return
}
entryDateLabel.text = entryDate
}
}
override var entry: String? {
didSet {
guard isViewLoaded else {
return
}
entryLabel.text = entry
}
}
}
In my opinion, the second solution is clearer and keeps implementation details more separate because you're using instructions or events to notify the child view controllers.
class ParentViewController: UIViewController {
#IBAction func leavingView(){
self.dismiss(animated: true, completion: didLeaveView)
}
func didLeaveView() { }
}
class ChildViewController: ParentViewController {
#IBOutlet var entryDateLabel: UILabel!
#IBOutlet var entryLabel: UILabel!
override func didLeaveView() {
entryDateLabel.text = ""
entryLabel.text = ""
}
}
Since your requirement is not that much clear I have created a demo for you and into that demo I have added child ContainerViewController into parent ViewController and from that parent view controller you can change UILabel text when you click on UIButton of parent ViewController and code will be for ViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnFromParentViewTapped(_ sender: Any) {
//Here get the child of your parent view controller
if let containerView = self.children[0] as? ContainerViewController {
containerView.lblContainer.text = ""
}
}
}
and ContainerViewController code will be:
class ContainerViewController: UIViewController {
#IBOutlet weak var lblContainer: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Don't need to add much here because you are accessing it from parent view.
And your result will be:
As you can see when I click on button which title says Change Container label text the label text from ContainerViewController set to empty string.
For more info check THIS demo project.

unexpectedly found nil while unwrapping an Optional value prepareForSegue

I am beginner in swift and working on one project where I am using collectionView. From collectionView, I want to transfer some values to details view but I am getting the above mentioned error. Values are not nil but somehow, it is giving this error while performing segue. Anybody help me, I am badly stuck here.
//In my CollectionView Controller.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "DetailsView")
{
if let vc:DetailsViewController = segue.destinationViewController as? DetailsViewController
{
vc.details.text = self.description
vc.line.text = self.subText
vc.startTime.text = self.formatted_time
}
}
}
//DetailsViewController
import UIKit
class DetailsViewController: UIViewController {
#IBOutlet var startTime: UILabel!
#IBOutlet var line: UILabel!
#IBOutlet var details: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
This basically means your IBOutlets are not yet initialised.
You should set strings and then in the viewDidLoad set you labels.
So to sum up:
Add string properties in your DetailsViewController
Set these string properties in your preparForsegue function
in the viewDidLoad of your DetailsViewController, set your labels
Your code should look like something like this :
//In my CollectionView Controller.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "DetailsView")
{
if let vc:DetailsViewController = segue.destinationViewController as? DetailsViewController
{
vc.detailsString = self.description
vc.lineString = self.subText
vc.startTimeString = self.formatted_time
}
}
}
//DetailsViewController
import UIKit
class DetailsViewController: UIViewController {
#IBOutlet var startTime: UILabel!
#IBOutlet var line: UILabel!
#IBOutlet var details: UILabel!
var startTimeString: String?
var lineString: String?
var detailsString: String?
override func viewDidLoad() {
super.viewDidLoad()
startTime.text = tmpStartTimeString
line.text = tmpLineString
details.text = tmpDetailsString
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Double check my code, I've wrote it very quickly ;)
The IBOutlets in a destination view controller aren't set until some time after prepareForSegue finishes
In DetailsViewController, add three instance variables:
var startTime: String?
var line: String?
var details: String?
Then in prepareForSegue, set those three values:
vc.details = self.description
vc.line = self.subText
vc.startTime = self.formatted_time
Then in viewDidLoad of DetailsViewController
detailsLabel.text = self.details
lineLabel.text = self.line
startTimeLabel.text = self.startTime
As the two answer above are point out when you create an instance of a UIViewController like for example in the prepareForSegue when you call segue.destinationViewController as? DetailsViewController this not mean that the #IBOutlet's are injected or initialized yet. The #IBOutlet's are initialized when the view is fully loaded, so you can do two of the following options:
Create variables in your UIViewController in which you can save the values after the init of the UIViewController in the prepareForSegue and then in the viewDidLoad() of the another UIViewController you set the values for the #IBOutlet's.
Another option is call the view (e.g let _ = vc.view) property when you create the instance of the UIViewController, in this way you can force the view to load load fully and you can set your #IBOutlet's from the prepareForSegue.
I hope this help you.

Pass data between ViewController and ContainerViewController

I'm working on an app, and need to pass data between view and containerView. I need to send data and receive data from both Views.
Let me explain better:
I can change the Label Master (Touch the Container Button) by protocol, but I can not change the Label Container (Touch the Master button). What happens is the Master connects with the container by a following. But do not have a follow Container linking to the Master.
I tried to add but segue to, but it worked.
The Master View Controller:
import UIKit
protocol MasterToContainer {
func changeLabel(text:String)
}
class Master: UIViewController, ContainerToMaster {
#IBOutlet var containerView: UIView!
var masterToContainer:MasterToContainer?
#IBOutlet var labelMaster: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "containerViewSegue" {
let view = segue.destinationViewController as? Container
view!.containerToMaster = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func button_Container(sender: AnyObject) {
masterToContainer?.changeLabel("Nice! It's work!")
}
func changeLabel(text: String) {
labelMaster.text = text
}
}
The Container View Controller:
import UIKit
protocol ContainerToMaster {
func changeLabel(text:String)
}
class Container: UIViewController, MasterToContainer {
var containerToMaster:ContainerToMaster?
#IBOutlet var labelContainer: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func button_Master(sender: AnyObject) {
containerToMaster?.changeLabel("Amazing! It's work!")
}
func changeLabel(text: String) {
labelContainer.text = text
}
}
Can someone help me?
All you need to do is keep a reference to Container in your master view controller.
That is, you should add an instance variable to Master that will hold a reference to the view controller, not just the view. You'll need to set it in prepareForSegue.
So the beginning of Master View Controller would look something like this:
class Master: UIViewController, ContainerToMaster {
#IBOutlet var containerView: UIView!
var containerViewController: Container?
#IBOutlet var labelMaster: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "containerViewSegue" {
containerViewController = segue.destinationViewController as? Container
containerViewController!.containerToMaster = self
}
}
And then in your button function, simply change the label using the variable you just added.
Example:
#IBAction func button_Container(sender: AnyObject) {
containerViewController?.changeLabel("Nice! It's work!")
}
This means you can get rid of your MasterToContainer protocol too.
I tested this code, so I know it works, but unfortunately I am an Objective-C dev, and know nothing about best practices in Swift. So I don't know if this is the best way to go about it, but it certainly works.
Edit:
Here's the exact code I've tested:
Master.swift:
import UIKit
class Master: UIViewController, ContainerToMaster {
#IBOutlet var containerView: UIView!
#IBOutlet var labelMaster: UILabel!
var containerViewController: Container?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "containerViewSegue" {
containerViewController = segue.destinationViewController as? Container
containerViewController!.containerToMaster = self
}
}
#IBAction func button_Container(sender: AnyObject) {
containerViewController?.changeLabel("Nice! It's work!")
}
func changeLabel(text: String) {
labelMaster.text = text
}
}
Container.swift:
import UIKit
protocol ContainerToMaster {
func changeLabel(text:String)
}
class Container: UIViewController {
#IBOutlet var labelContainer: UILabel!
var containerToMaster:ContainerToMaster?
#IBAction func button_Master(sender: AnyObject) {
containerToMaster?.changeLabel("Amazing! It's work!")
}
func changeLabel(text: String) {
labelContainer.text = text
}
}
I solved it with this code
To send data from ViewController -> ContainerViewController
Class ViewController : UIViewController {
func sendData(MyStringToSend : String) {
let CVC = childViewControllers.last as! ContainerViewController
CVC.ChangeLabel( MyStringToSend)
}
}
in your ContainerViewController
Class ContainerViewController : UIViewController {
#IBOutlet weak var myLabel: UILabel!
func ChangeLabel(labelToChange : String){
myLabel.text = labelToChange
}
}
To send data from ContainerViewController -> ViewController
Class ContainerViewController : UIViewController {
func sendDataToVc(myString : String) {
let Vc = parentViewController as! ViewController
Vc.dataFromContainer(myString)
}
}
and in ViewController
Class ViewController : UIViewController {
func dataFromContainer(containerData : String){
print(containerData)
}
}
I hope this will help someone.
you can use this extension to access the container child
extension UIViewController {
func getContainerChild<vc:UIViewController>(_ viewController : vc,_ hasNavigation : Bool = true) -> (vc) {
guard let vc = self.children[0] as? UINavigationController else {return viewController}
if hasNavigation {
guard let childVC = vc.children[0] as? PurchasedHistoryListVC else {
return viewController}
return childVC as! vc
} else {
return vc as! vc
}
}
}
so you can do some thing like this in your view Controller
let vc = self.getContainerChild(yourChildViewControllerClass())
vc.functionName()