Getting the optional value inside an array - ios

Given an optional array of type Book, declared in a table view controller class:
var books: [Book]?
Later in cellForRowAtIndexPath I have:
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath)
let book = books?[indexPath.row]
cell.textLabel?.text = book?.title
return cell
}
Now, so far I have only seen this books array optional unwrapped using if let statement in order to loop through the array.
But why in cellForRow here do we not unwrap books and unwrap book instead of adding another ? after it, as in books?[indexPath.row] and cell.textLabel?.text = book?.title

You don't have to unwrap books or book because the text property of the UILabel is an optional String?. The only time you have to unwrap optionals is if you're using the retrieved value in some context that doesn't accept an optional. But text does, so your example operates fine.
My problem with this syntax is that, while it's a useful shorthand, this is a bit sloppy. If there is a programming problem (for example, books is nil when this is called even though that technically shouldn't be possible), the code in the question will silently continue executing and you will be left scratching your head, wondering why the label in the cell was blank. (This is also true for patterns that suggest using guard statements, quietly returning the cell if the unwrapping failed.)
But I strongly disagree with suggestions provided elsewhere that books should not be an optional. If this array cannot be populated at the time the view controller is instantiated, then books should be an optional (and be nil) until such time that this information is retrieved.
Bottom line, there is a difference between state of "books has not yet been set" and "books was populated, but no records were returned". This is the purpose of optionals, to avoid arbitrary sentinel values like "empty array" to indicate that books does not have its value set. (I am sympathetic to those who object to the sloppy overuse of optionals, but this simply is not one of those cases. And I'm not buying the Optional performance overhead concern, either, because it is immaterial in this example and it strikes me as a perfect example of premature optimization.)
I believe, instead, that you should (a) leave books optional; but (b) detect if it is unexpectedly nil and report this as the error it is, because if cellForRowAt is called, it must not be nil.
Thus, I would suggest:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BookCell", for: indexPath)
let book = books![indexPath.row]
cell.textLabel?.text = book.title
return cell
}
This accomplishes what we want, populating the label correctly, but will also immediately take the developer to the offending line of code if there was a code logic error and books was nil.
Now, many developers have a maniacal aversion to using the ! forced unwrapping operator. Personally I think it's fine because it's a fundamental programming error if books was nil when this method was called, and the forced unwrapping operator will stop my execution at the offending line and I'll know precisely what the issue is.
But, if you really want to avoid the forced unwrapping operator, !, then use a guard statement, triggering an informative fatalError if books is nil:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BookCell", for: indexPath)
guard let book = books?[indexPath.row] else {
fatalError("No book found")
}
cell.textLabel?.text = book?.title
return cell
}
The key is that if a situation arose where books was nil, you want to know immediately in the development process, not left guessing what the source of the problem was.
To be clear, this forced unwrapping and/or fatalError pattern should never be used in situations where the unwrapping could ever fail for reasons outside of your control. For example, don't use these patterns when parsing responses from remote web service or processing user input. You want to gracefully handle errors that might have resulted outside of your control. But if it is a true program error for a particular scenario to take place, then forced unwrapping and/or fatalError approaches are advisable so logic errors are immediately apparent, rather than forcing you to hunt around looking for the source of aberrant app behavior.

An optional unwrap using ? means that if the value exists, unwrap it. This is ok to do (safe to do) if you don't plan on using the potentially nil value elsewhere. In your case, you could technically do it this way (it will compile, and likely won't crash), but it's not the safest way, because now you're relying on each piece along the way to support a nil value.
That said, you don't really need to declare your array as optional. Because you're enumerating the number of rows of data anyway, the array should always exist. The variable is the number of rows it contains, not whether it exists or not. In other words: checking that the array exists on top of checking the number of rows is just extra work for the compiler and extra code for you to write.
You can also take advantage of guard too, reducing the amount of code that executes if you don't have good data:
// ...
guard let book = books[indexPath.row] else { return cell }
cell.textLabel?.text = book.title
return cell

Never declare a data source array of a non-optional table view or collection view as optional.
var books = [Book]()
That makes your life so much easier
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath)
let book = books[indexPath.row]
cell.textLabel?.text = book.title
return cell
}

Related

Convert the original tableview to collectionview data cannot be passed

