#IBOutlet property cannot have non-object type - swift

I am trying to pass a struct between view controllers, but I get the compiler error "#IBOutlet property cannot have non-object type". I have tried to add #objc but still get the error. How can I pass this data between view controllers? Why do I get this error and how do I correct it? Thanks.
import UIKit
struct DocObject: Codable {
let filename: String
let doclink: Int
}
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
// var nameText = ""
var obj = DocObject(filename: "filename", doclink: 123)
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func done(_ sender: Any) {
print ("In VC1 nameText ", obj)
performSegue(withIdentifier: "name", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! VCTwo2
vc.finalObj = obj
}
}
import UIKit
class VCTwo2: UIViewController {
#IBOutlet var finalObj: DocObject!
// var finalName = 0
override func viewDidLoad() {
super.viewDidLoad()
print ("In VC2 ", finalObj!)
}
}

You only define a property with #IBoutlet if the property is loaded from a xib or storyboard. Since you are passing the data structure from another view controller just define finalObj as a regular var.
class VCTwo2: UIViewController {
var finalObj: DocObject!
override func viewDidLoad() {
super.viewDidLoad()
}
}

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()
}
}
}
}

i cannot able to pass data between viewcontrollers via protocols

View controller A
class ViewController: UIViewController {
var delegate: server?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func ok(_ sender: Any) {
delegate?.datum(data: "sd")
performSegue(withIdentifier: "goingB", sender: self)
}
}
View controller B
protocol server {
func datum(data: String)
}
class ViewControllerB: UIViewController, server {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func datum(data: String) {
self.label.text = data
print(data)
}
}
I need to pass the data via view controllers but I cannot able to pass however I know we can pass data through protocols, but anyhow I end up with error when try to run the program
If you need to pass data from one view controller to another and you're using segue for presenting new view controller, you can just override prepare(for:sender:), there is no need to using delegates. Here you can get reference for controller which will be presented and you can assign its variable.
So, first create variable in second view controller and declare that if you assign it with new value, it changes text of your label
class ViewControllerB: UIViewController {
#IBOutlet weak var label: UILabel!
var variable: String? {
didSet {
label.text = variable
}
}
}
Now in first view controller override prepare(for:sender:) and if segue is segue which you've performed, downcast destination view controller and assign its variable
class ViewController: UIViewController {
#IBAction func ok(_ sender: Any) {
performSegue(withIdentifier: "goingB", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goingB" {
let destinationVC = segue.destination as! ViewControllerB
destinationVC.variable = "sd"
}
}
}
Anyway, if you want to use your code with delegate, you have to set delegate of first view controller as second view controller which will be presented. For this purpose you can also use prepare(for:sender:) where you can get reference for destination of segue and then you can call your method on delegate
class ViewController: UIViewController {
var delegate: server?
#IBAction func ok(_ sender: Any) {
performSegue(withIdentifier: "goingB", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goingB" {
let destinationVC = segue.destination as! ViewControllerB
delegate = destinationVC
delegate?.datum(data: "sd")
}
}
}
Notes:
Name protocol with big capital letter Server and we are talking about delegates, add delegate word: ServerDelegate
Constrain protocol for just for classes
Make then your delegate variable weak
protocol ServerDelegate: class {
func datum(data: String)
}
...
weak var delegate: ServerDelegate?
The simplest here is to to set the property directly in prepare.
However, if you want to use delegate, you can. Your problem is that you have mixed between A and B.
The way you wrote it, when you call delegate?.datum, delegate is not defined and we can't access datum.
What do you want to do ? Go from A to B, and when in B, update a label in B with data received from A.
Here just to show how to use (but clearly too complex compared with direct assignment).
protocol Server {
func datum() -> String
}
class ViewControllerB: UIViewController {
#IBOutlet weak var label: UILabel!
var delegate: Server?
override func viewDidLoad() {
super.viewDidLoad()
let data = delegate?.datum()
self.label.text = data
}
}
class ViewControllerA: UIViewController, Server {
override func viewDidLoad() {
super.viewDidLoad()
}
var data = "sd"
func datum() -> String {
return data
}
#IBAction func ok(_ sender: Any) {
performSegue(withIdentifier: "goingB", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? ViewControllerB {
destVC.delegate = self
}
}
}

How can I pass data from a parent view controller to an embedded view controller in Swift?

