Why am I getting nil for this string value? - swift

Basically I'm trying to make a simple note app, and here I'm just trying to display the title and content.
Project (note) file
import Foundation
class Project
{
var title = " "
var content = " "
var after = " "
}
Note detail view controller file
class NoteDetailViewController: UIViewController
{
var project: Project!
#IBOutlet weak var titleTextField: UITextField!
#IBOutlet weak var contentTextField: UITextView!
#IBOutlet weak var afterTextField: UITextView!
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
titleTextField.text = project.title
contentTextField.text = project.content
afterTextField.text = project.after
contentTextField.sizeToFit()
afterTextField.sizeToFit()
}
override func viewWillDisappear(animated: Bool)
{
super.viewWillDisappear(animated)
project.title = titleTextField.text!
project.content = contentTextField.text!
}
}
When I reference any of the project. files, I get nil. I have no idea why I'm getting nil from a string?

You need to initialize the class "Project" correctly.
let project = Project()

It seems to me project is not initialized;
project = new Project()
so accessing its properties will eventually return nil because the object does not exist

Yes, initializing the property definitely fixes the issue but there's a reason behind the error being thrown, and its one that's worth understanding rather than just initializing the property to make the error go away.
When you declare your property as:
var project: Project! //- with a !
You are declaring an optional property that immediately gets unwrapped (Implicit Unwrapping), this means that the compiler expects you to handle the case where the property is nil, and it wont throw compile errors when you use the property (as it has already been unwrapped). Which is not ideal as Swift is designed to try to catch as many errors at compile time, rather than at run time, and by unwrapping your optional when you declare it you are missing out on all that goodness.
In your every day swift you would declare optional properties as so:
var project: Project?
Which would throw a compile time error if you try to access the property value without unwrapping it. For example when doing:
var project: Project?
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
titleTextField.text = project.title //- The compiler will scream at you here.
contentTextField.text = project.content
afterTextField.text = project.after
contentTextField.sizeToFit()
afterTextField.sizeToFit()
}
If you you don't need your property to be an optional, initialize the variable when you declare it, or do so in the constructor of your class (where it applies), and avoid Implicitly unwrapping your optionals as this can have the potential of resulting in run time errors.
Hope this helps!

Related

Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 2 years ago.
My Swift program is crashing with a fatal error, saying that "Unexpectedly found nil while implicitly unwrapping an Optional value" even with the GUARD statement . Can anyone help to tell me why, and how do I fix it? The code as follows:
var page: Page? {
didSet{
guard let unwrappedPage = page else { return }
NameLabel.text = unwrappedPage.dishName
Image.image = UIImage(named: unwrappedPage.imageName)
contentText.text = unwrappedPage.ingredient
contentText.text = unwrappedPage.instruction
}
}
The issue is likely that the outlets have not been hooked up by the time you set page, and if these outlets are implicitly unwrapped optionals (with the ! after the type name, e.g. UILabel!), that will result in the error you describe. This problem will manifest itself if, for example, you set page before the view controller in question has been presented and all of the outlets have been hooked up.
So, I’d recommend:
Use optional chaining with your #IBOutlet references so it won’t fail if the outlets haven’t been hooked up yet.
Go ahead and keep your didSet observer on page, if you want, but make sure you also update the controls in viewDidLoad in case page was set before the outlets were hooked up.
For example:
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var imageView: UILabel!
#IBOutlet weak var ingredientLabel: UILabel!
#IBOutlet weak var instructionLabel: UILabel!
var page: Page? { didSet { updateControls(for: page) } }
override func viewDidLoad() {
super.viewDidLoad()
updateControls(for: page)
}
func updateControls(for page: Page?) {
nameLabel?.text = page?.dishName
imageView?.image = page.flatMap { UIImage(named: $0) }
ingredientLabel?.text = page?.ingredient
instructionLabel?.text = page?.instruction
}
Note, you only need this didSet observer if the page might be set (again) after the view has been presented. If not, the didSet observer is not needed.

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?

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
}

Swift Promises - Reload Data method is not binding the data

I'm using promises to retrieve information some methods via JSON. I'm filling the information with the function:
I'm trying to set those records in my TableView with:
#IBOutlet weak var usersTableView: UITableView!
var dataSource: [UserResponse]? {
didSet {
self.usersTableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadFriends()
// Do any additional setup after loading the view.
}
func loadFriends() {
//UserService.getFriends()
let (request, promise) = UserService.getFriends()
promise.then { user in
self.dataSource = user
}.catch{
error in
SCLAlertView().showError("Error", subTitle: error.localizedDescription)
}
}
But is returning the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
How I can fix this error?
Your usersTableView is an implicitly unwrapped optional (UITableView!). When you call the dataSource setter, the didSet property observer tries to call reloadData() on usersTableView, but it is nil, so you get a crash - you cannot call methods on nil.
It looks like you haven't connected the outlet to your usersTableView property in your storyboard. Alternatively, you're setting it to nil somewhere else in your code. This could be happening automatically if usersTableView isn't part of your view hierarchy when the view controller is loaded, since it is a weak variable, but I expect you have added it as a subview in the storyboard?

Declare a variable in ViewDidLoad and use elsewhere (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?