I know there are a lot of unexpected nil value questions here, but I've tried other approaches and they're not working for me. The function at the bottom is called from a button on another view controller. Basically the issue is that this code works as expected, the form element has the DatePicker as the input view, and it updates startDatePickerField.text when the value is changed, so I know that startDatePickerField.text is not empty, as it is displayed on the screen. However, when calling the testForValidity() function is called from another view controller containing the one we are working in, I get the "unexpectedly found nil while unwrapping option value error". All of the outlets are connected properly so I know it isn't that.
class popoverTableViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet var startDatePickerField: UITextField!
override func viewDidLoad() {
let startDatePicker:UIDatePicker = UIDatePicker()
startDatePicker.datePickerMode = UIDatePickerMode.dateAndTime
startDatePickerField.inputView = startDatePicker
startDatePicker.minuteInterval = 5
startDatePicker.addTarget(self, action: #selector(popoverTableViewController.startDatePickerValueChanged(_:)), for: UIControlEvents.valueChanged)
}
#IBAction func startDateDidBegin(_ sender: AnyObject) {
}
func startDatePickerValueChanged(_ sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = DateFormatter.Style.long
dateFormatter.timeStyle = DateFormatter.Style.short
startDatePickerField.text = dateFormatter.string(from: sender.date)
finishDateTime = sender.date
}
And the function that errors:
func testForValidity() {
print(startDatePickerField.text)
}
Issue:
You're trying to access IBOutlet property from another class via instance method.
The reason behind the nil value is IBOutlets are initialized when the nib is loaded.
Hence in your case when you are in another view controller you can't access an Outlet variable else you will be always getting nil.
Solution:
If I assume correctly, you want to use a value from a view controller after having a value in it.
The best way is to follow 'Design patterns'. i.e., You have to store your required value in a model class and access the value via model class instance.
Apple has very good documentation about it. Please go through.
https://developer.apple.com/library/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson6.html#//apple_ref/doc/uid/TP40015214-CH20-SW1
Sample Code in Swift:
class TextFieldViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var outputLabel : UILabel!
#IBOutlet weak var textField : UITextField!
override func viewDidLoad() { super.viewDidLoad(); setupTextField() }
func testValidity() -> (textFieldValue: UITextField?, modelValue: String?) {
return (textField, TextFieldModel.sharedModel.textFieldData)
}
//MARK: - Text Field Delegates
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
outputLabel.text = textField.text
TextFieldModel.sharedModel.textFieldData = textField.text
return true
}
}
class TextFieldModel {
static let sharedModel = TextFieldModel()
var textFieldData: String?
}
class DemoController: UIViewController {
#IBOutlet weak var textFieldOutput: UILabel!
#IBOutlet weak var modelOutput: UILabel!
override func viewDidLoad() { super.viewDidLoad(); testValues() }
func testValues() {
let tvc = TextFieldViewController()
textFieldOutput.text = "Value of Text field is " + (tvc.testValidity().textFieldValue?.text ?? "nil")
modelOutput.text = "Value accessed from Model Class is \(tvc.testValidity().modelValue)"
}
}
extension TextFieldViewController {
func setupTextField() {
textField.placeholder = ""
textField.delegate = self
textField.becomeFirstResponder()
textField.keyboardType = UIKeyboardType.Default
textField.returnKeyType = UIReturnKeyType.Done
}
}
Output:
At the top, add UITextFieldDelegate so it becomes:
class popoverTableViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {
Then in viewDidLoad() add this line :
self.startDatePickerField.delegate = self
Related
I want to delegate some tasks to the AcceptController, but my delegate property inside of 'SendController' always returns nil, so no delegation will ever be executed. I just can't figure out why my delegate property 'übergabeDelegate' always returns nil.
protocol ÜbergabeDelegate {
func übergebeText(text: String)
}
class SendController: UIViewController {
#IBOutlet weak var textField: UITextField!
var übergabeDelegate: ÜbergabeDelegate?
#IBAction func save(_ sender: UIButton) {
if let text = textField.text {
if übergabeDelegate != nil {
übergabeDelegate!.übergebeText(text: text)
} else {
print("\nübergabeDelegate is nil\n")
}
}
}
}
class AcceptController: UIViewController {
#IBOutlet weak var label: UILabel!
let sendController = SendController()
override func viewDidLoad() {
super.viewDidLoad()
sendController.übergabeDelegate = self
//print("Delegate gesetzt")
}
}
extension AcceptController: ÜbergabeDelegate {
func übergebeText(text: String) {
label.text = "\(text)"
}
}
I expect the label to present the input I gave on my SendController but the text of the label never actually changes.
inside viewDidLoad let sendController = SendController() creates a local variable. the sendController will then be released after viewDidLoad returns. set the sendController as a property of the AcceptController and the delegate will persist when you assign it because the SendController object will not be released after viewDidLoad returns.
I have an onboarding user flow:
Name -> Age -> Gender
Each of the screens shares the same structure:
Question (top)
Input (middle)
Continue (bottom)
I have a class OnboardingHelper.swift that creates a class to set the question box and continue button:
class UserOnboardingHelper{
var text: String
var questionbox: UIView
var viewController: UIViewController
var continueButton: UIButton
init(text: String, questionbox: UIView, viewController: UIViewController, continueButton: UIButton){
self.text = text
self.questionbox = questionbox
self.viewController = viewController
self.continueButton = continueButton
}
func setQuestionBox(){
//sets question box
}
func setContinueButton(){
//sets continue button
enableContinueButton()
addContinueButtonPath()
}
func enableContinueButton(){
//enables continue button
}
func disableContinueButton(){
//disables continue button
}
func addContinueButtonPath(){
//sets path of continue button based on which view
}
}
In each of the onboarding ViewControllers I am setting the class in ViewDidLoad():
class NamePageViewController: UIViewController, UITextFieldDelagate {
#IBOutlet weak var questionbox: UIView!
#IBOutlet weak var continueButton: UIButton!
#IBOutlet weak var inputLabel: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let namePageSettings = UserOnboardingHelper(text: "What is your name", questionbox: questionbox, viewController: self, continueButton: continueButton)
namePageSettings.setQuestionBox()
namePageSettings.setContinueButton()
inputLabel.delegate = self
if nameIsFilled {
namePageSettings.enableContinueButton()
} else{
namePageSettings.disableContinueButton()
}
}
}
The issue is that in the ViewController I textFieldDidEndEditing() function which needs to call the namePageSettings class from viewDidLoad()
func textFieldDidEndEditing(_ textField: UITextField){
if (textField.text?.empty)!{
//I want to call disableContinueButton() from UserOnboardingHelper
} else {
//I want to enable enableContinueButton() from UserOnboardingHelper
}
}
Trying to understand if:
The overall approach is correct and if not, what's the best way
If the above approach is in the right direction, how should disableContinueButton() and enableContinueButton() be called?
Thanks in advance! Sorry if the approach is really dumb - I'm still trying to wrap my head around classes.
You can have the view controller have a weak reference to the onboarding helper, so you can still call helper methods without creating a retain cycle.
In NamePageViewController, add a property:
weak var userOnboardingHelper: UserOnboardingHelper?
Then, in UserOnboardingHelper's initializer, add:
self.viewController.userOnboardingHelper = self
You can now call the onboarding helper's methods in the view controller:
userOnboardingHelper.disableContinueButton()
userOnboardingHelper.enableContinueButton()
How do I find out if the keyboard is of type numeric, Twitter, email, etc...?
edit: Is there a way to detect keyboard type without using an outlet?
Consider that you have tow textFields in the ViewController, You will need to implement textFieldShouldBeginEditing method from UITextFieldDelegate protocol, as follows:
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var tfEmail: UITextField!
#IBOutlet weak var tfPassword: UITextField!
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField.keyboardType == .emailAddress {
// this is the tfEmail!
}
if textField.isSecureTextEntry {
// this is tfPassword!
}
}
}
Make sure their delegates are connected to the ViewController, programmatically:
tfEmail.delegate = self
tfPassword.delegate = self
or from the Interface Builder.
Note that you can recognize the keyboard type for the current textField by checking its keyboardType property, which is an instance of UIKeyboardType enum:
The type of keyboard to display for a given text-based view. Used with
the keyboardType property.
What about UITextView?
The same exact functionality should be applied when working with UITextViews, but you need to implement textViewDidBeginEditing(_:) method from UITextViewDelegate protocol instead of implementing textFieldShouldBeginEditing. Again, make sure the delegate of the textView is connected to the ViewController.
Also,
If your main purpose of checking the keyboard type is just for recognizing what is the current responded textField/textView, I suggest to do a direct check:
class ViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate {
#IBOutlet weak var tfEmail: UITextField!
#IBOutlet weak var tfPassword: UITextField!
#IBOutlet weak var textViewDescription: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
tfEmail.delegate = self
tfPassword.delegate = self
textViewDescription.delegate = self
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField === tfEmail {
// this is the tfEmail!
}
if textField === tfPassword {
// this is tfPassword!
}
}
func textViewDidBeginEditing(_ textView: UITextView) {
if textView === textViewDescription {
// this is description textview
}
}
}
For more information about === operator you might want to check this question/answers.
Hope this helped.
In addition to Ahmad F 's great answer, this is my approach of getting the current keyboard type, at any time:
Step 1: Delegate UITextField
class File: UIViewController, UITextFieldDelegate{//...}
Update viewDidLoad() to this:
#IBOutlet weak var normalTextField: UITextField!
#IBOutlet weak var numberTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
numberTextField.keyboardType = .numberPad
normalTextField.keyboardType = .default
emailTextField.keyboardType = .emailAddress
numberTextField.delegate = self
normalTextField.delegate = self
emailTextField.delegate = self
}
Step 2: Working with UITextField's methods:
Add a variable called keyboardType, as below:
var keyboardType: UIKeyboardType? = nil
Then, change it whenever a new textField begins editing:
func textFieldDidBeginEditing(_ textField: UITextField) {
keyboardType = textField.keyboardType
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
keyboardType = nil
return true
}
Step 3: Create and call a function like below:
func getCurrentKeyboard() -> String{
if keyboardType == nil{
return "no current keyboard"
}
else if keyboardType == .numberPad{
return "number"
}
else if keyboardType == .emailAddress{
return "email"
}
else{
return "default"
}
}
#IBAction func displayCurrentKeyboard(_ sender: UIButton) {
print(self.getCurrentKeyboard())
}
And this outputs: email / number / no current keyboard / default, depending on the case.
If you want to check which type of keyboard it is with if-else statements, you can change your displayCurrentKeyboard() method to this:
#IBAction func displayCurrentKeyboard(_ sender: UIButton) {
let keyboardString = self.getCurrentKeyboard()
if keyboardString == "number"{
//...
}
else if keyboardString == "email"{
//...
}
else{
//...
}
}
And that's it! You can call this wherever you want in your code with this usage:
let keyboardString = self.getCurrentKeyboard()
NOTE: This method also handles the case of no keyboard visible on the screen, returning no current keyboard, in this case.
Let me know if this helps!
I'm making a calculator app that solves for the missing variable and I've added a piece of code that dismisses the keyboard by tapping anywhere on the screen or pressing the "return" key.
This function was working before I added the if else statements to check which variable was missing (using .isNaN) so that it could solve for that missing variable.
Here is my code for that specific view controller:
import UIKit
class findSpeed: UIViewController, UITextFieldDelegate {
#IBOutlet weak var vValue: UITextField!
#IBOutlet weak var dValue: UITextField!
#IBOutlet weak var tValue: UITextField!
#IBOutlet weak var answer: UILabel!
#IBAction func solve(_ sender: UIButton) {
let v = Float(vValue.text! as String);
let d = Float(dValue.text! as String);
let t = Float(tValue.text! as String);
let solvev = (d!/t!);
let solved = (v!*t!);
let solvet = (d!/v!);
if (v!.isNaN) {
answer.text = "Answer: \(solvev)"
}
else if (d!.isNaN) {
answer.text = "Answer: \(solved)"
}
else {
answer.text = "Answer: \(solvet)"
}
}
override func viewDidLoad() {
super.viewDidLoad()
vValue.delegate = self;
dValue.delegate = self;
tValue.delegate = self;
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: Selector("dismiss")))
}
func dismiss() {
vValue.resignFirstResponder()
dValue.resignFirstResponder()
tValue.resignFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
vValue.resignFirstResponder()
dValue.resignFirstResponder()
tValue.resignFirstResponder()
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
The error I'm receiving occurs when I tap the button that links to this view controller where the calculator is:
Picture of error
Use the debugger and see why the app is crashing. Clearly vValve is nil which means you didn't connect anything to the outlet in Interface Builder.
Fix your outlet connections and the error posted in your question will be fixed.
You also need to eliminate all of the forced unwrapping you are doing. Otherwise you are going to have a lot more crashes to deal with. I strongly urge you to review What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?
I want to know how to edit the label of text while the app is running.
Example: there will be label called "Tap to Change text." When the user clicks it, it will be editable and the user can input text and enter. Then it will change to new text.
I know this can be done using UITextFieldDelegate,but I don't know how to approach to it because there is no way to put an action to a label when user touches it.
You can not edit label like you edit textField but when user click on label you can hide label and unhide textField and when user finish entering text you can again hide textField and unhide label and you can assign textField's text to label this way:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var lbl: UILabel!
#IBOutlet weak var textF: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textF.delegate = self
textF.hidden = true
lbl.userInteractionEnabled = true
let aSelector : Selector = "lblTapped"
let tapGesture = UITapGestureRecognizer(target: self, action: aSelector)
tapGesture.numberOfTapsRequired = 1
lbl.addGestureRecognizer(tapGesture)
}
func lblTapped(){
lbl.hidden = true
textF.hidden = false
textF.text = lbl.text
}
func textFieldShouldReturn(userText: UITextField) -> Bool {
userText.resignFirstResponder()
textF.hidden = true
lbl.hidden = false
lbl.text = textF.text
return true
}
}
Hope it will help.
To add my 2c here and remind me of the style in the latest Stanford University Paul Hegarty videos, the setup can be done in the "didSet" of the label Field - you can also set up different responders for different labels this way:
#IBOutlet weak var lblField: UILabel! {
didSet {
let recognizer = UILongPressGestureRecognizer()
recognizer.addTarget(self, action: #selector(ViewController.lbllongpress))
lblField.addGestureRecognizer(recognizer)
}
}
and then the implementation becomes:
func lbllongpress(gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case UIGestureRecognizerState.began:
break;
case UIGestureRecognizerState.ended:
// Implementation here...
default: break
}
}
It would be better to make the label a UITextField instead. This gives the appearance of a label, and upon click it's editable.
Create the UITextfield via storyboard
Connect the UITextfield outlet from the storyboard into the view controller file
Assign the value of the outlet with an observer
#IBOutlet weak var barTextField: UITextField! {
didSet {
self.barTextField.delegate = self // for step 4
barTextField.text = "value here"
}
}
Extend the viewcontroller with UITextFieldDelegate, which coincides with the delegate in step 3
Implement several of the optional functions in UITextFieldDelegate, including textFieldShouldReturn to detect when the enter key was pressed
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
barTextField.resignFirstResponder()
return true
}
Add TapGesture (UITapGestureRecognizer) to your label.
Now when user tap on it you can enable the text field & in the text fields delegate method you can convert it to label.
I made some adjustments on the selector from Dharmesh's answer. Hope this helps.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var lbl: UILabel!
#IBOutlet weak var textF: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textF.delegate = self
textF.hidden = true
lbl.userInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(lblTapped(_:))))
tapGesture.numberOfTapsRequired = 1
lbl.addGestureRecognizer(tapGesture)
}
#objc func lblTapped(_ sender:Any){
lbl.hidden = true
textF.hidden = false
textF.text = lbl.text
}
func textFieldShouldReturn(userText: UITextField) -> Bool {
userText.resignFirstResponder()
textF.hidden = true
lbl.hidden = false
lbl.text = textF.text
return true
}
}