Referencing IBOutlet in another View Controller - ios

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!

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

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.

Related

"unrecognized selector sent to instance" when selector is in other file

Here is the problematic ViewController class.
class ViewController: UIViewController {
#IBOutlet weak var photoSelectActionSheet: UIButton!
#IBOutlet weak var selectedImageView: UIImageView!
var imagePicker: UIImagePickerController!
var selectedImage: UIImage? = ni
var iCloud = ICloud() // see below for code of that file
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Must use this system to check each time the view appears.
// This is checked each time so if user goes into settings and come back in same session
// then this will be updated => viewDidLoad is cached and not reloaded each time.
NotificationCenter.default.addObserver(
self,
selector:#selector(ICloud.checkIfUserIsLoggedInIcloud), // PROBLEM IS HERE
name: UIApplication.didBecomeActiveNotification,
object: nil)
}
// more code not shown
}
Problem is in viewWillAppear: the notification center calls a selector that is in an other Model file ICloud.swift:
import UIKit
import CloudKit
class ICloud {
var isLoggedInIcloud: Bool? = nil
var userIcloudId: String? = nil
func getUserIcloudId() {
// Run this only if user is logged into icloud, and we don't have yet its iCloud id.
if isLoggedInIcloud ?? false && userIcloudId == nil {
CKContainer.default().fetchUserRecordID(completionHandler: { (recordId, error) in
if let id = recordId?.recordName {
print("userIcloudId set to: " + id)
self.userIcloudId = id
}
else if let error = error {
print(error.localizedDescription)
}
})
}
}
#objc func checkIfUserIsLoggedInIcloud() {
// Check if user is logged into iCloud
CKContainer.default().accountStatus { accountStatus, error in
if accountStatus == .available {
self.isLoggedInIcloud = true
print("isLoggedInIcloud set to true")
self.getUserIcloudId()
return
}
print("User is not logged into icloud")
}
}
}
I expected "ICloud.checkIfUserIsLoggedInIcloud" passed in selector to work. It doesn't:
2020-07-01 11:46:12.931419+0200 QDog[53348:3422745] -[QDog.ViewController checkIfUserIsLoggedInIcloud]: unrecognized selector sent to instance 0x7fcad5f0c1d0
2020-07-01 11:46:12.938567+0200 QDog[53348:3422745] *** Terminating app due to uncaught exception
If I add back the functions and variables from ICloud.swift directly into the ViewController file and pass to the selector "checkIfUserIsLoggedInIcloud" it WILL WORK properly.
I wanted to put all icloud code into a separate model file for separation of concerns and to make the code more clear.
Question is: why does 'ICloud.checkIfUserIsLoggedInIcloud' passed as selector doesn't work? And how to make this work with ICloud code in its own file, and not in ViewController file?
According to the documentation, the first parameter should be the observer, which in your case you're pointing to self.
In this case, self is your ViewController, which doesn't have the method checkIfUserIsLoggedInIcloud. In order to make it work, as an observer, you need to pass the property iCloud instead of self.

Xcode 7 + swift. "Use of local variable '_' before its declaration" Error

