Declare a variable in ViewDidLoad and use elsewhere (Swift) - swift

First Swift project, and I'm confused as to how scope works. I'm trying to choose a random integer in ViewDidLoad and then allow the user to guess the number, but I can't work out how to access the variable answer created in ViewDidLoad in my button action.
class ViewController: UIViewController {
#IBOutlet var guess: UITextField!
#IBOutlet var result: UILabel!
#IBAction func guessButton(sender: AnyObject) {
var userGuess = guess.text.toInt()
if userGuess != nil {
if userGuess == answer {
result.text = "You got it!"
} else if userGuess > answer {
result.text = "Too high! Guess again!"
} else {
result.text = "Too low! Guess again!"
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
var answer = arc4random_uniform(100) + 1
}
}

Three scopes:
File top level
Object type declaration top level (like your guess)
Local (inside function curly braces) (like your userGuess)
You can only see up (higher surrounding scope).
So, code in guessButton method cannot see variable declared in viewDidLoad method. But they can both see variable declared at file top level or variable declared at object type declaration top level. Which one do you think is most appropriate here?

Related

Xcode finding a nil variable where none should be when methods is called from another ViewController class

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?

if statement executing itself instead of others

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

cs193p Swift - key value coding error

I can't believe I'm having a problem with the very first lecture! We are building a calculator (it is not at all functional as a calculator at this point). Mine was working just fine until the performOperation function was added. Now I'm getting "this class is not key value coding compliant" error. The thing is that, as far as I can see, my code EXACTLY replicates the code that he is using in the class. I must be missing something but I've checked roughly 50 times to find a difference and I can't find one. Help? It is crashing every time.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var display: UILabel!
var userIsInTheMiddleOfTyping = false
#IBAction func touchDigit(sender: UIButton) {
let digit = sender.currentTitle!
if userIsInTheMiddleOfTyping {
let textCurrentlyInDisplay = display.text!
display.text = textCurrentlyInDisplay + digit
} else {
display.text = digit
}
userIsInTheMiddleOfTyping = true
}
#IBAction func performOperation(sender: UIButton) {
userIsInTheMiddleOfTyping = false
if let mathematicalSymbol = sender.currentTitle {
if mathematicalSymbol == "π" {
display.text = String(M_PI)
}
}
}
}
EDIT: Code format
You can check your viewcontroller in storyboard, at the Module TextField in the right toolbar.
Do following steps if module textfield
is empty (there’s no placeholder):
Step 1. Focus Module TextField
Step 2. Edit randomly then clear
Step 3. Press Enter, if the result is as same as this picture, everything’s going okey.

Set maximum characters (to one) in a NSTextfield in Swift

How do i set the maximum amount of characters in multiple NSTextfields (OSX cocoa app, NOT iOS) to one in Swift?
Please explain how to do it, because I'm a complete newbie when it comes to OSX app development and therefore I don't understand short answers like "Use NSFormatter", because I have no idea what it is and how to implement it. Like Examples
There's no built-in way to simply set the maximum, I think because you need to decide what behavior you want. For example, if there's already one character in the field, and the user enters a second character, what should happen? Should the 2nd character be ignored? Replace the first? Beep?
In any case, you can get whatever behavior you want using the NSText Delegate methods. Make your view controller (or whatever object has the logic) a delegate of the text field, and implement the various delegate method(s) to do what you need.
Again, the exact behavior is up to you, but if I were implementing this, I might be inclined to make the text field always use the last character entered (such that, if one character is already present, pressing a second replaces the first). To do that, you'd want to override textDidChange to look at the value of the text field, and modify it if appropriate. Something like:
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var textField: NSTextField!
override func controlTextDidChange(obj: NSNotification) {
if self.textField.stringValue.characters.count > 1 {
self.textField.stringValue = String(self.textField.stringValue.characters.last!)
}
}
}
You don't need to limit the characters a user will enter just only look at the first character entered. In fact, it is probably better since you will always have to handle possible user errors. If you want to you can issue an alert that they entered too many by getting the characters.count. You might want an alert if they don't answer at all. The code below will work as is if you set up a storyboard with 1 NSTextField and one button and connect them. If you have more than one textfield, i.e. like a multiple choice test, just set up all the text fields the same way.
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var firstLetter: NSTextField!
Add as many text fields as you need:
#IBOutlet weak var secondLetter: NSTextField!
#IBOutlet weak var thirdLetter: NSTextField!
etc.
#IBAction func button(sender: AnyObject) {
var firstEntry = firstLetter!.stringValue
var index1 = firstEntry.startIndex
if firstEntry.characters.count > 1 {
runMyAlert("Bad USER! ONLY ONE Character!")
}
if firstEntry == "" { //left it blank
runMyAlert("You need to enter at least one character!")
exit(0) //or you'll crash on next line
}
var nameLetter1:Character = firstEntry[index1]
print( "First Letter == \(nameLetter1) ")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func runMyAlert( alertMessage: String){
var myWindow = NSWindow.self
let alert = NSAlert()
alert.messageText = "ERROR ERROR ERROR"
alert.addButtonWithTitle("OK")
alert.informativeText = alertMessage
alert.runModal()
}
}

Assigning text field value to variable in Swift

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
}