In the cellForRowAtIndexPath method, I have created a custom cell. The cell has a label to display project name. After assigned value to the label, it shows nil value when print. so that label could not display in the table view.
let cell = self.LeadsListTable.dequeueReusableCell(withIdentifier:"LeadsID", for: indexPath) as! LeadsCell
if let leadsDict: NSDictionary = self.leadsArray[indexPath.row] as? NSDictionary
{
cell.project_title?.text = (leadsDict.object(forKey: "project_title") as! String)
}
This way i used to display the value of the label
po print(cell.project_title?.text) (or) print(cell.project_title?.text)
If the print statement returns nil, that could only mean cell.project_title is nil, since neither cell nor .text are optionals. Ensure that you initialize the .project_title property before you try setting its .text property.
#A.Yesuvin
put this line above your optional binding (if let) statement
cell.project_title?.text = "test_text"
If then when you print print(cell.project_title?.text) and it's "test_text", then you know you're not allowed to do this : self.leadsArray[indexPath.row] as? NSDictionary
If that was the case then try do what #vadian said and use native Swift Dictionary ([String:Any]).
If the code in the if statement actually runs, then double check if the key "project_title" is actually in the dictionary.
If thats the case, then ...well, fix it.
Also just a hint: Continue to use 'optional binding' instead of force unwrapping/ force cast cause you will eventually run into errors doing this: (leadsDict.object(forKey: "project_title") as! String
For all we know, This might just be where the "error/nil" lays.
Related
I'm trying to migrate an objc project to swift3. I'm not sure how can I compare an array to nil. I have found this topic, but that was 2 years ago and the swift's syntax has changed a lot.
If I have a code like this in swift:
let variable = something as? NSArray
if variable == nil {
// do something
}
It won't let me to compare this variable with nil, causing an error "comparing this variable, always returns false". I have tried comparing variable.description with " ", but does it do the same thing?
By "something" i meant:
var variable = dict.object(forKey: someString) as! NSArray
The main thing I wanted to do with this was:
var variable = dict.object(forKey: someString) as! NSArray
if variable == nil {
//create
}
else {
// append
}
That's what the optional unwrapping syntax is for. You can combine the unwrapping and cast into one if statement:
if let variable = something as? NSArray {
// variable is not nil and is an NSArray
// Now you can do something with it.
} else {
// Either something is nil or it is not able to be cast as an NSArray
// Handle this case.
}
I should also mention that if you don't need to use something in Objective-C, then you should use the Swift-native array type. This can be declared like this:
let someArray = ["string1", "string2"]
This line indicates that variable is and must be an NSArray. If dict.object(forKey: someString) is not an NSArray, this will cause a crash
var variable = dict.object(forKey: someString) as! NSArray
// ^
// This exclamation mark means you are certain this is an NSArray
// Also, because there is no question mark after NSArray, this variable
// is not optional. It cannot be nil
However, you then use
if variable == nil {
And this is where the warning comes from. The variable can never be nil, because the variable is not optional
What you probably want is:
if let variable = dict.object(forKey:someString) as? NSArray
This will return false if:
dict.object(forKey:someString) returns a nil object
the object returned is not an NSArray
After this variable is now a non-optional NSArray. It is guaranteed to be an NSArray and is guaranteed to not be nil. You can use it without unwrapping it. e.g.
if let variable = dict.object(forKey:someString) as? NSArray {
for element in variable {
}
}
else {
//The dict doesn't contain the object yet. `variable` is nil
//Create a new array and add it to dict
let newArray = ["First Value"]
dict[someString] = newArray
}
let variable = something as? NSArray
With this declaration, variable will be an optional type (NSArray?) and never nil. This is because casting with as? returns an optional value that either contains the successfully casted object or nothing. You can see this by alt-clicking the variable name in Xcode.
If you want to know whether it contains a value, you need to use the if let syntax:
if let variable = variable {
// variable is guaranteed to be an NSArray here.
}
You can also use this format with guard-else:
guard let variable = something as? NSArray else {
// your variable is nil. Do something if needed
}
// your variable is available in this scope. Do something when variable contains Array
I started to use swiftLint and noticed one of the best practices for Swift is to avoid force cast. However I used it a lot when handling tableView, collectionView for cells :
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as! MyOffersViewCell
If this is not the best practice, what's the right way to handle this? I guess I can use if let with as?, but does that mean for else condition I will need to return an empty cell? Is that acceptable?
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as? MyOffersViewCell {
// code
} else {
// code
}
This question is probably opinion based, so take my answer with a grain of salt, but I wouldn't say that force downcast is always bad; you just need to consider the semantics and how that applies in a given situation.
as! SomeClass is a contract, it basically says "I guarantee that this thing is an instance of SomeClass". If it turns out that it isn't SomeClass then an exception will be thrown because you violated the contract.
You need to consider the context in which you are using this contract and what appropriate action you could take if you didn't use the force downcast.
In the example you give, if dequeueReusableCellWithIdentifier doesn't give you a MyOffersViewCell then you have probably misconfigured something to do with the cell reuse identifier and an exception will help you find that issue.
If you used a conditional downcast then you are going to get nil and have to handle that somehow - Log a message? Throw an exception? It certainly represents an unrecoverable error and something that you want to find during development; you wouldn't expect to have to handle this after release. Your code isn't going to suddenly start returning different types of cells. If you just let the code crash on the force downcast it will point straight to the line where the issue occurred.
Now, consider a case where you are accessing some JSON retrieved from a web service. There could be a change in the web service that is beyond your control so handling this more gracefully might be nice. Your app may not be able to function but at least you can show an alert rather than simply crashing:
BAD - Crashes if JSON isn't an array
let someArray=myJSON as! NSArray
...
Better - Handle invalid JSON with an alert
guard let someArray=myJSON as? NSArray else {
// Display a UIAlertController telling the user to check for an updated app..
return
}
Update
After using Swiftlint for a while, I am now a total convert to the Zero Force-Unwrapping Cult (in line with #Kevin's comment below).
There really isn't any situation where you need to force-unwrap an optional that you can't use if let..., guard let... else, or switch... case let... instead.
So, nowadays I would do this:
for media in mediaArray {
if let song = media as? Song {
// use Song class's methods and properties on song...
} else if let movie = media as? Movie {
// use Movie class's methods and properties on movie...
}
}
...or, if you prefer the elegance and safety of an exhaustive switch statement over a bug-prone chain of if/elses, then:
switch media {
case let song as Song:
// use Song class's methods and properties on song...
case let movie as Movie:
// use Movie class's methods and properties on movie...
default:
// Deal with any other type as you see fit...
}
...or better, use flatMap() to turn mediaArray into two (possibly empty) typed arrays of types [Song] and [Movie] respectively. But that is outside the scope of the question (force-unwrap)...
Additionally, I won't force unwrap even when dequeuing table view cells. If the dequeued cell cannot be cast to the appropriate UITableViewCell subclass, that means there is something wrong with my storyboards, so it's not some runtime condition I can recover from (rather, a develop-time error that must be detected and fixed) so I bail with fatalError().
Original Answer (for the record)
In addition to Paulw11's answer, this pattern is completely valid, safe and useful sometimes:
if myObject is String {
let myString = myObject as! String
}
Consider the example given by Apple: an array of Media instances, that can contain either Song or Movie objects (both subclasses of Media):
let mediaArray = [Media]()
// (populate...)
for media in mediaArray {
if media is Song {
let song = media as! Song
// use Song class's methods and properties on song...
}
else if media is Movie {
let movie = media as! Movie
// use Movie class's methods and properties on movie...
}
Others have written about a more general case, but I want to give my solution to this exact case:
guard let cell = tableView.dequeueReusableCell(
withIdentifier: PropertyTableViewCell.reuseIdentifier,
for: indexPath) as? PropertyTableViewCell
else {
fatalError("DequeueReusableCell failed while casting")
}
Basically, wrap it around a guard statement and cast it optionally with as?.
"Force Cast" has its place, when you know that what you're casting to is of that type for example.
Say we know that myView has a subview that is a UILabel with the tag 1, we can go ahead and force down cast from UIView to UILabel safety:
myLabel = myView.viewWithTag(1) as! UILabel
Alternatively, the safer option is to use a guard.
guard let myLabel = myView.viewWithTag(1) as? UILabel else {
... //ABORT MISSION
}
The latter is safer as it obviously handles any bad cases but the former, is easier. So really it comes down to personal preference, considering whether its something that might be changed in the future or if you're not certain whether what you are unwrapping will be what you want to cast it to then in that situation a guard would always be the right choice.
To summarise: If you know exactly what it will be then you can force cast otherwise if theres the slightest chance it might be something else use a guard
As described in some casting discussions, forcing the cast for tableView.dequeueReusableCell is ok and can/should be done.
As answered on the Swiftlint Github site you can use a simple way to turn it off for the table cell forced cast.
Link to Swiftlink issue 145
// swiftlint:disable force_cast
let cell = tableView.dequeueReusableCell(withIdentifier: "cellOnOff", for: indexPath) as! SettingsCellOnOff
// swiftlint:enable force_cast
When you are working with your types and are sure that they have an expected type and always have values, it should force cast. If your apps crash you can easily find out you have a mistake on which part of UI, Dequeuing Cell, ...
But when you are going to cast types that you don't know that is it always the same type?
Or is that always have value?
You should avoid force unwrap
Like JSON that comes from a server that you aren't sure what type is that or one of that keys have value or not
Sorry for my bad English I’m trying to improve myself
Good luck🤞🏻
In cases where you are really sure the object should be of the specified type it would be OK to down cast. However, I use the following global function in those cases to get a more meaningful result in the logs which is in my eyes a better approach:
public func castSafely<T>(_ object: Any, expectedType: T.Type) -> T {
guard let typedObject = object as? T else {
fatalError("Expected object: \(object) to be of type: \(expectedType)")
}
return typedObject
}
Example usage:
class AnalysisViewController: UIViewController {
var analysisView: AnalysisView {
return castSafely(self.view, expectedType: AnalysisView.self)
}
override func loadView() {
view = AnalysisView()
}
}
In Swift guide that as published on ibooks, as! operator was not mentioned. But in online reference and in some example code, they (i mean Apple in both cases) used as! operator.
Is there a difference between as and as! operators? If there are, can you explain please?
edit: Im so tired that i wrongly typed "is", instead of "as". That is now corrected...
as? will do an optional downcast - meaning if it fails it will return nil
so "Blah" as? Int will return Int? and will be a nil value if it fails or an Int if it does not.
as! forces the downcast attempt and will throw an exception if the cast fails. Generally you will want to favour the as? downcast
//ex optional as?
let nine = "9"
if let attemptedNumber = nine as? Int {
println("It converted to an Int")
}
//ex as!
let notNumber = "foo"
let badAttempt = notNumber as! Int // crash!
( You may find that you that an update is sitting there for the swift guide. It is mentioned for sure in the online version https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html )
operator is the forcefully unwrapped optional form of the as? operator. As with any force unwrapping though, these risk runtime errors that will crash your app should the unwrapping not succeed.
Further, We should use as to upcast if you wish to not write the type on the left side, but it is probably best practice to write it with normal typing as shown above for upcasting.
Example:
You use the as keyword to cast data types. UIWindow rootViewController is of type UIViewController. You downcast it to UISplitViewController.
Another better example can be taken as follows.
var shouldBeButton: UIView = UIButton()
var myButton: UIButton = shouldBeButton as UIButton
The as? operator returns an optional, and then we use optional binding to assign it to a temporary constant, and then use that in the if condition, like we are doing in the below example.
let myControlArray = [UILabel(), UIButton(), UIDatePicker()]
for item in myControlArray
{
if let myLabel = item as? UILabel
{
var storeText = myLabel.text
}
else if let someDatePicker = item as? UIDatePicker
{
var storeDate = someDatePicker.date
}
}
Before I upgraded to Swift 1.2, I could write the following line:
if let width = imageDetails["width"] as Int?
Now it forces me to write this line:
if let width = imageDetails["width"] as! Int?
My question is, if I'm forced to write it as above, couldn't I just write the code below and it would do the same thing? Would it give me the same result in all values of imageDetails?
if let width = imageDetails["width"] as Int
The as keyword used to do both upcasts and downcasts:
// Before Swift 1.2
var aView: UIView = someView()
var object = aView as NSObject // upcast
var specificView = aView as UITableView // downcast
The upcast, going from a derived class to a base class, can be checked at compile time and will never fail.
However, downcasts can fail since you can’t always be sure about the specific class. If you have a UIView, it’s possible it’s a UITableView or maybe a UIButton. If your downcast goes to the correct type – great! But if you happen to specify the wrong type, you’ll get a runtime error and the app will crash.
In Swift 1.2, downcasts must be either optional with as? or “forced failable” with as!. If you’re sure about the type, then you can force the cast with as! similar to how you would use an implicitly-unwrapped optional:
// After Swift 1.2
var aView: UIView = someView()
var tableView = aView as! UITableView
The exclamation point makes it absolutely clear that you know what you’re doing and that there’s a chance things will go terribly wrong if you’ve accidentally mixed up your types!
As always, as? with optional binding is the safest way to go:
// This isn't new to Swift 1.2, but is still the safest way
var aView: UIView = someView()
if let tableView = aView as? UITableView {
// do something with tableView
}
Got this from a site: SOURCE
as
In Swift 1.2 and later, as can only be used for upcasting (or disambiguation) and pattern matching:
// 'as' for disambiguation
let width = 42 as CGFloat
let block = { x in x+1 } as Double -> Double
let something = 3 as Any? // optional wrapper can also be added with 'as'
// 'as' for pattern matching
switch item {
case let obj as MyObject:
// this code will be executed if item is of type MyObject
case let other as SomethingElse:
// this code will be executed if item is of type SomethingElse
...
}
as?
The conditional cast operator as? tries to perform a conversion, but returns nil if it can't. Thus its result is optional.
let button = someView as? UIButton // button's type is 'UIButton?'
if let label = (superview as? MyView)?.titleLabel {
// ...
}
as!
The as! operator is for forced type conversion.
Use the forced form of the type cast operator (as!) only when you are sure that the downcast will always succeed. This form of the operator will trigger a runtime error if you try to downcast to an incorrect class type.
// 'as!' for forced conversion.
// NOT RECOMMENDED.
let buttons = subviews as! [UIButton] // will crash if not all subviews are UIButton
let label = subviews.first as! UILabel
The correct idiom that should do exactly what you want (in all versions of Swift at least upto and including 1.2) is the as? optional cast.
if let width = imageDetails["width"] as? Int
The optional cast returns an optional (Int? in this case) and is tested at runtime. Your original code probably forced a cast to the optional type.
I am trying to send pictures to a collection view, similar to the way you would do in a to do list tutorial, but I am working with images, not text. I get an error in the code where I say:
Picture.append(picture.image)
The error says that [String] does not have a member named 'image'.
How should I approach this if I cant use a string? What other type should I use?
I am also getting an error in the cellForItemAtIndexPath section. This is my code:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as Cell
cell.ImageView.image = UIImage(named: picture[indexPath.row])
return cell
}
In this I am getting an error that saying as Cell at the end of the first line is undeclared, but I am not sure where or how I am supposed to declare this, since I have other projects with Collection Views and I didn't need to declare anything.
Please let me know if you can help, Thanks!
I believe I see the source of the errors. I'll cover each error below, and suggestions on how to alleviate them.
Picture.append(picture.image)
Error: [String] does not have a member named 'image'.
Since you are acquiring the member "image" via dot-notation from the object "picture", that error suggests that "picture" (the lowercase one) is an array of standard Swift Strings.
Swift Strings do not have a property named "image" in them, so the error is pointing that out. If you just want the filename for your images, it can be left as a String.
Then, when you call it in your next Snippet of code, it just pulls out the filename String and passes it to that UIImage initializer.
From looking at your code in the next snippet, I think you might actually have meant something like this:
let image = "someImage.png" //The filename for the image to append to the array.
var picture = [String]() //An array of Strings to hold the filenames to the images.
picture.append(image)
In this case we have an array called "picture", and then we append the filename String (stored as the String named "image") to this array.
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as Cell
Error: Use of undeclared type 'Cell'
This says that the Cell type is undeclared. I have not used UICollectionView yet, but from my understanding, its data type is a UICollectionViewCell, not just "Cell" (the capital one). You would probably have to type that as a UICollectionViewCell.
From my research, it appears that a UICollectionViewCell does not have a member named ImageView. In that case, you could write a custom subclass of UICollectionViewCell named Cell, and give it a property named ImageView, like:
class Cell: UICollectionViewCell
{
var imageView = UIImageView()
}
cell.imageView.image = UIImage(named: "someImage.png")
One note about my code above, I renamed the property to "imageView" (now starting with lowercase 'I'). Apple suggests that we name only Types (like UICollectionViewCell or String) with capital letters at the beginning. Functions and properties should start with lowercase letters to conform to that coding standard.
Then you can wire your imageView from the storyboard or other part of code to this property, and set your UICollectionViewCell to this "Cell" type, and set the imageView property.
For more information on using UICollectionViews, take at look at Brian J Coleman's post "Tutorial: Collection View Using Swift"
I hope my answer has been of help. Good luck on learning Swift!