Alexander Logan
Alexander Logan

Reputation: 31

Referencing IBOutlet in another View Controller

So, I have been having some major trouble figuring this out and I have searched extensively for a solution but I surprisingly could not find one. I am attempting to create a multiple page (5, to be exact) Sign-Up for users.

I'll start off by showing you the layout of page 1 and 5 (since solving that issue will solve the issue for page 2-4):

Sign Up Page #1

Sign Up Page #5

As you may see (from the page control dots), I am using a page view controller to allow users to scroll from page to page. What I am trying to accomplish is giving the user the ability to enter their sign-up information in pages 1-5 before submitting it all at once (which can be located on page 5).

Here is the current code I am using for page #1:

class SignUpInfoViewController: UIViewController {

   @IBOutlet weak var emailTextField: UITextField!
   @IBOutlet weak var passwordTextField: UITextField!

   override func viewDidLoad() {
      super.viewDidLoad()
      // Do any additional setup after loading the view.

   }

   override func didReceiveMemoryWarning() {
      super.didReceiveMemoryWarning()
      // Dispose of any resources that can be recreated.
   }
}

Here is the current code I am using for page #5:

class TermsOfUseViewController: UIViewController {

let minPasswordCharCount = 6

@IBAction func signUpAction(_ sender: Any) {

    let providedEmailAddress = SignUpInfoViewController().emailTextField.text!
    let providedPassword = SignUpInfoViewController().passwordTextField.text!
    let trimmedPassword = providedPassword.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)

    if !(validEmail(enteredEmail: providedEmailAddress) && validPassword(enteredPassword: trimmedPassword)) {
        invalidCredentialsAlert()
    }
    else {
        FIRAuth.auth()?.createUser(withEmail: providedEmailAddress, password: providedPassword) { user, error in
            if error == nil {
                FIRAuth.auth()!.signIn(withEmail: providedEmailAddress,
                                       password: providedPassword)
            }
            else {
                let alertController = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)

                let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
                alertController.addAction(defaultAction)

                self.present(alertController, animated: true, completion: nil)
            }
        }
    }
}

// Email is valid if it has a standard email format
func validEmail(enteredEmail: String) -> Bool {
    let emailFormat = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
    let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailFormat)

    return emailPredicate.evaluate(with: enteredEmail)
}

// Password is valid if it is not empty or greater than a specified number of characters
func validPassword(enteredPassword: String) -> Bool {
    if (enteredPassword != "" && enteredPassword.characters.count >= minPasswordCharCount) {
        return true
    }

    return false
}

In the TermsOfUseViewController class, I am attempting to use the emailTextField and passwordTextField outlets from the SignUpInfoViewController, but I am receiving the following error:

fatal error: unexpectedly found nil while unwrapping an Optional value

I debugged the error and saw that the emailTextField property from SignUpInfoViewController is nil and so force unwrapping it will cause the app to crash (Note: I have correctly connected the IBOutlets to the SignUpInfoViewController, so no issue there).

How can I safely transfer the usage of the IBOutlets from the SignUpInfoViewController class to the TermsOfUseViewController class without it crashing? In other words, how can I make it to where the IBOutlets are no longer nil when I reference them in the TermsOfUseViewController class?

Thank you!

Upvotes: 1

Views: 808

Answers (2)

Lamour
Lamour

Reputation: 3040

That is a perfect scenario for delegate pattern

protocol SignUpProtocol: class { 
   func didProvideUserData(username: String ,password: String)
}

In your signup class declare a delegate: public weak var delegate:SignUpProtocol?

I am assuming when the user has provided the require info, they need to press some button to go to the next step: Thus in that button you should raise the delegate

@IBAction func nextButton(sender:UIButton) {
   guard let username = usernameTextfield?.text, let password = passwordTextField?.text, else { fatalError("textfields were empty") }
     if delegate != nil { // this saying when someone is listening to me, I will expose any method associated to me 
        delegate?.didProvideUserData(username:username, password:password) // passing the username and password from textfield 
     }
}

if you don't have a button, then look at property observer, where you could have some property

var didFulfill:Bool? = nil {
    didSet {
          if didFulfill != nil && didFulfill == true {}
        // here you check if your textfields are sets then raise the delegate
  }
}

set this property didFulfill = when both textfields are not empty :)

Now in your Terms class, just subscribe to that delegate

class TermsOfUseViewController: UIViewController, SignUpProtocol {
     var signUpVc: SignUpInfoViewController?
     override func viewDidLoad() {
       super.viewDidLoad()
        signUpVc = SignUpInfoViewController()
        signUpVc?.delegate = self 
    }

    func didProvideUserData(username: String, password:String) {
      // there is your data 
    }
}

Upvotes: 1

Miknash
Miknash

Reputation: 7948

You have to take in account that you don't have all references for all UIPageViewControllers all the time. That being said, I would suggest either to keep object in UIPageViewController with updated information or using Singleton Pattern to use it to store info into it and later use it. UIPageViewController are being reused and you might have one before and one after and relying onto having them would be wrong.

You can use UIPageViewController as self.parentViewController or something like that.

Upvotes: 0

Related Questions