I want to replace the original tableview with collectionview, the code of the original tableview:
let selectedRow = MarketView.indexPathForSelectedRow!.row
I'm learning online to change to this code and I get an error:
let selectedRow = MarketView.indexPathsForSelectedItems!.first
The error shows:
Cannot convert value of type 'IndexPath?' to expected argument type 'Int'
This is the complete code as shown in the figure
I just learned to use collectionview, how should I modify it, thank you for your help
Unlike indexPathForSelectedRow which returns a single index path indexPathsForSelectedItems returns an array of index paths
And row is not first, the collection view equivalent of row – as the name of the API implies – is item, you have to write indexPathsForSelectedItems!.first!.item.
But it's not recommended to force unwrap the objects. A safer way is
guard let selectedRow = MarketView.indexPathsForSelectedItems?.first?.item else { return }

swift Could not cast value of type

I have many TableViewCells, one is base class
BaseTableViewCell<T: BaseTableViewItem>: UITableViewCell{
public var item: T!
}
and another is subClass like
ATableViewCell: BaseTableViewCell<ATableViewItem>
BTableViewCell: BaseTableViewCell<BTableViewItem>
ATableViewItem and BTableViewItem is subclass of BaseTableViewItem
problem is
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: item.cellIdentifier)
(cell as! BaseTableViewCell).item.title = "title"
return cell!;
}
and crash in cell as! BaseTableViewCell
Could not cast value of type 'Example.ATableViewCell' (0x1078453f0) to 'Example.BaseTableViewCell
how can i do ?
ps:
I do this because i want to improve my project https://github.com/JavenZ/ZJTableViewManager, in this project, when i use custom cell, always write code like let item = self.item as! ZJTextItem, i think It's a little tricky to use.
so i try to use generic. It seems that I haven't found a good way. you can see demo in ZJTableViewManager.
I'm very sorry that my English is poor and I can't express myself clearly
It looks like you're trying to cast a subclass to its superclass, which is impossible. You can downcast (casting a superclass to its subclass) because the subclass has the same properties as its superclass, and thus all the information of an instance of the superclass can find a place in the instance of the subclass. However, the subclass can have more information in it than the superclass, so all of its information might not be able to find a place in the superclass.
I also don't see why this is necessary. Anywhere the type of an object is specified as one class, its subclasses can be used in its place. If you need to use an overridden method of the superclass from the subclass, you can always use the super keyword. I don't see why upcasting (subclass to superclass, what you are doing) is necessary. If you can clarify a reason, maybe I can help more, but you should avoid upcasting if it is unnecessary.
You can only cast things that are strict subclass/superclasses of each other.
For example, if I have
class MyCell: UITableViewCell { ... }
then I can say
let cell: UITableViewCell = MyCell() // Swift knows that MyCell inherits from `UITableViewCell`
and I can say
var someCell: UITableViewCell
...
let myCell = someCell as? MyCell // Swift knows that this might work
but you can't say something like:
let s: String = someCell
These types just aren't related.
In your case you have introduced generics, so even though the basic classes are in an inheritance chain, the addition of the generic variation means that are aren't compatible. I can't say something like:
let cell: ATableViewCell<Int> = ATableViewCell<String>()
They just aren't the same.
To do so would violate the Liskov Substitution Principle.
Consider a simple generic class:
class SomeClass<T> {
var item:T
init(item: T) {
self.item = item
}
}
let a = SomeClass<Int>(item: 10)
print(a.item+10) // Prints 20
let b = SomeClass<String>(item: "10")
print(b.item+10) // Compiler error - You can't add a string and an integer
You can see that I need to know the specific type that is used with the generic in order to understand what operations are possible. I can't assign an instance of SomeClass<String> to a variable declared as SomeClass<Int>; the set of valid operations are incompatible.
You could perhaps use a protocol with an associated type and then use type erasure so that you could use that protocol with a subclass, but at some point you need to know the concrete type of cell you are dealing with in a particular row and so you are going to need to cast the result of dequeueReusableCell to that concrete class. Generics and protocols probably just make things more complicated.

Closure does not run in the main thread in swift

