Swift: Using try? to catch Index out of range - ios

so i am making game and there is a grid and you can only place if any of the adjacent blocks are a source block, well if you are trying to place a block in the top grid there's no block above it so an Index out of range error will be thrown. I am trying to catch it but xcode seems to think no error is possible. Am i doing this wrong? I guess I could go to each case and add a catch but I feel like this should work.
copy friendly text if you would like to try it out in your own IDE:
func isValidPlacement(row:Int,col:Int) -> Bool {
if let t = try? tiles[row-1][col], t.isSource {
return true
}
else if let t = try? tiles[row][col-1], t.isSource {
return true
}
else if let t = try? tiles[row+1][col], t.isSource {
return true
}
else if let t = try? tiles[row][col+1], t.isSource {
return true
}
else {
return false
}
}

Runtime errors and returning bools is the Objective-C way! Throw throw throw! 🙀
public extension Collection {
/// - Returns: same as subscript, if index is in bounds
/// - Throws: CollectionIndexingError
func element(at index: Index) throws -> Element {
guard indices.contains(index)
else { throw CollectionIndexingError() }
return self[index]
}
}
extension Collection where Element: Collection {
/// - Returns: same as subscripting, if indices are in bounds
/// - Throws: CollectionIndexingError
func element( at indices: (Index, Element.Index) ) throws -> Element.Element {
try element(at: indices.0).element(at: indices.1)
}
}
/// Thrown when `getElement` is called with an invalid index.
public struct CollectionIndexingError: Error { }
extension Tile {
enum PlacementError: Error {
case invalid
case noSources
}
}
extension Collection where
Index == Int,
Element: Collection, Element.Index == Int, Element.Element == Tile
{
func validatePlacement(row: Index, column: Element.Index) throws {
let tiles = [(-1, 0), (0, -1), (1, 0), (0, 1)].compactMap {
try? element( at: (row + $0.0, column + $0.1) )
}
guard !tiles.isEmpty
else { throw Tile.PlacementError.invalid }
guard tiles.contains(where: \.isSource)
else { throw Tile.PlacementError.noSources }
}
}

In Swift. you can only catch instances of Error that are thrown by code, you can't catch runtime exceptions such as an array bounds violation
You can create your own safeAccess function. You don't say what type is in your array so I will use SomeType as an example
func safeAccess(row:Int, col:Int) -> SomeType? {
guard row >= 0, row < tiles.count else {
return nil
}
guard col >= 0, col < tiles[row].count else {
return nil
}
return tiles[row][col]
}
func isValidPlacement(row:Int,col:Int) -> Bool {
if let t = tiles.safeAccess(row:row-1,col:col), t.isSource {
return true
}
if let t = tiles.safeAccess(row:row,col:col-1),, t.isSource {
return true
}
if let t = tiles.safeAccess(row:row+1,col:col), t.isSource {
return true
}
if let t = tiles.safeAccess(row:row,col:col+1), t.isSource {
return true
}
return false
}
You could also define an extension on Array
extension Array {
func element(at index: Int) -> Element? {
if index >= 0 && index < self.count {
return self[index]
}
return nil
}
}
And to use it:
func isValidPlacement(row:Int,col:Int) -> Bool {
if let tiles = tiles.element(at:row-1), let t = tiles.element(at:col), t.isSource {
return true
}
else if tiles.element(at:row), let t = tiles.element(at:col-1), t.isSource {
return true
}
else if let tiles = tiles.element(at:row+1), let t = tiles.element(at:col), t.isSource {
return true
}
else if let tiles = tiles.element(at:row), let t = tiles.element(at:col+1), t.isSource {
return true
}
else {
return false
}
}

Accessing an index of an array which doesn't exist results in a run time exception. You can't use try here because the array is not throwing anything.
Try adding these two lines at the start of the func and get rid of all of try?
guard row <= tiles.count - 1 else {
return false
}
guard col <= tiles[row].count - 1 else {
return false
}
Also i am assuming, whatever the type array stores here is an optional. if not you can remove if lets too
func isValidPlacement(row:Int,col:Int) -> Bool {
guard row <= tiles.count - 1 else {
return false
}
guard col <= tiles[row].count - 1 else {
return false
}
if let t = tiles[row-1][col], t.isSource {
return true
}
else if let t = tiles[row][col-1], t.isSource {
return true
}
else if let t = tiles[row+1][col], t.isSource {
return true
}
else if let t = tiles[row][col+1], t.isSource {
return true
}
else {
return false
}
}

Related

How to combine two foreach to one in Swift?

