I would really like to use a more simple classic try catch block in my Swift code but I can't find anything that does it.
I just need:
try {
// some code that causes a crash.
}
catch {
// okay well that crashed, so lets ignore this block and move on.
}
Here's my dilema, when the TableView is reloaded with new data, some information is still siting in RAM which calls didEndDisplayingCell on a tableView with a freshly empty datasource to crash.
So I frequently thrown the exception Index out of range
I've tried this:
func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
do {
let imageMessageBody = msgSections[indexPath.section].msg[indexPath.row] as? ImageMessageBody
let cell = tableView.dequeueReusableCellWithIdentifier("ImageUploadCell", forIndexPath: indexPath) as! ImageCell
cell.willEndDisplayingCell()
} catch {
print("Swift try catch is confusing...")
}
}
I've also tried this:
func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
print(indexPath.section)
print(indexPath.row)
if msgSections.count != 0 {
if let msg = msgSections[indexPath.section].msg[indexPath.row] as? ImageMessageBody {
let cell = tableView.dequeueReusableCellWithIdentifier("ImageUploadCell", forIndexPath: indexPath) as! ImageCell
cell.willEndDisplayingCell()
}
}
}
This is a very low priority block of code, and I have wasted a lot of time with trial and error figuring out which error handler built into swift works for what seems to be extremely unique situations when I have tons of scenarios just like this one where the code can crash and it will not have any effect on the user experience.
In short, I don't need anything fancy but Swift seems to have extremely specific error handlers that differ based on whether I'm getting a value from a functions return value or getting a value from an array's index which may not exist.
Is there a simple try catch on Swift like every other popular programming language?
As suggested in comments and other answers it is better to avoid this kind of situations. However, in some cases you might want to check if an item exists in an array and if it does safely return it. For this you can use the below Array extension for safely returning an array item.
Swift 5
extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Swift 4
extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Swift 3
extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Generator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Swift 2
extension Array {
subscript (safe index: Int) -> Element? {
return indices ~= index ? self[index] : nil
}
}
This way you'll never hit Index out of range
You'll have to check if the item is nil
refer this question for more
Trying the Swift3 code in a Playground in Xcode 8.3.2 still leads to a
"crash" when I do let ar = [1,3,4], then let v = ar[5]. Why? – Thomas
Tempelmann May 17 at 17:40
You have to use our customized subscript so instead of let v = ar[5], it wll be let v = ar[safe: 5].
Default getting value from array.
let boo = foo[index]
Add use the customized subscript.
let boo = fee[safe: index]
// And we can warp the result using guard to keep the code going without throwing the exception.
guard let boo = foo[safe: index] else {
return
}
Swift's Error Handling (do/try/catch) is not the solution to runtime exceptions like "index out of range".
A runtime exception (you might also see these called trap, fatal error, assertion failure, etc.) is a sign of programmer error. Except in -Ounchecked builds, Swift usually guarantees that these will crash your program, rather than continuing to execute in a bad/undefined state. These sorts of crashes can arise from force-unwrapping with !, implicit unwrapping, misuse of unowned references, integer operations/conversions which overflow, fatalError()s and precondition()s and assert()s, etc. (And, unfortunately, Objective-C exceptions.)
The solution is to simply avoid these situations. In your case, check the bounds of the array:
if indexPath.section < msgSections.count && indexPath.row < msgSections[indexPath.section].msg.count {
let msg = msgSections[indexPath.section].msg[indexPath.row]
// ...
}
(Or, as rmaddy says in comments — investigate why this problem is occurring! It really shouldn't happen at all.)
Swift 4:
extension Collection where Indices.Iterator.Element == Index {
subscript (exist index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Usage:
var index :Int = 6 // or whatever number you need
if let _ = myArray[exist: index] {
// do stuff
}
or
var index :Int = 6 // or whatever number you need
guard let _ = myArray[exist: index] else { return }
Don't it's not considered a unusual state error, its a state of its completely screwed up, do something like
guard array.indicies.contains(index) else { return }
Swift 5
This answer in Swift 5 become:
extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Otherwise from Swift 4 there's the ability to have clauses on associated types and it can become:
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
You can try a different approach to this. Will surely work!
if msgSections != nil {
for msg in msgSections[indexPath.section] {
if msgSections[indexPath.section].index(of: msg) == indexPath.row {
(Code)
}
}
Related
I am looking for a more efficient solution for exhaustively comparing objects of the same array to each in Swift. Nesting for-in loops is exhaustive, simple, and returns correct results but the time complexity is O(n^2) which nobody recommends when it comes to performance and larger data sets. I hunted around for solutions for a long time, and only found people describing the for-in loop scenario as a classic example of quadratic time complexity. Help?
EDIT (now includes func tableView(tableView: cellForRowAt:) code ) :
Ok let’s go deeper as requested.
Think of a UITableView where you have an array of Event objects.
Event is a class, and you want to exhaustively check the array’s Event objects against each other for conflicts.
The array is already sorted chronologically by each Event element’s startTike property. There could be duplicate events because maybe a user would want that (who knows why, but that’s the end user’s choice to make).
Each Event object has a DateInterval property for this check.
If a conflict is found, you will need to run logic to evaluate the conflict.
If certain criteria are met, then you set a Boolean property (isConflicted) on the Event object.
The resulting array is then used to pass the indexPath.row specific Event object to a custom UITableViewCell for presentation in the UITableView where that ‘isConflicted’ Boolean is integral to the UITableViewCell’s layout/presentation.
Here is the nested for in loop solution I have working just fine, though the run time complexity is not great.
apologies for not including code earlier. i, mistakenly, didn't think it was necessary.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// unwrap my custom cell
guard let cell = tableView.dequeueReusableCell(withIdentifier: "eventCell", for: indexPath) as? EventDetailTableViewCell else {
return UITableViewCell()
}
// get the relevant section's events array from EventModelController.shared.groupedEventsDictionary check for nil
guard let events = EventModelController.shared.groupedEventsDictionary[sections[indexPath.section]] else {
print("ERROR: no event value found in EventModelController.shared.groupedEventsDictionary[sections[indexPath.section]] in EventsTableViewController.swift -> tableView(cellForRowAt:) - line 117.")
return UITableViewCell()
}
var eventDictionary: [DateInterval : Event] = [:]
for event in events {
eventDictionary[event.dateInterval] = event
}
// START: - option with time complexity O(n^2) that always works
// check the array for conflicts and set relevant event's isConflicted property
for event in events {
for anotherEvent in events {
// ensure that we don't redundantly check the same events against themselves
if event != anotherEvent {
if event.dateInterval.intersects(anotherEvent.dateInterval) {
if event.stopTime == anotherEvent.startTime || event.startTime == anotherEvent.stopTime {
event.isConflicted = false
} else {
event.isConflicted = true
}
}
}
}
}
// END: - option with time complexity O(n^2) that always works
// Configure the cell...
cell.event = events[indexPath.row]
return cell
}
There is a suggestion.
extension Array where Element: Hashable {
var containsDouplicated: Bool {
Set<Element>.init(self).count < self.count
}
}
I created this extension in order to use power of Sets to eliminate duplicated values, and after that I checked if the set (which has only unique values) has the same number of elements.
I think this is O(2n) so O(n).
There is an example of how to use it.
let a1 = [1,2,3]
let a2 = [1,2,3,4,1]
a1.containsDouplicated // false
a2.containsDouplicated // true
extension Array where Element: Hashable {
var containsDouplicated: Bool {
Set<Element>.init(self).count < self.count
}
}
EDIT: After your edit I suggest you to make Event : Hashable and implement a kind of:
extension Array where Element: Hashable {
var containsDouplicates: Bool {
Set<Element>.init(self).count < self.count
}
var orderedUniqueElements: [Element] {
var uniq: Set<Element> = .init(self)
let filtered: [Element] = self.filter { element -> Bool in
if !uniq.contains(element) {
return false
} else {
uniq.remove(element)
}
return true
}
return filtered
}
}
in order to preserve indexPath.
I am trying to migrate my code base to swift 3.0 using xCode. There are few issues which I am not able to understand.
Issue: Type 'Element' constrained to non-protocol type 'IndexPath'
In left side of error navigation panel it's shows only below errors. I am not able to understand for which line of code or branch of code causes the error.
Can anyone help me to solve this please.
UPDATE
After struggling a lot I am stuck at these issues.
UPDATE
Thank you all for your help. Now I faced only the following issues.
Few of you are asking to post the source code but Xcode didn't show any kind of warning or error on pages. There are few generics
private extension Array where Element: IndexPath {
func indexOf(_ indexPath: IndexPath) -> Int {
var counter = 0
for object in self {
if object.section == indexPath.section && object.row == indexPath.row {
return counter
}
counter += 1
}
return 0
}
}
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l < r
case (nil, _?):
return true
default:
return false
}
}
You can use a specific type with a different syntax:
extension Array where Element == IndexPath {
As opposed to the more historic syntax for protocols:
extension Array where Element: Indexable {
Previously you could / had to shuffle around problems by creating a protocol, something like:
protocol Indexable {
var row: Int { get }
var section: Int { get }
}
extension IndexPath: Indexable {}
extension Array where Element: Indexable {
...
I have written some code to find a user's favourites out of an array of custom objects. It works absolutely fine unless that object does not exist, in which case it just crashes. I was considering completely rewriting the code a different way, but I imagine there is probably a way to fix it... I just can't figure out how.
Here is my code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("rideCell", forIndexPath: indexPath) as! RideCell
var ride = DataManager.sharedInstance.getRideByName(favouritesArray[indexPath.row] as! String)
if ride != nil {
cell.rideNameLabel.text = ride!.name
var dateFormat = NSDateFormatter()
dateFormat.dateFormat = "h:mm a"
cell.updatedLabel.text = dateFormat.stringFromDate(ride!.updated!)
if ride!.waitTime! == "Closed" {
cell.waitTimeLabel.text = ride!.waitTime!
} else {
cell.waitTimeLabel.text = "\(ride!.waitTime!)m"
}
}
return cell
}
func getRideByName(name: String) -> Ride? {
let result = self.rideArray.filter({
$0.name == name
})
return result[0]
}
Like I say, it works fine if the Strings in favouritesArray can be found, however it crashes if not.
Can anyone suggest what changes I could make to stop the crash, and to just get it to return nil?
Thanks!
You can use the first property on the filtered array:
return result.first
which returns an optional. A better option, though, is to use the indexOf() function (if you're in Swift 2), as it doesn't build an entire array, and stops looking through the rest of the rideArray once it finds what you're looking for:
return self.rideArray.indexOf { $0.name == name }.map { self.rideArray[$0] }
You need to check result's length - you can do so by replacing
return result[0]
with
return result.count == 0 ? nil : result[0]
I'm loading a list of objects from a core data database into a table view.
class ScheduleViewController: UITableViewController {
private var items: [AnyObject]?
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let itemCount = items?.count {
return itemCount
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DayScheduleCell", forIndexPath: indexPath) as DayScheduleCell
if let act = items[indexPath.row] as Activity {
if act.client != nil {
// ...
}
}
return cell
}
}
The data is retrieved inside a closure so I have declared an items array as an optional because it might be nil in the first run.
I'm getting the error '[AnyObject]?' does not have a member named 'subscript' at this line if let act = items[indexPath.row] as? Activity.
I can't figure out how to resolve this.
The array is declared as:
private var items: [AnyObject]?
so, as you also said, it's an optional
In swift an optional is an enum, so a type on its own - and as an optional, it can contain either a nil value or an object of the contained type.
You want to apply the subscript to the array, not to the optional, so before using it you have to unwrap the array from the optional
items?[indexPath.row]
but that's not all - you also have to use the conditional downcast:
as? Activity
because the previous expression can evaluate to nil
So the correct way to write the if statement is
if let act = items?[indexPath.row] as? Activity {
First of all you need to unwrap or optional chain the items as it can be nil. AnyObject has different behaviour when getting as element from array, due to the fact that AnyObject can be a function. You would have to cast from items like this:
if let act = items?[indexPath.row] as AnyObject? as Activity? {
if act.client != nil {
// ...
}
}
If items will never contain a function you can use
private var items: [Any]?
instead and cast with:
if let act = items?[indexPath.row] as? Activity {
if act.client != nil {
// ...
}
}
I have fixed my problem by convert index var type from UInt to Int
let obj = items[Int(index)]
Hope this can help someone who still get this problem.
I have a cellForItemAtIndexPath method I'm using for my UICollectionView. The reuse identifier depends on the indexPath, so I was planning to do something like:
var reuseIdentifier: String
if indexPath.row == 0 {
reuseIdentifier = "One"
} else if indexPath.row == 1 {
reuseIdentifier = "Two"
} else if indexPath.row == 2 {
reuseIdentifier = "Three"
}
var cell: UICollectionViewCell = collectionView.dequeueReusableCellWithIdentifier(reuseIdentifier, indexPath:indexPath)
Is this correct? Should reuseIdentifier be an optional String, or is it fine being a normal one?
Furthermore, am I handling it correctly in the nil case? If it's other than 2, reuseIdentifier is nothing, right? (Which is different from nil?) I should be handling this distinctly, right?
Why not something simpler, like this?
let reuseIdentifier = ["One", "Two", "Three"][indexPath.row]
Note: this will crash if indexPath.row > 2. If this might happen, then you should be more careful. You might consider something like this:
extension Array {
func at(index: Int) -> T? {
if index >= count {
return nil
} else {
return self[index]
}
}
}
// ...
let reuseIdentifier: String? = ["One", "Two", "Three"].at(indexPath.row) // might be nil
If you declare a var of non-optional type and never assign to it, it's a compile time error.
If you want to let it be nil, you'll need to declare it as an optional. Then you'll need to appropriately handle the case where it is nil -- since the table will accept a nil cell, you could do that.