0

So I'm very new to Swift at the moment and am working on a class (as below).

The variable selectedOption can be an array of String or Int and the methods need to compare the values received against that array.

The below code works, but I don't like the fact that I'm type checking and replicating the code to compare Strings then again to compare Ints in methods isSelected and removeSelectedItem

Such as

    if selectedOption is String {
        return (self.selectedOption!.indexOf({ $0 as! String == selectedOption as! String }) != nil)
    } else if ( selectedOption is Int ) {
        return (self.selectedOption!.indexOf({ $0 as! Int == selectedOption as! Int })) != nil
    }

There must be a better way?

public class SearchOption {

    private var title: String
    private var allowAny: Bool
    private var allowMultiple: Bool
    private var dependencies: [SearchOption]?

    // Store the selected Item in an array of AnyObjects.
    private var selectedOption: [AnyObject]?

    init(title: String, allowAny: Bool, allowMultiple: Bool, dependencies: [SearchOption]?) {
        self.title = title
        self.allowAny = allowAny
        self.allowMultiple = allowMultiple
        self.dependencies = dependencies
    }

    public func setSelectedItem(selectedOption: AnyObject) -> Void {
        if self.selectedOption == nil || !self.allowMultiple{
            self.selectedOption = [AnyObject]()
        }
        self.selectedOption?.append(selectedOption)
    }

    public func getSelectedItem() -> [AnyObject]? {
        return self.selectedOption
    }

    public func removeSelectedItem(selectedOption: AnyObject) -> Void {
        if self.selectedOption != nil {
            if selectedOption is String {
                self.selectedOption = self.selectedOption!.filter() { $0 as! String != selectedOption as! String }
            } else if ( selectedOption is Int ) {
                self.selectedOption = self.selectedOption!.filter() { $0 as! Int != selectedOption as! Int }
            }
        }
    }

    public func isSelected(selectedOption: AnyObject) -> Bool {
        if self.selectedOption != nil {
            if selectedOption is String {
                return (self.selectedOption!.indexOf({ $0 as! String == selectedOption as! String }) != nil)
            } else if ( selectedOption is Int ) {
                return (self.selectedOption!.indexOf({ $0 as! Int == selectedOption as! Int })) != nil
            }
        }
        return false
    }

    public func clearSelectedItems() -> Void {
        self.selectedOption = nil
    }

    public func checkDependencies() -> Bool {
        if dependencies != nil {
            for dependency in dependencies! {
                if dependency.getSelectedItem() == nil {
                    return false
                }
            }
        }
        return true
    }

}

var make: SearchOption = SearchOption(title: "Make", allowAny: true, allowMultiple: true, dependencies: nil)
make.setSelectedItem("Vauxhall")
make.setSelectedItem("Mazda")
make.setSelectedItem("Audi")
print(make.getSelectedItem())
make.removeSelectedItem("Mazda")
print(make.getSelectedItem())
print(make.isSelected("Audi"))

Outputs:

Optional([Vauxhall, Mazda, Audi])
Optional([Vauxhall, Audi])
true
Richard Poole
  • 591
  • 1
  • 5
  • 21
  • 3
    I'm not at all clear on what you're actually trying to do, but you should start by eliminating code and creating the absolute simplest example possible to replicate what you're trying to do. – nhgrif Oct 01 '15 at 11:58
  • 1
    Will [selectedOption] be either all strings or all ints? If so, consider making the class generic based on the type the selectedOption array contains and get rid of the all the AnyObject types in the code. – Wayne Tanner Oct 01 '15 at 12:03
  • @WayneTanner yes they will only ever contain one type. I've tried generics, but when I get to `setSelectedItem` it complains when I try to initialise the array with `self.selectedOption = [T]()` with Cannot assign a value of type '[T]' to a value of type '[T]'. Also when trying to append to the array in `setSelectedItem` is complains Cannot invoke 'append' with an argument list of type '(T)' – Richard Poole Oct 01 '15 at 12:10
  • 2
    The common protocol conformance of `String` and `Int` is `Printable` (Swift1) / `CustomStringConvertible` (Swift 2). Use the protocol as type and wrap the variables in the literal initializer form `"\(foo)"`. – vadian Oct 01 '15 at 12:22
  • @vadian that's beautiful thank you! – Richard Poole Oct 01 '15 at 12:31
  • 1
    `String` and `Int` are both `Equatable`, and that is all you should need. – Martin R Oct 01 '15 at 12:35
  • MartinR, vadian, WayneTanner thank you, managed to solve it using a bit of all your responses. – Richard Poole Oct 01 '15 at 12:47