I have one function which is having some logic which have 2 foreach loop but i want to make code compact so I am trying to use compactmap
func getData() -> [String] {
var ids = [String]()
self.item?.connections?.forEach { connection in
connection.validLine?.forEach { line in
if let _ = line.connection?.links[LinkKey.dataGroups],
let dataGroups = line.dataGroupsCache, dataGroups.isContinue {
ids += checkinGroups.connections?.compactMap { $0.id } ?? []
}
}
}
return ids
}
so instead of 2 foreach i am trying to make in one by using self.item?.connections?.compactMap({ $0.validline }) but I am getting error saying "Type of expression is ambiguous without more context"
I don't see how you can do it without to forEach or compactMap. Here is a possible solution:
func getData() -> [String] {
return item?.connections?.compactMap { connection in
connection.validLine?.compactMap { line in
guard let _ = line.connection?.links[LinkKey.dataGroups], line.dataGroupsCache?.isContinue == true else { return nil }
return checkinGroups.connections?.compactMap(\.id)
}
}
}
Here's a translation of your post into something that is compilable and a direct translation into a version that doesn't use forEach.
I changed connectionIds to ids in your example because otherwise, you might as well just return [].
class Example {
func getData() -> [String] {
var ids = [String]()
self.item?.connections?.forEach { connection in
connection.validLine?.forEach { line in
if let _ = line.connection?.links[LinkKey.dataGroups],
let dataGroups = line.dataGroupsCache, dataGroups.isContinue {
ids += checkinGroups.connections?.compactMap { $0.id } ?? []
}
}
}
return ids
}
func getDataʹ() -> [String] {
guard let connections = item?.connections else { return [] }
let numberOfProperLines = connections.flatMap { $0.validLine ?? [] }
.filter { line in
if let _ = line.connection?.links[LinkKey.dataGroups],
let dataGroups = line.dataGroupsCache, dataGroups.isContinue {
return true
} else {
return false
}
}
.count
return (0..<numberOfProperLines).flatMap { _ in checkinGroups.connections?.compactMap(\.id) ?? [] }
}
var checkinGroups: CheckInGroups!
var item: Item!
}
enum LinkKey: Int {
case dataGroups
}
struct Item {
let connections: [Connection]?
}
struct Connection {
let id: String?
let validLine: [Line]?
let links: [LinkKey: Void]
}
struct Line {
let dataGroupsCache: DataGroups?
let connection: Connection?
}
struct DataGroups {
let isContinue: Bool
}
struct CheckInGroups {
let connections: [Connection]?
}

How do I return to the top of a function in swift?

I have this function that picks a random value from a list:
func pickAttraction(attractionType: Array<Attraction>) -> Attraction {
var randAttr = attractionType.randomElement()
if favoritesNames.contains(randAttr!.attractionName) {
return pickAttraction(attractionType: attractionType)
} else {
return randAttr!
}
}
If the random value is in the array favoritesName, I want it to go back to the top of the function and pick a new value.
How can I make this more efficient?
In addition, is it possible for the program to crash while force-unwrapping randAttr (on line 3) if attractionType always has at least 8 values?
The question is not worth asking! It'll never get you there.
func pickAttraction(from attractions: [Attraction]) -> Attraction? {
attractions.filter { !favoritesNames.contains($0.name) }
.randomElement()
}
Here is a sample in playground
import UIKit
class RandomAttraction:Hashable{
static func == (lhs: RandomAttraction, rhs: RandomAttraction) -> Bool {
return lhs.attractionName == rhs.attractionName
}
func hash(into hasher: inout Hasher) {
hasher.combine(attractionName)
}
init(attractionName:String) {
self.attractionName = attractionName
}
var attractionName:String
}
var favoritesNames = [RandomAttraction.init(attractionName: "a"),RandomAttraction.init(attractionName: "b")]
var otherNames = [RandomAttraction.init(attractionName: "c"),RandomAttraction.init(attractionName: "d")]
func pickAttraction(attractionType: Array<RandomAttraction>) -> RandomAttraction? {
//Filters the content of the favoritesNames from attractionType
let filteredArray = Array(Set(attractionType).subtracting(favoritesNames))
//If count is zero return nil
if filteredArray.count == 0{
return nil
}
let randAttr = filteredArray.randomElement()
return randAttr
}
//MARK: Usage
if let pickedAttraction = pickAttraction(attractionType: otherNames){
print(pickedAttraction.attractionName)
}else{
print("No attractions")
}

RxSwift UITableView binding when also using a UISearchController