I get the above error when trying to create a function to check user inputs and store data. My project builds fine until I reach this function RegisterButtonTapped(). Does anyone have some structural or syntax changes that could get rid of this error?
#IBAction func RegisterButtonTapped(sender: AnyObject) {
let userEmail = userEmailTextField.text;
let userPassword = userEmailTextField.text;
let userRepeatPassword = userRepeatPasswordTextField.text;
// Check for empty fields
if(userEmail!.isEmpty || userPassword!.isEmpty || userRepeatPassword!.isEmpty){
displayAlertMessage("All fields are required");
return;
}
// Check if passwords match
if(userPassword != userRepeatPassword){
displayAlertMessage("Passwords do not match");
return;
}
// Store Data
NSUserDefaults.standardUserDefaults().setObject(userEmail, forKey: "userEmail");
NSUserDefaults.standardUserDefaults().setObject(userEmail, forKey: "userPassword");
NSUserDefaults.standardUserDefaults().synchronize();
// Display Alert message with confirmation
var myAlert = UIAlertController(title:"Alert",message:"Registration is successful, thank you.",preferredStyle: UIAlertControllerStyle.Alert);
func displayAlertMessage(userMessage:String){
var myAlert = UIAlertController(title:"Alert",message: userMessage, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title:"ok",style: UIAlertActionStyle.Default, handler:nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil);
} // END OF FUNCTION 'displayAlertMessage()'
} // END of FUNCTION 'RegisterButtonTapped()'
When you have a nested function, you have to declare it before you can call it. In this case, move the "displayAlertMessage(userMessage:String)" function above the "// Check for empty fields" comment, then it should compile.
You have declared displayAlertMessage after you are calling it, move the declaration of it near the top of RegisterButtonTapped() if you want to keep it as a nested function, otherwise move it out of RegisterButtonTapped().
Apart from that you have two variables both called myAlert, the first is useless, and you are saving userEmail as both the email and the password, also calling synchronize() is not required.
There are some details to get it running right. The displayAlertMessage was declared inside the register function after the calling and like that we get the warning. When you get success, you must call the function with the success message, and not declaring var myAlert like inside the alert function. And final detail: when getting the UITextFields values, you got email input field and set it to password value, so the validation will be wrong.
Here a sample of code that works great:
class ViewController: UIViewController {
#IBOutlet var userEmailTextField:UITextField!
#IBOutlet var userPassTextField:UITextField!
#IBOutlet var userRepeatPasswordTextField:UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func RegisterButtonTapped(sender:UIButton) {
let userEmail = userEmailTextField.text;
let userPassword = userPassTextField.text;
let userRepeatPassword = userRepeatPasswordTextField.text;
// Check for empty fields
if(userEmail!.isEmpty || userPassword!.isEmpty || userRepeatPassword!.isEmpty){
displayAlertMessage("All fields are required");
return;
}
// Check if passwords match
if(userPassword != userRepeatPassword){
displayAlertMessage("Passwords do not match");
return;
}
// Store Data
NSUserDefaults.standardUserDefaults().setObject(userEmail, forKey: "userEmail");
NSUserDefaults.standardUserDefaults().setObject(userEmail, forKey: "userPassword");
NSUserDefaults.standardUserDefaults().synchronize();
displayAlertMessage("Registration is successful, thank you.")
}
// Display Alert message with confirmation
func displayAlertMessage(userMessage:String){
let myAlert = UIAlertController(title:"Alert",message: userMessage, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title:"ok",style: UIAlertActionStyle.Default, handler:nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil);
}
}

On Tap of UILabel (gesture recogniser) finding nil in tableview prototype cell for one cell and its working fine for another two cells