2 Answers2

2

Expanding on the comments already made to the question: You should make the class generic, so that it can be used with String or Int items. What you need is that the items can be compared with ==, i.e. that the type is Equatable.

The class declaration would then be

public class SearchOption<T : Equatable> {
   // ...
}

and all occurrences of AnyObject have to be replaced by T.

The isSelected method simplifies to

public func isSelected(selectedOption: T) -> Bool {
    if self.selectedOption != nil {
        return self.selectedOption!.contains( { $0 == selectedOption })
    }
    return false
}

which can be further simplified using optional chaining and the nil-coalescing operator ??:

public func isSelected(selectedOption: T) -> Bool {
    return self.selectedOption?.contains( { $0 == selectedOption }) ?? false
}

Similarly:

public func removeSelectedItem(selectedOption: T) -> Void {
    self.selectedOption = self.selectedOption?.filter( { $0 != selectedOption } )
}

For String items you would then create an instance of the class with

let make = SearchOption<String>(title: "Make", allowAny: true, allowMultiple: true, dependencies: nil)
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
1

Managed to solve the problem using Generics and Equatable (I've never really used these before so forgive me)

In the class T is constrained to a type of Equatable, which seems to keep .filter() and .indexOf happy.

When creating the instance you pass the type you'll be using:

var make: SearchOption = SearchOption<String>(title: "Make", allowAny: true, allowMultiple: true, dependencies: nil)

The resulting class looks as follows

public class SearchOption<T: Equatable> {

    private var title: String
    private var allowAny: Bool
    private var allowMultiple: Bool
    private var dependencies: [SearchOption]?

    // Store the selected Item in an array of AnyObjects.
    private var selectedOption: [T]?

    init(title: String, allowAny: Bool, allowMultiple: Bool, dependencies: [SearchOption]?) {
        self.title = title
        self.allowAny = allowAny
        self.allowMultiple = allowMultiple
        self.dependencies = dependencies
    }

    public func setSelectedItem(selectedOption: T) -> Void {
        if self.selectedOption == nil || !self.allowMultiple {
            self.selectedOption = [T]()
        }
        self.selectedOption?.append(selectedOption)
    }

    public func getSelectedItem() -> [T]? {
        return self.selectedOption
    }

    public func removeSelectedItem(selectedOption: T) -> Void {
        if self.selectedOption != nil {
            self.selectedOption = self.selectedOption!.filter() { $0 != selectedOption }
        }
    }

    public func isSelected(selectedOption: T) -> Bool {
        if self.selectedOption != nil {
            return (self.selectedOption!.indexOf({ $0 == selectedOption }) != nil)
        }
        return false
    }

    public func clearSelectedItems() -> Void {
        self.selectedOption = nil
    }

    public func checkDependencies() -> Bool {
        if dependencies != nil {
            for dependency in dependencies! {
                if dependency.getSelectedItem() == nil {
                    return false
                }
            }
        }
        return true
    }

}

var make: SearchOption = SearchOption<String>(title: "Make", allowAny: true, allowMultiple: true, dependencies: nil)
make.setSelectedItem("Vauxhall")
make.setSelectedItem("Mazda")
make.setSelectedItem("Audi")
print(make.getSelectedItem())
make.removeSelectedItem("Audi")
print(make.getSelectedItem())
print(make.isSelected("Mazda"))
Richard Poole
  • 591
  • 1
  • 5
  • 21