patternswiftMinor
Concatenating an array of optional NSAttributedStrings
Viewed 0 times
arraynsattributedstringsconcatenatingoptional
Problem
Imagine you had to join together attributed strings with a separator in between them. Which of the following methods would you use?
An extension on SequenceType with a function that takes in a separator. Can't use any optionals in the array.
A private function that takes in an array of optional NSAttributedString and a separator.
Extension on _ArrayType that can take in an array of options NSAttributedString
```
extension _ArrayType where Generator.Element == NSAttributedString? {
private func join(withSeparator separator: NSAttributedString) -> NSAttributedString? {
let unwrappedStrings = flatMap{$0} as [NSAttributedString]
var shouldAddSeparator = false
return unwrappedStrings.reduce(NSMutableAttributedString(), combine: { (string, element) in
An extension on SequenceType with a function that takes in a separator. Can't use any optionals in the array.
extension SequenceType where Generator.Element: NSAttributedString {
func join(withSeparator separator: NSAttributedString) -> NSAttributedString {
var shouldAddSeparator = true
return self.reduce(NSMutableAttributedString()) {(element, sequence) in
if shouldAddSeparator {
shouldAddSeparator = false
}
else {
element.appendAttributedString(separator)
}
element.appendAttributedString(sequence)
return element
}
}
}A private function that takes in an array of optional NSAttributedString and a separator.
private func join(attributedStrings strings: [NSAttributedString?], withSeparator separator: NSAttributedString) -> NSAttributedString? {
let unwrappedStrings = strings.flatMap{$0} as [NSAttributedString]
guard unwrappedStrings.count == strings.count else { return nil }
let finalString = NSMutableAttributedString()
for (index, string) in unwrappedStrings.enumerate() {
if index == 0 {
finalString.appendAttributedString(string)
}
else {
finalString.appendAttributedString(separator)
finalString.appendAttributedString(string)
}
}
return finalString
}Extension on _ArrayType that can take in an array of options NSAttributedString
```
extension _ArrayType where Generator.Element == NSAttributedString? {
private func join(withSeparator separator: NSAttributedString) -> NSAttributedString? {
let unwrappedStrings = flatMap{$0} as [NSAttributedString]
var shouldAddSeparator = false
return unwrappedStrings.reduce(NSMutableAttributedString(), combine: { (string, element) in
Solution
The second and third approach combine two tasks into one:
attributed strings, and
My suggestion is to separate these concerns. This rules out #2 and #3
and leaves us with your #1 approach, the extension method on
Another disadvantage of the
so that might break in a future version of Swift.
The
and this can be combined with your extension method by the caller:
A user might also want to concatenate non-optional strings, which
works with the extension method:
but not with the other two approaches.
Another argument for approach #1 is that it resembles
the existing method to join strings:
The method itself can be improved. The
and append all elements (similar as on your
Update: The Swift language changes constantly. For the convenience
of future readers, here is an update to Swift 4 of the above code:
Also
- Extract the non-nil elements from the given array of optional
attributed strings, and
- concatenate these with a given separator.
My suggestion is to separate these concerns. This rules out #2 and #3
and leaves us with your #1 approach, the extension method on
SequenceType.Another disadvantage of the
extension _ArrayType is that it uses an internal type,so that might break in a future version of Swift.
The
flatMap() method from the Swift standard library already provides a method for the first task:let attributedStrings = optionalAttributedStrings.flatMap { $0 }and this can be combined with your extension method by the caller:
let joined = optionalAttributedStrings.flatMap { $0 }.join(withSeparator: separator)A user might also want to concatenate non-optional strings, which
works with the extension method:
let joined = attributedStrings.join(withSeparator: separator)but not with the other two approaches.
Another argument for approach #1 is that it resembles
the existing method to join strings:
extension SequenceType where Generator.Element == String {
public func joinWithSeparator(separator: String) -> String
}The method itself can be improved. The
reduce() method creates a newNSMutableAttributedString in each iteration. Better create only oneand append all elements (similar as on your
join() function):extension SequenceType where Generator.Element: NSAttributedString {
func join(withSeparator separator: NSAttributedString) -> NSAttributedString {
let finalString = NSMutableAttributedString()
for (index, string) in enumerate() {
if index > 0 {
finalString.appendAttributedString(separator)
}
finalString.appendAttributedString(string)
}
return finalString
}
}Update: The Swift language changes constantly. For the convenience
of future readers, here is an update to Swift 4 of the above code:
extension Sequence where Element: NSAttributedString {
func join(withSeparator separator: NSAttributedString) -> NSAttributedString {
let finalString = NSMutableAttributedString()
for (index, string) in enumerated() {
if index > 0 {
finalString.append(separator)
}
finalString.append(string)
}
return finalString
}
}Also
flatMap has been renamed to compactMap:let attributedStrings = optionalAttributedStrings.compactMap { $0 }Code Snippets
let attributedStrings = optionalAttributedStrings.flatMap { $0 }let joined = optionalAttributedStrings.flatMap { $0 }.join(withSeparator: separator)let joined = attributedStrings.join(withSeparator: separator)extension SequenceType where Generator.Element == String {
public func joinWithSeparator(separator: String) -> String
}extension SequenceType where Generator.Element: NSAttributedString {
func join(withSeparator separator: NSAttributedString) -> NSAttributedString {
let finalString = NSMutableAttributedString()
for (index, string) in enumerate() {
if index > 0 {
finalString.appendAttributedString(separator)
}
finalString.appendAttributedString(string)
}
return finalString
}
}Context
StackExchange Code Review Q#135814, answer score: 4
Revisions (0)
No revisions yet.