I have a view controller embedded in another VC.
I would like to get the value of a variable from the main VC inside the embedded one. Specifically, I would like to change the text of label2 based on the value of label1.
I tried with "prepareForSegue", but it seems it's not triggered for embedded view controllers. I tried to isolate the problem in a test project:
Code for main VC:
class MyViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
}
}
Code for embedded VC:
class EmbeddedVC: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Thanks for your help :)
A way to achiŠµve this is to get the child view controller instance in the parent's viewDidLoad. It appears that the parent's viewDidLoad: gets called after the child's viewDidLoad:, which means the label is already created in the child's view.
override func viewDidLoad() {
super.viewDidLoad()
if let childVC = self.childViewControllers.first as? ChildVC {
childVC.someLabel.text = "I'm here. Aye-aye."
}
}
First of all you can't set directly EmbeddedVC's lable2.text In prepareForSegue
because call sequence following below
MainVC's prepareForSeque this time EmbeddedVC's label2 is nil
EmbeddedVC's viewDidLoad called then label2 loaded
MainVC's viewDidLoad called then label1 loaded
so if you assign MainVC's label1.text to EmbeddedVC's label2.text in prepareForSeque
both label1 and label2 are nil so did not work
There are two way to solve this question
First Solution
MainViewController has EmbeddedVC and when MainVC's viewDidLoad called, assign label1.text to embeddedVC.label2.text
class MyViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
var embeddedVC: EmbeddedViewController? = nil
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
embeddedVC?.label2.text = label1.text
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let embeddedVC = segue.destination as? EmbeddedViewController {
self.embeddedVC = embeddedVC
}
}
}
class EmbeddedViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Second Solution, use protocol and get MainVC's label text when viewWillAppear or viewDidAppear (later viewDidLoad called)
protocol EmbeddedVCDelegate: class {
func labelText() -> String?
}
class MyViewController: UIViewController, EmbeddedVCDelegate {
#IBOutlet weak var label1: UILabel!
// MARK: EmbeddedVCDelegate
func labelText() -> String? {
return label1.text
}
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let embeddedVC = segue.destination as? EmbeddedViewController {
embeddedVC.delegate = self
}
}
}
class EmbeddedViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
weak var delegate: EmbeddedVCDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
override viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
label2.text = delegate?.labelText()
}
}
You should try to use prepareForSegue like this:
if segue.identifier == "identifier" {
guard let destinationViewController = segue.destination as? VC2 else { return }
destinationViewController.label2.text = mytext
}
Where the segue identifier you assign in storyboard

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()

Swift: Pass multiple values between views

I have a view that has two text fields and a button.
#IBOutlet var inputURL: UITextField!
#IBOutlet var inputName: UITextField!
#IBAction func submitUrlButton(sender: AnyObject) {
}
and a second view that has two variables:
var submittedURL = ""
var submittedName = ""
println("Name \(submittedName)")
println("URL \(submittedURL)")
In Swift How do I pass the values entered in the two text fields and assign them to those variables in the second view?
Thanks
EDIT FOR THETOM:
import UIKit
class ViewController: UIViewController {
#IBOutlet var inputURL: UITextField!
#IBAction func submitBtn(sender: AnyObject) {
performSegueWithIdentifier("submissionSegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
// Create a new variable to store the instance of the next view controller
let destinationVC = segue.destinationViewController as BrandsViewController
destinationVC.submittedURL.text = inputURL.text
}
}
You can use the method prepareForSegue.
In the first view (the one from which the segue is coming from) write the following code :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
// Create a new variable to store the instance of the next view controller
let destinationVC = segue.destinationViewController as CustomViewController
destinationVC.submittedURL = inputURL.text
destinationVC.submittedName = inputName.text
}
Here CustomViewController is the custom class of the UIViewController to which the segue is going to.
To perform the segue programmatically in your button #IBAction do that :
#IBAction func buttonWasClicked(sender: AnyObject) {
performSegueWithIdentifier("submissionSegue", sender: self)
}
Since your view controllers are linked with segue you can override the prepareForSegue method in first view controller and pass data by doing so
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "secondViewController") { // here secondViewController is your segue identifier
var secondViewController = segue.destinationViewController as SecondViewController // where SecondViewController is the name of your second view controller class
secondViewController.submittedURL = inputURL.text
secondViewController.submittedName = inputName.text
}
}
And to performSegue inside your button action use perfromSegueWithIdentifier method
#IBAction func submitUrlButton(sender: AnyObject) {
//replace identifier with your identifier from storyboard
self.performSegueWithIdentifier("secondViewController", sender: self)
}
The simplest way of accessing values globally not neccessary to pass with segue
First View controller
import UIKit
var submittedURL:NSString? // declare them here
var submittedName:NSString? // Now these two variables are accessible globally
class YourViewController : UIViewController
{
#IBOutlet var inputURL: UITextField!
#IBOutlet var inputName: UITextField!
#IBAction func submitUrlButton(sender: AnyObject) {
if inputURL.text == "" && inputName.text == ""
{
//Show an alert here etc
}
else {
self.submittedURL.text = inputURL.text
self.submittedName.text = inputName.text
}
}
}
SecondView Controller
import UIKit
class SecondviewController: UIViewController
{
//inside viewDidload
println(submittedURL)
println(submittedName)
}