I want to show some images on UITableViewCell. However I got an error below
fatal error: Index out of range. The problem is that closure does not run in the main thread probably. How can I solve this issue?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PickupTableViewCell", for: indexPath) as! PickupTableViewCell
APIManager.getAnotherArticle{ (articles: Array<Article>?) in
for info in articles! {
self.authorArray.append(info.author)
self.descriptionArray.append(info.description)
if info.publishedAt != nil {
self.publishedAtArray.append(info.publishedAt)
}
self.titleArray.append(info.title)
self.urlArray.append(info.url)
self.urlToImageArray.append(info.urlToImage)
print(self.authorArray)
}
}
let program = urlToImageArray[indexPath.row] //index out of range
let urlToImage = NSURL(string: program)
cell.pickupImageView.sd_setImage(with: urlToImage as URL!)
return cell
}
Wrap anything you want to run on the main queue in DispatchQueue.main.async{ ... }.
That said, your current approach likely won't work. This method gets called a lot. While the user is scrolling, this method gets called every time a cell is about to come on the screen (in iOS 10, sometimes a bit before it'll come on the screen). Cells are often recycled, and you're appending data to the titleArray and other arrays every time a cell is requested (they may not be in order; they might have already been fetched; this array isn't going to wind up in the right order).
You need to move all your data about a cell into a model object and out of the view controller. There shouldn't be a titleArray and an urlArray, etc. There should just be an Article, and the Article should take care of fetching itself and updating its properties. And the job of this method is to fetch the correct Article from your cache, or create a new one if needed, and assign it to an ArticleCell. The ArticleCell should watch the Article and update itself any time the Article changes (i.e. when the fetch completes). Almost no work should happen directly in this method since it gets called so often, and in possibly random orders.
The common way to build this kind of thing is with a simple model object (often a reference type so it can be observed; there are many other approaches that allow a struct, but they're a little more advanced so we'll keep this simple):
class Article {
var author: String
var description: String
var publishedAt: Date
var title: String
var url: URL
var image: UIImage
func refresh() {
// fetch data from server and replace all the placeholder data
}
}
Then there's some kind of Model that vends these:
class Model {
func article(at index: Int) -> Article {
if let article = lookupArticleInCache(at: index) {
return article
}
let article = createAndCachePlaceholderArticle(at: index)
article.refresh()
}
}
And then your code looks like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PickupTableViewCell", for: indexPath) as! PickupTableViewCell
cell.article = sharedModel.article(at: indexPath.row)
return cell
}
You can use KVO or Swift Observables or an ArticleDelegate protocol to let the cell observe the Article. When the Article updates, the cell updates itself.
Again, there are many ways to approach this. You could have a "PlaceHolderArticle" that all the cells share and when the real Article comes in, the cell replaces the whole thing (so that Articles are immutable rather than self-updating). You could use the more generic approaches described by Swift Talk. There are lots of ways. But the key is that there is this model that updates itself, independent of any particular UI, and a UI (views, view controllers) that watch the model and display what it holds.
If you want much, much more on this topic, search for "Massive View Controller." That's the common name for the anti-pattern you're currently using. There are lots of ways to fight that problem, so don't assume that any particular article you read on it is "the right way" (people have come up with some very elaborate, and over-elaborate, solutions). But all of them are based on separating the model from the UI.
APIManager.getAnotherArticle{ (articles: Array<Article>?) in
for info in articles! {
self.authorArray.append(info.author)
self.descriptionArray.append(info.description)
if info.publishedAt != nil {
self.publishedAtArray.append(info.publishedAt)
}
self.titleArray.append(info.title)
self.urlArray.append(info.url)
self.urlToImageArray.append(info.urlToImage)
print(self.authorArray)
}
}
you have to make separate function for this calculation and try to avoid the any calculate functionality in "cellForRowAt"

Parse.com query not working properly

