Swift extension for [String]?

edit/update

Swift 4 or later it is better to constrain the collection elements to StringProtocol which will cover Substrings as well.

extension BidirectionalCollection where Element: StringProtocol {
    var joinedWithCommas: String {
        guard let last = last else { return "" }
        return count > 2 ? dropLast().joined(separator: ", ") + ", or " + last : joined(separator: " or ")
    }
}

And if all elements are just Characters we can simply extend StringProtocol:

extension StringProtocol {
    func joined(with separator: String = ",", conector: String = "") -> String {
        guard let last = last else { return "" }
        if count > 2 {
            return dropLast().map(String.init).joined(separator: separator + " ") + separator + " " + conector + " " + String(last)
        }
        return map(String.init).joined(separator: " " + conector + " ")
    }
}

let elements = "abc"
let elementsJoined = elements.joined()                   // "a, b, c"
let elementsSeparated = elements.joined(conector: "or")  // "a, b, or c"
let elementsConected = elements.joined(conector: "and")  // "a, b, and c"


Original answer

In Swift 3.1 (Xcode 8.3.2) you can simply extend Array constraining element type equal to String

extension Array where Element == String {
    var joinedWithCommas: String {
        guard let last = last else { return "" }
        return count > 2 ? dropLast().joined(separator: ", ") + ", or " + last : joined(separator: " or ")
    }
}

["a","b","c"].joinedWithCommas    // "a, b, or c"

You could follow the declaration of joinWithSeparator (Cmd-click on it) and find that it is defined as an extension of the protocol SequenceType instead of the type Array.

// swift 2:
extension SequenceType where Generator.Element == String {
    public func joinWithSeparator(separator: String) -> String
}

(Note: In Xcode 8 / Swift 3 if you Cmd-click on join(separator:) you will land on Array even if it is still implemented inside Sequence, but that won't invalidate the idea below)

We could do the same with your function, where we extend a protocol adopted by Array instead of Array itself:

// swift 2:
extension CollectionType where
        Generator.Element == String,
        SubSequence.Generator.Element == String,
        Index: BidirectionalIndexType
{
    func joinWithCommas() -> String {
        switch count {
        case 0, 1, 2:
            return joinWithSeparator(" or ")
        default:
            return dropLast(1).joinWithSeparator(", ") + ", or " + last!
        }
    }
}

// swift 3:
extension BidirectionalCollection where
        Iterator.Element == String,
        SubSequence.Iterator.Element == String
{
    func joinWithCommas() -> String {
        switch count {
        case 0, 1, 2:
            return joined(separator: " or ")
        default:
            return dropLast().joined(separator: ", ") + ", or " + last!
        }
    }
}

Note:

  • we extend CollectionType to be able to use count
  • we constraint Generator.Element == String to use joinWithSeparator
  • we constraint SubSequence.Generator.Element == String to ensure dropLast(1) can use joinWithSeparator. dropLast(1) returns the associated type SubSequence.
  • we constraint Index: BidirectionalIndexType to use last.

Tags:

Swift