I've written a simple application using Swift and Xcode 6.1.1. The program, which is a simple calculator, works well but I haven't been able to validate non-nil input for the three text fields. As a result, if the user leaves the field blank and then hits "calculate," the application crashes.
The app takes three inputs, initially as strings. I wrote an if statement to check for nil but it doesn't work - it will just pass to else regardless. Here's the code block that's relevant to my question:
...
#IBOutlet var calcButton: UIBarButtonItem!
#IBOutlet var yearOneField: UITextField!
#IBOutlet var yearTwoField: UITextField!
#IBOutlet var yearThreeField: UITextField!
#IBOutlet var displayWindow: UILabel!
#IBAction func calcButtonTapped(sender: AnyObject) {
if (yearOneField == nil) {
displayWindow.text = ("Non-zero entries are not permitted. Please enter new values.")
} else {
let yearOne = yearOneField.text.toInt()
let yearTwo = yearTwoField.text.toInt()
let yearThree = yearThreeField.text.toInt()
...
I was thinking I could evaluate the IBOutlet for nil but that didn't work. I'm new to Swift and Xcode so I hope this is a n00b question to the more experienced developers out there. Thank you.
The only way the #IBOutlets could be nil is if you forgot to wire them up in Interface Builder. Usually you don't need to check that because the crash will tell you to fix that problem.
The toInt() function returns an Optional Int (aka Int?) that must be unwrapped before being used. toInt() will return nil if the value in the text field does not represent a valid Int. "2.1", "seven", and "" would all return nil if converted with toInt(). I recommend you use optional binding (if let) syntax to check the conversion for nil and unwrap the result if it is not nil:
if let yearOne = yearOneField.text.toInt() {
if let yearTwo = yearTwoField.text.toInt() {
if let yearThree = yearThreeField.text.toInt() {
// yearOne, yearTwo, and yearThree are all valid Ints
// so do the calculations
}
}
}
Alternatively, if you know you want to use a default value (like 0) when the field can't be converted to an Int, you can unwrap the result using the nil coalescing operator ?? like so:
let yearOne = yearOneField.text.toInt() ?? 0
let yearTwo = yearTwoField.text.toInt() ?? 0
let yearThree = yearThreeField.text.toInt() ?? 0
The text fields themselves will never be nil. They are created and assigned during initialization, and you're never removing them.
I think you want to check if their text properties contain any text, which you can do like this:
Updated for Swift 2:
if let text = yearOneField.text where !text.isEmpty {
// perform the conversions
} else {
// the text field is empty
}
You can avoid nesting using guard:
guard let text = yearOneField.text where !text.isEmpty else {
// the text field is empty
return
}
// perform the conversions
I prefer the guard syntax because it's clearer about what the ideal result is.
You can just check as you do with normal optionals.
guard let unwrapped = myLabel else {return}
Or like this
if myLabel == nil {
//do stuff
}
Or like this:
if let unwrappedLabel = myLabel {
}
Related
I got midway through creating a MacOS app (my first app ever that isn't part of a "Your First App" tutorial) involving a lot of user options in the main window and became fed up with the fact that my ViewController file had become an unwieldy mess that was not going to be maintainable in the long run.
I decided to break it input multiple view controllers in smaller chunks to make it more manageable using container views in UIBuilder for embedding views, but all the tutorials I found were either for outdated versions of Xcode/Swift, or were about managing multiple views in iOS, so I had to extrapolate a little, and I may have done it wrong.
Now I'm getting an error on a method in one ViewController when the method is called by another ViewController, even though that method works find when called by its own view controller.
Either I'm missing something obvious, or I set things up wrong.
Global variables:
var inputPathUrl = URL?
var outputExtension: String = ""
#IBOutlets and local properties for the InOutViewController class:
#IBOutlet weak var inputTextDisplay: NSTextField!
#IBOutlet weak var outputTextDisplay: NSTextField!
#IBOutlet weak var inputBrowseButton: NSButton!
#IBOutlet weak var outputBrowseButton: NSButton!
var outputDirectoryUrl: URL?
var inputFilePath: String = ""
#IBOutlets for the OptionsViewController class
#IBOutlet weak var Button1: NSButton!
#IBOutlet weak var Button2: NSButton!
#IBOutlet weak var Button3: NSButton!
#IBOutlet weak var Button4: NSButton!
#IBOutlet weak var Button5: NSButton!
Methods for the InOutViewController class:
#IBAction func InputBrowseClicked(\_ sender: Any) {
let inputPanel = NSOpenPanel()
inputPanel.canChooseFiles = true
inputPanel.canChooseDirectories = false
inputPanel.allowsMultipleSelection = false
inputPanel.allowedFileTypes = \["aax"\]
let userChoice = inputPanel.runModal()
switch userChoice{
case .OK :
if let inputFileChosen = inputPanel.url {
inputFileUrl = inputFileChosen // define global variable that will be called by other methods in other classes to check if an input file has been chosen
updateInputText() // call methods to display path strings in text fields
updateOutputText()
}
case .cancel :
print("user cancelled")
default :
break
}
}
#IBAction func outputBrowseClicked(_ sender: Any) {
let outputPanel = NSOpenPanel()
outputPanel.canChooseFiles = false
outputPanel.canChooseDirectories = true
outputPanel.allowsMultipleSelection = false
let userChoice = outputPanel.runModal()
switch userChoice{
case .OK :
if let outputUrl = outputPanel.url {
outputDirectoryUrl = outputUrl
updateOutputText()
}
case .cancel :
print("user cancelled")
default:
break
}
}
func updateInputText() {
// call getOutputOption method to see which radio button is selected
OptionsViewController().getOutputOption()
if inputFileUrl != nil {
inputFilePath = inputFileUrl!.path
inputTextDisplay.stringValue = inputFilePath
}
}
func updateOutputText() {
// derive output file path and name from input if no output location is chosen
if inputFileUrl != nil && outputDirectoryUrl == nil {
let outputDirectory = inputFileUrl!.deletingPathExtension()
let outputDirectoryPath = outputDirectory.path
let outputPath = outputDirectoryPath + "(outputExtension)"
outputTextDisplay.stringValue = outputPath
} else if inputFileUrl != nil && outputDirectoryUrl != nil {
// derive default file name from input but use selected output path if one is chosen
let outputDirectoryPath = outputDirectoryUrl!.path
let outputFile = inputFileUrl!.deletingPathExtension()
let outputFilename = outputFile.lastPathComponent
// derive file extension from getOutputOption method of OptionsViewController class
let outputPath = outputDirectoryPath + "/" + outputFilename + "(outputExtension)"
outputTextDisplay.stringValue = outputPath
}
}
That last line (outputTextDisplay.stringValue = outputPath) is what I'm getting the fatal error on, but ONLY when I call this method from the #IBAction for the output format radio buttons in OptionsViewController to update the output display when a different file extension is chosen. When I call the method from the actions methods in InOutViewController it works fine.
Here are the #IBAction method and getOutputOption methods from the OptionsViewController class:
#IBAction func radioButtonClicked(_ sender: Any) {
getOutputOption()
// update display with new file extension
InOutViewController().updateOutputText()
}
func getOutputOption() {
// make sure an input file has been chosen
if inputFileUrl != nil {
// check which radio button is selected and derive output file format based on selection
// not sure why I need to specify the button isn't nil, since one is ALWAYS selected, but I was getting a fatal error without doing so
if (Button1 != nil) && Button1.state == .on {
outputExtension = ".extA"
} else if (Button2 != nil) && Button2.state == .on {
outputExtension = ".extB"
} else if (Button3 != nil) && Button3.state == .on {
outputExtension = ".extC"
} else if (Button4 != nil) && Button4.state == .on {
outputExtension = ".extD"
} else {
outputExtension = ".extE"
}
}
}
I'm sure I'm missing something obvious but like I said, it's my first time working with multiple view controllers and I'm not sure I've implemented them properly, and I've only been coding for a few weeks, so I can't spot where I'm going wrong.
My guess is that outputTextDisplay is an IBOutlet in InOutViewController, and it is declared as implicitly unwrapped. (Something like this: )
var outputTextDisplay: UITextField!
If you reference such a variable from one of the IBActions in your InOutViewController, all is well because at that point your view controller's views are loaded.
If, on the other hand, you call updateOutputText() from another view controller, your InOutViewController's views may not have been loaded yet, so the outlet for outputTextDisplay is still nil. When a variable is declared as implicitly unwrapped (using a ! at the end of the type) then any time you reference it, the compiler force-unwraps it, and if it's nil, you crash.
You should change your updateOutputText() to use ? to unwrap the variable. That stops implicitly unwrapped optionals from crashing. Something like this:
func updateOutputText() {
// derive output file path and name from input if no output location is chosen
if inputFileUrl != nil && outputDirectoryUrl == nil {
let outputDirectory = inputFileUrl!.deletingPathExtension()
let outputDirectoryPath = outputDirectory.path
let outputPath = outputDirectoryPath + "(outputExtension)"
outputTextDisplay?.stringValue = outputPath //Note the `?`
} else if inputFileUrl != nil && outputDirectoryUrl != nil {
// derive default file name from input but use selected output path if one is chosen
let outputDirectoryPath = outputDirectoryUrl!.path
let outputFile = inputFileUrl!.deletingPathExtension()
let outputFilename = outputFile.lastPathComponent
// derive file extension from getOutputOption method of OptionsViewController class
let outputPath = outputDirectoryPath + "/" + outputFilename + "(outputExtension)"
outputTextDisplay?.stringValue = outputPath //Note the `?`
}
}
}
The code outputTextDisplay?.stringValue = outputPath uses "optional chaining", which causes the compiler to check if outputTextDisplay is nil, and stop executing the code if it is.
Note that if you make that change, your string value won't get installed into your outputTextDisplay field when you call the updateOutputText() function. You'll need to install the value after viewDidLoad() is called. (Perhaps in your viewDidLoad or in viewWillAppear.)
Edit:
This code:
#IBAction func radioButtonClicked(_ sender: Any) {
getOutputOption()
// update display with new file extension
InOutViewController().updateOutputText()
}
Is very wrong. In response to the user clicking a radio button, the InOutViewController() bit creates a throw-away instance of an InOutViewController, tries to call its updateOutputText() method, and then forgets about the newly created InOutViewController. Don't do that.
You need a way to keep track fo the child view controllers that are on-screen. To show you how to do that you'll need to explain how your various view controllers are being created. Are you using embed segues?
I have a code here that, each time I run it, only the if statement which states "All fields are required" works but NOT ONLY when it must be called, it actually runs in place of the others. So whatever I do even when all the fields are complete, I have "All fields are required" as an alert message.
Here is the code, all help is appreciated, thank you in advance.
import UIKit
class RegisterPageViewController: UIViewController {
#IBOutlet weak var userEmailTextField: UITextField!
#IBOutlet weak var userPasswordTextField: UITextField!
#IBOutlet weak var repeatPasswordTextField: 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 userEmail = ""
let userPassword = ""
let userRepeatPassword = ""
// Check for empty fields
if (userEmail.isEmpty || userPassword.isEmpty ||
userRepeatPassword.isEmpty)
{
// Display Alert Message
displayMyAlertMessage(userMessage:"All fields are required")
return
}
//Check if passwords match
if (userPassword != userRepeatPassword)
{
// Display an alert message
displayMyAlertMessage(userMessage:"Passwords do not match")
return
}
// Store data
UserDefaults.standard.set(userEmail, forKey:"userEmail")
UserDefaults.standard.set(userEmail, forKey:"userPassword")
UserDefaults.standard.synchronize()
// Display alert message with confirmation
_ = UIAlertController(title:"Alert", message:"Registration is
successfull. Thank you!",
preferredStyle:UIAlertControllerStyle.alert);
_ = UIAlertAction(title:"Ok", style:UIAlertActionStyle.default)
{
action in
self.dismiss(animated: true, completion:nil)
}
}
func displayMyAlertMessage(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.present(myAlert, animated:true, completion:nil)
}
}
Do you ever assign any values to userEmail, userPassword, userRepeatPassword? You initialize them as empty at the start of the function, and it looks like their values never change.
Instead of declaring them in the function, try using class level variables, and linking them to your textfields in Storyboard.
#IBOutlet weak var userEmail: UITextField!
#IBOutlet weak var userPassword: UITextField!
#IBOutlet weak var userRepeatPassword: UITextField!
#IBAction func registerButtonTapped(_ sender: AnyObject) {
// Check for empty fields
if (self.userEmail.text.isEmpty || self.userPassword.text.isEmpty || self.userRepeatPassword.text.isEmpty) {
// Display Alert Message
displayMyAlertMessage(userMessage:"All fields are required")
return
}
...
}
I suspect that this code is not an accurate representation of your implementation. Would you be able to copy and paste the registerButtonTapped(_:) function unedited?
If it is, I would agree with #unmarshalled: It appears that you have declared each of the variables with an empty string as their value. If the code you have posted is implemented exactly as above, that is the cause of your issue.
based on the code you have posted, I would also recommend the following alterations:
get the email, password & repeatPassword from outside the scope of the function: usually, by just pulling it directly from the UI, most commonly from text fields: i.e. userEmailTextField.text
extracting your user defaults keys, and any other string literals you have, into a constants file is good practice and avoid any unnecessary misspelling related bugs.
you don't need to add a handler to a UIAlertAction if all you want it to do is dismiss the alert. UIAlterController will automatically be dismissed automatically: the handler argument has a default value of nil and can be omitted, simply:
let okayButton = UIAlertAction(title: "Ok", style: .default)
Generally speaking, you don't want to store a reference to a shared instance. However, within small local scopes its a little cleaner to do so:
let userDefaults = UserDefaults.standard
userDefaults.set(userEmail, forKey:"userEmail")
userDefaults.set(userEmail, forKey:"userPassword")
userDefaults.synchronize()
Cheers :)
EDIT:
I would suggest extracting the conditional out to a computed property for readability and check if count == 0 rather than isEmpty. The advantage of this is that you can make the computed property more comprehensive, I.e this will check that the strings are not nil or empty. Usually checking the count is enough, but there’s no harm in covering your bases.
As it stands with the current UIKit implementation, UITextField.text can never be nil. That being said, official documentation does not make that guarantee explicitly, so the best way to handle it is to implement it like an optional, below.
So something like:
fileprivate var registrationFormCompleted: Bool {
guard username = usernameTextfield.text,
password = passwordTextField.text,
repeat = repeatPasswordTextField.text,
else {
return false
}
return username.count > 0 &&
password.count > 0 &&
repeat.count > 0
}
In use it would be:
#IBAction func registerButtonTapped(_ sender: AnyObject) {
// Check for empty fields
if !registrationFormCompleted {
// Display Alert Message
displayMyAlertMessage(userMessage:"All fields are required")
return
}
//....
}
Please help and I'm sorry if this is covered somewhere else although i have looked elsewhere and whilst there are answers to my topic i cannot find one for my problem. so if anyone could help that would be great.
basically i am trying to do a switch statement inside a button so that when i click the button the statement activates in my label, but for some reason i cannot get it to work
here is my code below
when i hit run it always shows the "This is an even number between 0 and 10" regardless of even number, odd number or large number
Any help would be greatly appreciated
thank you very much
Right before the switch line you have to add three lines to assign the value of the input text field to the variable number.
guard let inputText = numberInput.text, let input = Int(inputText) else {
numberOutput.text = "Please enter a number!"
return }
number = input
This code works fine in playground, always prints expected output.
var number = 6
switch number {
case 0,2,4,6,8,10:
print("even")
case 1,3,5,7,9:
print("odd")
default:
print("higher")
}
where are you changing the number variable? Are you doing it on one of the callback methods for textfield delegate?
you are not changing the value of your var number. Looks like it is always 6 based from your code
You can simply do this:
import UIKit
class EmptyViewController: UIViewController
{
#IBOutlet weak var label: UILabel!
#IBOutlet weak var textField: UITextField!
#IBAction func buttonHit(_ sender: UIButton)
{
if let text = textField.text, let number = Int(text)
{
switch number {
case 0,2,4,6,8,10:
label.text = "Even"
case 1,3,5,7,9:
label.text = "Odd"
default:
label.text = "Higher"
}
}
else
{
label.text = "Please enter a valid number"
}
}
}
I have 6 textfields that I want to at the push of a button concatenate into a string (in order) and then take that string and convert it to an Int.
I know how to convert the string into an Int.
What I keep getting stuck at is how do I create a property under the IBOutlets that is optional and then store the optionally unwrapped textfield.text into the property?
What I have tried is
#IBOutlet weak var firstTextField: UITextField!
this is the IBOutlet
var firstTextFieldString: String?
that is the property
then later in a function I say
func functionName(textField: UITextField) {
if textField == firstTextField {
if let text = textField.text {
firstTextFieldString = text
}
}
}
The text property of a UITextField is an optional string (String?) and can be assigned to any other variables that are of the same type. It looks like your outlet and property are correct.
You should be able to assign the text property directly to the firstTextFieldString property.
firstTextFieldString = firstTextField.text
If you want a function to be called when a button is pressed, you can mark the function with #IBAction and hook it up in the storyboard.
#IBAction func functionName(textField: UITextField) {
// your function here
}
If you reference all your text fields as outlets, you should be able to write a function that directly accesses the text property of each field and combine them together. It depends on exactly what you want to when the text fields have no value. If you want to treat them as empty strings, you could unwrap them with a default value before concatenating them all together.
#IBAction func functionName(textField: UITextField) {
let first = firstTextField.text ?? ""
let second = secondTextField.text ?? ""
let third = thirdTextField.text ?? ""
// etc...
let result = first + second + third + ...
// etc...
}
I am trying to learn Swift and it is turning out to be more different from other languages than I expected...
I just want to store the value of a user's input as an integer in a variable.
My attempts result in the following error:
"fatal error: unexpectedly found nil while unwrapping an Optional value"
I have tried this multiple ways and can't seem to come up with a solution, I know there must a simple way to do this.
var intNumber: Int = 0
#IBOutlet weak var txt_Number: UITextField!
for view in self.view.subviews as [UIView]{
if let txt = view as? UITextField
{
if let txtData = txt.text where txtData.isEmpty
{
// Error Message
}
else
{
intNumber = Int(txt_Number.text)
}
}
}
I know the above code isn't correct, but I think that's the closest to correct I have come. I seem to be missing something as far as unwrapping goes. I understand the principal of unwrapping, but nothing I have tried will compile, or if it does compile then it fails with the error above when the code is initiated (code is initiated when a button is pressed).
Thank you in advanced for any help!
A couple of thoughts:
Make sure your outlet is hooked up to txt_Number. All of that code checking to make sure it's not nil is not necessary if (a) it's an outlet you hooked up in IB; and (b) you're not doing the above code before the view is completely loaded (i.e. viewDidLoad was called).
If the outlet is not hooked up, you'll see an empty dot on the left margin:
If it is hooked up correctly, you'll see a filled in dot on the left margin:
If everything is hooked up correctly, you can just do:
guard let txtData = txt_Number.text, let value = Int(txtData) else {
// report error and then `return`
return
}
intNumber = value
If you want to get fancy, you might want to ensure the user only enters numeric values by
In viewDidLoad, specify that the keyboard is for decimal numbers only.
txt_Number.keyboardType = .NumberPad
Or you can specify this in IB, too.
Specify a delegate for the text field and only allow them to enter numeric values. (This might seem redundant based upon the prior point, but it's not, because you have to also anticipate them pasting in a string to the text field.)
See https://stackoverflow.com/a/26940387/1271826.
For starters, you don't have to iterate over subviews if you have direct reference txt_Number, but this is not an essence of your question.
if let semantics will let you unwrap any optional inside {} brackets, so the most visible solution here is to:
if let unwrappedString = txt_Number.text {
if let unwrappedIntegerInit = Int(unwrappedString) {
intNumber = unwrappedIntegerInit
}
}
My full example from playgrounds:
var intNumber: Int = 0
var txt_Number: UITextField = UITextField()
txt_Number.text = "12"
if let unwrappedString = txt_Number.text {
if let unwrappedIntegerInit = Int(unwrappedString) {
intNumber = unwrappedIntegerInit
}
}
print(intNumber)
Or you can use guard inside a function:
func parse() {
guard let text = txt_Number.text, let number = Int(text) else { return } // no text
intNumber = number
}
TIP:
You have to unwrap txt_Number.text and Int(text) separately cause Int(text) has to have nonoptional argument.
Did you try with this?
if let txtData = txt.text where !txtData.isEmpty
{
intNumber = Int(txtData)
}
else
{
// Error Message
}
ADD:
Int() function returns an Optional. If you are sure that the value is correct, you can force the unwrapping by using ! at the end of the variable name (when you are using it), otherwise just put the question mark ?
tried below code to assign value of TextField to variable of float type and all bug disappear like magic
#IBOutlet weak var txtamount: UITextField!
#IBOutlet weak var txtrate: UITextField!
#IBOutlet weak var txtyear: UITextField!
#IBOutlet weak var lblresult: UILabel!
#IBAction func btncalculate(_ sender: UIButton)
{
print("button is clicked")
var amount,rate,year,answer : Float
amount = Float(txtamount.text!)!
rate = Float(txtrate.text!)!
year = Float(txtyear.text!)!
answer = (amount * rate * year) / 100.0
}