In my view controller I have a UISearchController associated with the UITableView. So all my normal table view datasource methods do the old
if isSearching {
// use filteredTableData array
} else {
// use SharedModel.shared.participants
}
I'm not clear on how I'd implement that using RxCocoa as I'm brand new to Rx.
Create a Variable like below
var tableViewOptions = Variable<[String]>([]) // replace String with your own object
bind tableViewOptions to tableview after your view loads.
tableViewOptions
.asObservable()
.bind(to: self.tableView
.rx
.items(cellIdentifier: "cellIdentifier",
cellType: CustomCell.self)) { _, values, cell in
// do your stuff
}
Then when ever you search change the values of tableViewOptions like below.
if isSearching {
tableViewOptions.value = filteredTableArray
} else {
tableViewOptions.value = SharedModel.shared.participants
}
I solve this by declaring a decorator for an Observable and an extension for a UISearchBar (you could also declare it for the UISearchController):
//FIXME: Item can't be type constrained. Have to use optional casting.
class FilterableByTitleCollection<Item>: ObservableType {
private let origin: Observable<Array<Item>>
private let filteringStrategySource: Observable<TitlableModelFilteringStrategy> //FIXME: This is a strategy source
init<Origin: ObservableType>(
origin: Origin,
filteringStrategySource: Observable<TitlableModelFilteringStrategy>) where Origin.E == Array<Item> {
self.origin = origin.asObservable()
self.filteringStrategySource = filteringStrategySource
}
typealias E = Array<Item>
func subscribe<O:ObserverType>(_ observer: O) -> Disposable where O.E == Array<Item> {
return Observable.combineLatest(
origin,
filteringStrategySource
)
.observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
.map{ origin, strategy in
guard origin is Array<Titlable> else { assert(false); return origin }
return origin.filter{ strategy.shouldInclude(item: $0 as! Titlable) }
}
.observeOn(MainScheduler.instance)
.subscribe(observer)
}
}
...
extension UISearchBar {
var titlableFilteringStrategy: Observable<TitlableModelFilteringStrategy> {
return Observable<String?>.merge(
[
self.rx.text.asObservable(),
self.rx.textDidEndEditing
.map{ [weak self] in
assert(self != nil)
return self?.text
},
self.rx.cancelButtonClicked.map{ Optional<String>.some("") }
]
).distinctUntilChanged{ (old: String?, new: String?) -> Bool in
old == new
}.map{ TitlableModelFilteringStrategy(filteringPredicate: $0) }
}
}
...
struct TitlableModelFilteringStrategy {
private let filteringPredicate: String
init(filteringPredicate: String?) {
self.filteringPredicate = filteringPredicate ?? ""
}
func shouldInclude(item: Titlable) -> Bool {
return filteringPredicate.isEmpty ? true : item.title.localizedCaseInsensitiveContains(filteringPredicate)
}
func equals(to another: TitlableModelFilteringStrategy) -> Bool {
return filteringPredicate == another.filteringPredicate
}
}
...
protocol Titlable {
var title: String { get }
}

Return statement in nested if function

I have been struggling with something for a while.
How can I return something in a nested if function?
The function below has the task of finding out if the userProfile has a verified card or not, if verified == 1 (true) then return true, else return false.
func userHasVerfifiedCard() -> Bool{
let userDocument = users.documentWithID(Meteor.userID!)
if let card = userDocument.valueForKey("profile")!["card"] {
print("has card")
if let verified = card!["verified"] as? Int {
print("card.verified as Int")
if verified == 1{
print("card.verified == 1")
lastFourCreditCardLbl.text = card!["last4"] as? String
return true
}else {
return false
}
}
}
your method won't return anything, if if let card won't work. But it must return a bool in any case.
func userHasVerfifiedCard() -> Bool {
let userDocument = users.documentWithID(Meteor.userID!)
if let card = userDocument.valueForKey("profile")!["card"] {
print("has card")
if let verified = card!["verified"] as? Int {
print("card.verified as Int")
if verified == 1 {
print("card.verified == 1")
lastFourCreditCardLbl.text = card!["last4"] as? String
return true
}
}
}
return false
}
Try this and let me know if it's helps..!
func userHasVerfifiedCard() -> Bool{
let userDocument = users.documentWithID(Meteor.userID!)
if let card = userDocument.valueForKey("profile")!["card"], verified = card!["verified"] as? Int where verified == 1 {
return true
} else {
return false
}
}

Swift 2.0 Cannot invoke 'map' with argumet list of type ([AnyObject],(_) -> _)

I've been trying to fix all my projects after the swift 2.0 update. After some work I narrowed it down to this single error:
Cannot invoke 'map' with argumet list of type ([AnyObject],(_) -> _)
This is the whole code:
extension JSON {
//Optional [JSON]
public var array: [JSON]? {
get {
if self.type == .Array {
return map(self.object as! [AnyObject]){ JSON($0) }
} else {
return nil
}
}
}
//Non-optional [JSON]
public var arrayValue: [JSON] {
get {
return self.array ?? []
}
}
//Optional [AnyObject]
public var arrayObject: [AnyObject]? {
get {
switch self.type {
case .Array:
return self.object as? [AnyObject]
default:
return nil
}
}
set {
if newValue != nil {
self.object = NSMutableArray(array: newValue!, copyItems: true)
} else {
self.object = NSNull()
}
}
}
}
The error appears here:
return map(self.object as! [AnyObject]){ JSON($0) }
Any ideas? My swift knowledge doesnt go this far...
map is now a member function on the CollectionType protocol.
Try return (self.object as! [AnyObject]).map { JSON($0) }

Resources