I am trying to filter the posts based on their profile. For instance, when I go to my profile I only want to see my posts, not all the posts in my database. I attempted to make a filter for that but the code below does not seem to work and I am unsure as to why that is. It may be something obvious but I can not seem to pinpoint the issue, any ideas?
I have attached a picture of the database to further assist anybody.
The code runs perfectly fine it just does not filter the usernames.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var user = PFUser.currentUser()?.username!
let bucketCellObj = tableView.dequeueReusableCellWithIdentifier("bucket", forIndexPath: indexPath) as! BucketTableViewCell
var query = PFQuery(className: "Bucket")
query.whereKey("creator", equalTo: user!)
query.findObjectsInBackgroundWithBlock { (PFObject, error) -> Void in
if error == nil {
bucketCellObj.bucketname.numberOfLines = 0
bucketCellObj.username.text = self.bucketsArray[indexPath.row]["creator"] as? String
bucketCellObj.bucketname.text = self.bucketsArray[indexPath.row]["name"] as? String
bucketCellObj.selectionStyle = .None
} else {
print("ERROR")
}
}
return bucketCellObj
}
What you are doing here might work under some circumstances but will certainly bite you at some point.
What your code does:
show some arbitrary number of cells - probably based on self.bucketsArray.count or something similar
in each cell creation, run a parse query
when the query returns, customize the already displayed cell accordingly - without any usage of the requested query response
That will cause problems for a couple of reasons:
you perform too many requests, each cell requests some data, each new displayed cell requests its own data again
you display the old data in the cell until the new data is fetched which could take a few seconds due the amount of requests
you could encouter a problem where you requests some data for a cell, that cell moves off-screen, gets reused, then the first query returns, still holds the reference to it and will therefore display wrong data
How it can be solved
Do not requests the data in the cellForRowAtIndexPath.
Request the data once in viewDidLoad or similar. as soon as the data gets returned, parse it and initiate a tableView.reload().
In the cellForRowAtIndexPath make use of the already retrieved data, do not perform anymore async tasks.

How to better understand optionals

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if (segue.identifier == "segueone")
{
let cellIndexPath = self.tableView!.indexPathForCell(sender as UITableViewCell)
if let unwrappedCellindexPath = cellIndexPath
{
var nextVC = (segue.destinationViewController as TableTwo)
nextVC.items = items[unwrappedCellindexPath.row]
}
}
}
With this piece of code, I have a few questions regarding the optionals. I recently read-through the apple developer web document as well as a few personal explanations of optionals but still have question.
Anyways,
in the line
let cellIndexPath = self.tableView!.indexPathForCell(sender as UITableViewCell)
Is this statement only considered to be an optional because a user may not select one of the cells in my table? And with that, since I know that as long as a user wants to continue through the app, they must select a cell, I can place the exclamation point in to notify the compiler that this cell does in deed have a value(index path)?
Why does the exclamation point go after "self.tableview" and not after "sender as UITableView) in parentheses?
If my assuming is correct, I am able to use the if let syntax because I have an optional in the previous line of code?
let cellIndexPath = self.tableView!.indexPathForCell(sender as UITableViewCell)
Uses an exclamation point after tableView, because self.tableView! may not have been set (it may be nil).
It is an optional because it has the option to have a value, and it also has the option to be nil. Variables that are not optionals cannot be nil, they have to have a value.
The exclamation point goes after tableView because that is the optional which could be nil. If it went after .indexPathForCell(sender as UITableViewCell), that would imply that the value returned could be nil.
You can use the if let syntax because of this optional. This will return true if the value can be assigned to the variable. For example:
var myNilValue: String?
if(let nonNilValue = myNilValue){
//this will not be called
}
if let will return false, but:
var myNonNilValue: String? = "Hello, World!"
if(let nonNilValue = myNilValue){
//this will be called
}
Will return true.
Question marks ? are used after variable declarations to define that the variable may not have a value, and it may never have a value. They need to be unwrapped by using an exclamation point ! after the variable name. This can be used for results from a database, in case there is no result.
Exclamation points ! are used after variable declarations to define that the variable may not have a value, but it will have a value when you need to access it. They do not need to be unwrapped, but the compiler will throw an error if the variable is nil at the time of accessing it. This is typically used in #IBOutlets or #IBActions which are not defined until the program compiles
Both self.tableView and indexPathForCell() are optionals. By banging one, you only have one optional to dereference in the result. sender is also an optional and indexPathForCell() doesn't take an optional so it probably needs a bang too, but I haven't compiled it so I can't say for sure what the compiler will do.
self.tableView is reference to a property that might not have been set (e.g. is nil), so it is an optional. You can declare it for example an #IBOutlet with a ! at the end if you know it will ALWAYS be defined, such as from the Storyboard, and then you don't have to bang dereference it later.
A common thing to do is:
#IBOutlet var tableView : UITableView!
That's an implicitly unwrapped optional, and will generate an error if it's referenced when nil. But you don't need to use a ! to dereference.
Correct. The result of the line will produce an optional that you can test with if let

Resources