I am trying to implement UITapGestureRecognizer, Idea is that on tap of label I will get a pop up displaying the number to make call and it should come up with pop up alert saying call or cancel!!
Code I've written worked for me in 3 to 4 places but I am stuck at one point
In this screen I have a tableview with prototype cells grouped type here, please check this Image:
Link For Image
Third Cell
Now If I am Tapping 065668982 I have canOpenURL: failed for URL: "telprompt://065668982" - error: "This app is not allowed to query for scheme telprompt" which actually works on iPhone not on simulator and it pulls to call which is working fine.
Second Cell
If I am Tapping 065454858 I have canOpenURL: failed for URL: "telprompt://065668982" - error: "This app is not allowed to query for scheme telprompt" which actually works on iPhone not on simulator and it pulls to call which is working fine.
first Cell
But for first one it never works and end up with fatal error: unexpectedly found nil while unwrapping an Optional value
NOTE : I am Getting phone Number from an API and append the data in view controller to UITableViewCell.
I Hope I make sense, Thanks in advance for any help also if I am not clear please comment below
Here is my code:
import UIKit
import Foundation
class XyzTableViewCell: UITableViewCell
{
#IBOutlet weak var phoneNumber: UILabel!
var touchContact : String = ""
var myCell: MyCellData! {
didSet {
self.updateUI()
}
}
func updateUI()
{
touchContact = vicarCell.phone_no
//Tap Gesture
tapGestureAddonView()
}
//MARK:- Tap to Call and Open Email
func tapGestureAddonView(){
let contacttap = UITapGestureRecognizer(target: self, action:("contactTapped"))
contacttap.numberOfTapsRequired = 1
phoneNumber!.userInteractionEnabled = true
phoneNumber!.addGestureRecognizer(contacttap)
}
func contactTapped() {
// do something cool here
print("contactTapped")
print(touchContact)
dispatch_async(dispatch_get_main_queue())
{
if UIApplication.sharedApplication().canOpenURL(NSURL(string: "telprompt://\(self.touchContact)")!){
UIApplication.sharedApplication().openURL(NSURL(string: "telprompt://\(self.touchContact)")!)
}
else{
//showAlert("Info",message: "Your device could not called" ,owner: self)
}
}
}
The issues: 1) you should add gesture only once 2) you should check NSURL for nil. Let me assume that you use storyboard and improve your code a bit
class XyzTableViewCell: UITableViewCell
{
#IBOutlet weak var phoneNumber: UILabel!
var touchContact : String = ""
var myCell: MyCellData! {didSet {self.updateUI()}}
func updateUI() {
touchContact = myCell.phone_no // did you mean myCell instead vicarCell?
}
override func awakeFromNib() {
super.awakeFromNib()
tapGestureAddonView()
}
func tapGestureAddonView(){
let contacttap = UITapGestureRecognizer(target: self, action: #selector(contactTapped))
contacttap.numberOfTapsRequired = 1
phoneNumber!.userInteractionEnabled = true
phoneNumber!.addGestureRecognizer(contacttap)
}
func contactTapped() {
print("contactTapped")
print(touchContact)
if touchContact.isEmpty {return}
guard let url = NSURL(string: "telprompt://\(touchContact)") else {
print("url string invalid")
return
}
dispatch_async(dispatch_get_main_queue())
{
if UIApplication.sharedApplication().canOpenURL(url){
UIApplication.sharedApplication().openURL(url)
} else{
//showAlert("Info",message: "Your device could not called" ,owner: self)
}
}
}
}

Swift Expected Expression in list of Expression Error

Im making a basic registration form, and I'm getting multiple "Expected Expression in list of Expression Errors," as well as a couple expected separator errors. Ive tried fixing the errors myself, but I'm new at Swift, and wasn't able to fix it. Any help is appreciated.
import UIKit
class RegisterPageViewController: UIViewController {
#IBOutlet weak var userFullNameTextField: UITextField!
#IBOutlet weak var userEmailTextField: UITextField!
#IBOutlet weak var userPasswordTextField: UITextField!
#IBOutlet weak var userRepeatPasswordTextField: 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.
}
#IBAction func RegisterButtonTapped(sender: AnyObject) {
let userFullName = userFullNameTextField.text
let userEmail = userEmailTextField.text
let userPassword = userPasswordTextField.text
let userRepeatPassword = userRepeatPasswordTextField.text
}
// Check for empty fields
**//ERROR HERE EXPECTED DECLARATION**
if (((userFullName.isEmpty || userEmail.isEmpty || userPassword.isEmpty || userRepeatPassword.isEmpty))
{
// Display alert message
displayMyAlertMessage("All fields are required")
return;
}
// Check if passwords match
if(userPassword != userRepeatPassword)
{
// Display alert message
displayMyAlertMessage("Passwords do not match")
return;
}
func displayAlertMessage(userMessage:string)
{
var myAlert = UIAlertController(title:"Alert", message: userMessage, preferredStyle: UIAlertControllerStyle.Alert);
}
}
// Store Data
// Display alert message with confirmation
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Error 1: Non-closed parantheses
You're missing a closing paranthesis in the first if statement of the RegisterButtonTapped function:
if (((userFullName!.isEmpty || userEmail!.isEmpty
|| userPassword!.isEmpty || userRepeatPassword.isEmpty)) {
// Display alert message
displayMyAlertMessage("All fields are required")
return
}
Three left pranthesis ((( finished by only two right ones )). I this case, you really need no set of parantheses at all, but could save one set for readability:
if (userFullName!.isEmpty || userEmail!.isEmpty
|| userPassword!.isEmpty || userRepeatPassword.isEmpty) {
// Display alert message
displayMyAlertMessage("All fields are required")
return
}
Error 2: trying to include a function declaration within another function
(As pointed out by Duy Tran)
The function displayAlertMessage(...) is positioned within the function RegisterButtonTapped. Functions may only exists as methods directly in a class/structure etc (or as a global non-class owned function e.g. in a playground).
Hence, you should make sure the function displayAlertMessage(...) is placed outside the body of function RegisterButtonTapped.
General remarks
You should avoid "forced unwrappings" of optional variables. E.g. userFullName!.isEmpty will lead to a runtime exception in case the optional immutable userFullName has valuenil`. You should read up on optionals, optional binding, and so on.
Also note that you needn't put semi-colons ; after lines in Swift. However you won't get an error for doing so:
[swift-evolution] Proposal to remove semicolons
The function displayAlertMessage is placed within the IBAction body.
It should be placed outside your IBAction function.

first portion of If statement is being passed over; optional error

First off, here is a link to my simple little GPA project: https://www.dropbox.com/sh/prkf2e3u6ok7jmo/AACY8b8_kDfigxluJyEzdZbGa?dl=0 .
import UIKit
class calccontroller: UIViewController {
#IBOutlet weak var GPA1: UITextField!
#IBOutlet weak var Credits1: UITextField!
#IBOutlet weak var GPA2: UITextField!
#IBOutlet weak var Credits2: UITextField!
#IBOutlet weak var answerLabel: UILabel!
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.
}
#IBAction func calculatorButton(sender: AnyObject) {
if GPA1 == nil || GPA2 == nil || Credits1 == nil || Credits2 == nil {
let title = "Oops"
let message = "Please add a value to each text field"
let okText = "OK"
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
let okayButton = UIAlertAction(title: okText, style: UIAlertActionStyle.Cancel, handler: nil)
alert.addAction(okayButton)
presentViewController(alert, animated: true, completion: nil)
}
else
{
let gpa1text = Float(GPA1.text!)!
let gpa2text = Float(GPA2.text!)!
let credits1text = Float(Credits1.text!)!
let credits2text = Float(Credits2.text!)!
let firstGrade = gpa1text * credits1text
let secondGrade = gpa2text * credits2text
let allGPA = firstGrade + secondGrade
let allCredits = credits1text + credits2text
let finalGrade = allGPA / allCredits
answerLabel.text = "\(finalGrade)"
GPA1.resignFirstResponder()
GPA2.resignFirstResponder()
Credits1.resignFirstResponder()
Credits2.resignFirstResponder()
}
}
}
Anyways, I have a simple 4 text field, 1 button, and 1 label set up. I thought I had my code set up successfully that if each text field was not holding a value, a ui alert value would pop up asking for a value in each text box. If there are 4 values, then the app would complete the "math." However, when I leave all 4 empty or if I click the button when all 4 are filled and then delete 1 text field, my app crashes... "unexpectedly found nil while unwrapping an Optional value". I find it frustrating (which could be a simple mistake on my end, but in my mind I shouldn't be having this value this my pop up alert is meant to be used when a text field is nil.
Thank you for your time, Jon.
Your if logic is incorrect.
Since you create IBOutlet and check for nil, it will always return false if you instantiate your View Controller from storyboard or nib.
What you want to do is check its value is empty or not. So something like this should do the trick
if GPA1.text.isEmpty || GPA2.text.isEmpty || Credits1.text.isEmpty || Credits2.text.isEmpty {
// show alert
} else {
// do your calculation
}

Resources