I'm playing around with the Master-Detail iOS project in Xcode6.01 and Swift. I have a simple data entry screen that adds swift dictionary objects to a Swift datasource array for the table.
The data entry and table view work fine. I've run into this problem when tapping on a cell, extracting a dictionary object from the array and trying to pass that dict object along to the detail view.
The configureView() function in the detail view is causing an exception. Here's the code:
func configureView() {
if let dictObject:Dictionary = detailItem as? Dictionary<String,String> {
if var strName = dictObject["name"] {
// prints fine:
println(strName)
// fatal error
self.detailDescriptionLabel.text = strName
// debug output:
// prints the string in strName (no mention of optional)
// assigning to label:
// fatal error: unexpectedly found nil while unwrapping an Optional value
}
}
}
It seems strange that the println() statement has no problem with the strName variable and there is no mention of it being an optional in the output panel. As soon as it tries to assign the string to the label, bang - crash.
Found the answer my self. It was not the dictionary object that was nil, but the labels in the view!
After adjusting the code with an IF LET construct for the labels it works like this:
func configureView() {
if let dictObject:Dictionary = detailItem as? Dictionary<String,String> {
if let dLabel = detailDescriptionLabel {
dLabel.text = dictObject["name"]
}
if let eLabel = emailLabel {
eLabel.text = dictObject["email"]
}
}
}
I must admit though, I don't know why this is needed. I thought the storyboard would init the labels. The labels in question are regular IBOutlets:
#IBOutlet weak var detailDescriptionLabel: UILabel!
#IBOutlet weak var emailLabel: UILabel!
Related
I am using some values to perform some calculations. For testing purposes I show in Label1 a value as string, since it is stored as a string and in Label2 I show a casted value as a Double since I need them at the end as doubles for my calculations.
The weird thing is, that when I access the ViewController the first time it doesn't show any values. But if I go back and klick on it again using the navigation controller it actually works. But I need the values right away cause my original intention is as I said, not showing some labels but rather making some calculations with it.
I made a little gif to show you what the problem is but I have problem with adding photos. Basically what happens is, that I click on the ViewController with the labels and nothing is showed. I go back and press again and the values will be showed in the labels.
Why is that and how can it be showed right away/ used for calculations right away
Thanks for the help. :)
class AHPfinalPreferencesViewController: UIViewController {
var ahpPrios = [AHPPriorityStruct]()
let decoder = JSONDecoder()
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
let ajkpXc = globaLajkpXc
let ajkpXijr = globaLajkpXijr
let valueA = globaLajkpXc
let valueB = Double(globaLajkpXijr)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UserService.ahpPref(for: User.current) { (ahpPrios) in
self.ahpPrios = ahpPrios
print("This is our AHP PRIOS", ahpPrios)
for ahpPrio in ahpPrios {
print(ahpPrio)
}
print("this is the global ajk. ", self.ajkpXc)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Mark: - Get Data
label1.text = valueA
label2.text = "\(String(describing: valueB))"
// MARK: - Set Values for calculation
// setValues()
// ahpCalculation()
}
}
Could it be because of the globalVariables? I know that it is not the right way to do it but for my purposes its absolutely "okay"
import Foundation
import FirebaseAuth.FIRUser
import FirebaseDatabase
import FirebaseUI
import FirebaseAuth
import CodableFirebase
var globaLajkpXc: String = String()
var globaLajkpXijr: String = String()
var globaLajkpXqpa: String = String()
struct UserService {
static func ahpPref(for user: User, completion: #escaping ([AHPPriorityStruct]) -> Void) {
let ref = Database.database().reference().child("AHPRatings").child(user.uid)
ref.observe(DataEventType.value, with: { snapshot in
guard let value = snapshot.value else { return }
do {
let ahpPrios = try FirebaseDecoder().decode(AHPPriorityStruct.self, from: value)
print(ahpPrios)
// MARK: - lets store the values in the actual constants :)
let ajkpXc = ahpPrios.ajkpXc
let ajkpXijr = ahpPrios.ajkpXijr
let ajkpXqpa = ahpPrios.ajkpXqpa
globaLajkpXc = ajkpXc ?? "no Value"
globaLajkpXijr = ajkpXijr ?? "no Value"
globaLajkpXqpa = ajkpXqpa ?? "no Value"
} catch let error {
print(error)
}
})
}
}
[1]: https://i.stack.imgur.com/VKxaE.png
You are calling UserService's ahpPref in your controller's viewWillAppear. BUT you are attempting to put your valueA (globaLajkpXc's value) to your label in your controller's viewDidLoad.
So what does that mean? Do you know which of these two controller's life cycle method gets called and when they do get called?
To solve your problem, have your label assigning value code
label1.text = globaLajkpXc
move in the completion block of your ahpPref (in the viewWillAppear).
Here's the Apple's documentation about the UIViewController's lifecycle: https://developer.apple.com/library/archive/referencelibrary/GettingStarted/DevelopiOSAppsSwift/WorkWithViewControllers.html
Also, below this line: globaLajkpXqpa = ajkpXqpa ?? "no Value"
add your completion call, like:
completion([ahpPrios]).
This should make my answer above work.
In the prepare segue, I add data to a dictionary in the other controller, here is that dictionary:
var data = [String:String]()
and in prepare segue I add value and key to the dictionary
movieDetail.data.updateValue("title", forKey: self.animationItems[index!].snippet.title)
movieDetail.data.updateValue("description", forKey: self.animationItems[index!].snippet.description)
Is it right?
Then, there is an outlet in the new controller:
#IBOutlet weak var movieTitle: UILabel!
this title should get the value(title) of the dictionary
I did that:
override func viewDidLoad() {
super.viewDidLoad()
movieTitle.text = data[title]?
}
but it shows an error:
Cannot subscript a value of type '[String : String]' with an index of type 'String?'
Could you help me on that?
It should have been:
override func viewDidLoad() {
super.viewDidLoad()
movieTitle.text = data["title"]
}
You should have added the "" to denote a string literal.
Your prepareForSegue is also wrong. The two arguments for updateValue are the other way around. To avoid confusion, you should just always use the dict[key] = value syntax:
movieDetail.data["title"] = self.animationItems[index!].snippet.title
movieDetail.data["description"] = self.animationItems[index!].snippet.description
Anyway, you should use a class/struct to represent your movies, then these problems will go away.
struct Movie {
var title: String
var description: String
}
// in MovieDetailController
var movie: Movie!
// in prepareForSegue
movieDetail.movie = Movie(title: self.animationItems[index!].snippet.title, description: self.animationItems[index!].snippet.description)
// in viewDidLoad
movieTitle.text = movie.title
Yes, another found nil while unwrapping an Optional value error. I have read tons of other stack overflow posts with similar errors such as this one and many others. I still do not fully understand how to properly deal with unwrapping a variable.
I have a class that is similar to the following:
#IBOutlet weak var nameTextField: UITextField?
#IBOutlet weak var valueInput: UITextField?
var checkbox : CheckBox?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let name = nameTextField.text ?? ""
let state = buttonState.getIsChecked()
let value : Int? = Int(valueInput.text!)
let isMoveable = true
checkbox = CheckBox(name: name, value: value, state: state, isMoveable: isMoveable)
}
I get the error on the line the "let value : Int? = Int(valueInput.text!) line.
You can safely unwrap the value using if let construct
var value : Int? = nil
if let val = valueInput.text {
value = Int(val) // it would either nil or optional value
}
and also you can do it by nil coalescing operator ?? in a single line
let value : Int? = Int(valueInput.text ?? "")
UPDATE
First check if textfields disconnected from the Interface Builder , if not connect, connect them. and if you become your textfields optionals you also have to safely unwrap the textfields ( you forgot to add it from interface builder and it will not crash if you make them optionals).
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var name: String? = nil
var value : Int? = nil
let state = buttonState.getIsChecked()
let isMoveable = true
if let nameTextField = self.nameTextField where nameTextField.text != nil {
name = nameTextField.text!
}
if let valueTextField = self.valueInput where valueTextField.text != nil {
value = Int(valueTextField.text!)
}
checkbox = CheckBox(name: name, value: value, state: state, isMoveable: isMoveable)
}
Sahil's answer is on track, but doesn't address that valueInput is also an optional. Use this code:
if let valueInput = valueInput,
let val = valueInput.text {
value = Int(val)
}
In addition regarding properly unwrapping the optional valueInput I wanted to add that chances are that if valueInput is an IBOutlet it's defined as:
#IBOutlet weak var valueInput: UITextField!
That's called an implicitly unwrapped optional. The annoying thing is that since it is also an IBOutlet, if it ever becomes disconnected from the UITextField in Interface Builder, the variable will become nil, and accessing it from anywhere in the code will cause a crash.
Change it to this:
#IBOutlet weak var valueInput: UITextField?
I've also written about this on my blog: http://cleanswifter.com/implicitly-unwrapped-iboutlets/
Note: you didn't show the definition of valueInput so I assumed it to be a UITextField
I have a dummy question...
in one of my ViewController, I set a UILabel text with a String var coming from a singleton:
var nom: String!
var prenom: String!
override func viewDidLoad() {
.....
self.nom = User.sharedInstance.nom
self.prenom = User.sharedInstance.prenom
....
print("User: \(self.nom) - \(self.prenom)")
self.labelWelcome.text = ("Bienvenue \(self.prenom) \(self.nom)")
the print is displaying the right user value, but my View is displaying Bienvenue nil nil....
any idea ?
try this:
if let nom1 = self.nom {
print("User: \(nom1)")
}
I have been using this following thing to do my plist and save data and display contents of plist in a Table View. I am using a NSMutableArray to fetch the contents of the array and display it in the table view . VC1 is my table view & VC2 is my EnterDetail VC So I have declared my code as follows -
class ListTableViewController: UITableViewController
{
var arr = NSMutableArray()
var plistfinalpath = String()
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
override func viewDidAppear(animated: Bool)
{
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, .UserDomainMask, true)[0]
plistfinalpath = path.stringByAppendingString("/login.plist")
print(plistfinalpath)
arr = (NSMutableArray(contentsOfFile: plistfinalpath))!
print("time of appearing\(arr)")
/* if let temparray = NSMutableArray(contentsOfFile: plistfinalpath)
{
arr = temparray
}*/
self.tableView.reloadData()
}
So as you can see that I have declared a 'arr' as a NSMutableArray type and it is not optional. And when I use in my didappear() the compiler forces me to force unwrap it. Why is that so why does it happens evenif I have declared 'arr' as non optional? And here comes the real problem whenever I run my app for the first time the plist is empty implies 'arr' is also empty. And since I have used forced unwrapping my app crashes due to fatal error. And if I uncomment my commented line in the above code, the if let part, it works fine . So any suggestions. Thanks.
there is no trouble with your NSMutableArray.
var arr = NSMutableArray()
create non optional instance of an array. so far, so good ... An expression
NSMutableArray(contentsOfFile: plistfinalpath)
can return nil, so it returns the optional value. Here you try to create another instance of NSMutableArray and assign the result to your var arr.
A mutable array containing the contents of the file specified aPath.
Returns nil if the file can’t be opened or if the contents of the file
can’t be parsed into a mutable array.
Probably the best way for you is something like
import Foundation
var arr = NSMutableArray(contentsOfFile: "somePath") ?? NSMutableArray()
Here your variable is guaranty to have the default value (an empty instance of NSMutableArray) if the file does not exist yet (or other failure happened)
To resolve your fatal error check your file exist or not :-
//Check whether your file exist or not
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, .UserDomainMask, true)[0]
plistfinalpath = path.stringByAppendingString("/login.plist")
if NSFileManager.defaultManager().fileExistsAtPath(plistfinalpath)
{
arr = (NSMutableArray(contentsOfFile: plistfinalpath))!
}
Declare it like
var arr : NSMutableArray = []
// Or
var arr : NSMutableArray!