Richard Poole
Richard Poole

Reputation: 591

Swift variable comparison where type is not known

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

Upvotes: 0

Views: 130

Answers (2)

Martin R
Martin R

Reputation: 539835

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)

Upvotes: 2

Richard Poole
Richard Poole

Reputation: 591

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"))

Upvotes: 1

Related Questions