Padding a swift String for printing - swift

I'm trying to print a list of Strings all padded to the same width.
In C, I would use something like printf("%40s", cstr), where cstr is a C string.
In Swift, the best I could come up is this:
line += String(format: "%40s",string.cStringUsingEncoding(<someEncoding>))
Is there a better way ?

In Swift 3 you can use:
let str = "Test string"
let paddedStr = str.padding(toLength: 20, withPad: " ", startingAt: 0)
Result string: "Test string "
If you need to pad to the left the text (right justify), you can write the following function as an extension to String:
extension String {
func leftPadding(toLength: Int, withPad character: Character) -> String {
let newLength = self.characters.count
if newLength < toLength {
return String(repeatElement(character, count: toLength - newLength)) + self
} else {
return self.substring(from: index(self.startIndex, offsetBy: newLength - toLength))
}
}
}
So if you write:
let str = "Test string"
let paddedStr = str.leftPadding(toLength: 20, withPad: " ")
Result string: " Test string"
In Swift 4.1 the substring method is deprecated and there are a number of new methods to obtain a substring. Either prefix, suffix or subscripting the String with a Range<String.Index>.
For the previous extension we can use the suffix method to accomplish the same result. Since the suffix method returns a String.SubSequence, it needs to be converted into a String before being returned.
extension String {
func leftPadding(toLength: Int, withPad character: Character) -> String {
let stringLength = self.count
if stringLength < toLength {
return String(repeatElement(character, count: toLength - stringLength)) + self
} else {
return String(self.suffix(toLength))
}
}
}

For Swift >= 3
line += string.padding(toLength: 40, withPad: " ", startingAt: 0)
For Swift < 3
NSString has the stringByPaddingToLength: method:
line += string.stringByPaddingToLength(40, withString: " ", startingAtIndex: 0)

extension RangeReplaceableCollection where Self: StringProtocol {
func paddingToLeft(upTo length: Int, using element: Element = " ") -> SubSequence {
return repeatElement(element, count: Swift.max(0, length-count)) + suffix(Swift.max(count, count-length))
}
}
"123".paddingToLeft(upTo: 5) // " 123"
"123".paddingToLeft(upTo: 5, using: "0") // "00123"
"123".paddingToLeft(upTo: 3, using: "0") // "123"
"$199.99".dropLast(3).paddingToLeft(upTo: 10, using: "_") // "______$199"
To replicate the same behaviour as padding(toLength:, withPad:, startingAt:) we can add rotateTo left functionality to RangeReplaceableCollection
extension RangeReplaceableCollection {
func rotatingLeft(positions: Int) -> SubSequence {
let index = self.index(startIndex, offsetBy: positions, limitedBy: endIndex) ?? endIndex
return self[index...] + self[..<index]
}
}
And implement it as follow:
extension RangeReplaceableCollection where Self: StringProtocol {
func paddingToLeft<S: StringProtocol & RangeReplaceableCollection>(upTo length: Int, with string: S, startingAt index: Int = 0) -> SubSequence {
let string = string.rotatingLeft(positions: index)
return repeatElement(string, count: length-count/string.count)
.joined().prefix(length-count) + suffix(Swift.max(count, count-length))
}
}
"123".paddingToLeft(upTo: 10, with: "abc", startingAt: 2) // "cabcabc123"
"123".padding(toLength: 10, withPad: "abc", startingAt: 2) // "123cabcabc"

Put all string-format-code into extension and reuse it wherever you want.
extension String {
func padding(length: Int) -> String {
return self.stringByPaddingToLength(length, withString: " ", startingAtIndex: 0)
}
func padding(length: Int, paddingString: String) -> String {
return self.stringByPaddingToLength(length, withString: paddingString, startingAtIndex: 0)
}
}
var str = "str"
print(str.padding(10)) // "str "
print(str.padding(10, paddingString: "+")) // "str+++++++"

The following two functions return a string padded to the given width, either left or right justified. It is pure Swift 4, no NSString, and no C string either. You may choose if a string longer than the padding width will be truncated or not.
extension String {
func rightJustified(width: Int, truncate: Bool = false) -> String {
guard width > count else {
return truncate ? String(suffix(width)) : self
}
return String(repeating: " ", count: width - count) + self
}
func leftJustified(width: Int, truncate: Bool = false) -> String {
guard width > count else {
return truncate ? String(prefix(width)) : self
}
return self + String(repeating: " ", count: width - count)
}
}

For left padding, you can use double-reverse trick:
String(String(s.reversed()).padding(toLength: 5, withPad: "0", startingAt: 0).reversed())
Of course, you can wrap it as an extension:
extension String {
func leftPadding(toLength: Int, withPad: String) -> String {
String(String(reversed()).padding(toLength: toLength, withPad: withPad, startingAt: 0).reversed())
}
}

Here's my solution, specific to String, but I'm sure someone smarter than I could make it more generic.
extension String {
func frontPadding(toLength length: Int, withPad pad: String, startingAt index: Int) -> String {
return String(String(self.reversed()).padding(toLength: length, withPad: pad, startingAt: index).reversed())
}
}

Swift 5:
I just spent an embarrassing amount of time playing with this same problem.
let short = "ab"
String(repeating: "0", count: 8 - short.count).appending(short)
// 000000ab

import Foundation // for NSString.padding()
/**
* Custom Extension's API
* ------------------------------
* • str.padEnd(_:_:)
* • str.padStart(_:_:)
* ------------------------------
* • int.padStart(_:_:forceSign:)
*/
extension String {
// str.padEnd(8, "-")
func padEnd(_ length: Int, _ pad: String) -> String {
return padding(toLength: length, withPad: pad, startingAt: 0)
}
// str.padStart(8, "*")
func padStart(_ length: Int, _ pad: String) -> String {
let str = String(self.reversed())
return String(str.padEnd(length, pad).reversed())
}
}
extension Int {
// int.padStart(8)
func padStart(
_ length: Int, // total length
_ pad: String = "0", // pad character
forceSign: Bool = false // force + sign if positive
) -> String {
let isNegative = self < 0
let n = abs(self)
let str = String(n).padStart(length, pad)
return
isNegative ? "- " + str :
forceSign ? "+ " + str :
str
}
}
// test run
let s1 = "abc"
[
s1.padEnd(15, "*"), // abc************
s1.padStart(15, "*"), // ************abc
3.padStart(8, forceSign: true), // + 00000003
(-125).padStart(8) // - 00000125
].forEach{ print($0) }

left padding is here
extension String {
func leftPadding(toLength: Int, withPad character: Character) -> String {
if count < toLength {
return String(repeating: character, count: toLength - count) + self
} else {
return self
}
}
}
result
"234".leftPadding(toLength: 1, withPad: "0") // 234
"234".leftPadding(toLength: 2, withPad: "0") // 234
"234".leftPadding(toLength: 3, withPad: "0") // 234
"234".leftPadding(toLength: 4, withPad: "0") // 0234
"234".leftPadding(toLength: 5, withPad: "0") // 00234

Related

How to align the table rows and columns within exercise on Protocols in Swift

As per exercise, after the title, author or rating is longer than the header in main column/s, the table becomes misaligned (after correcting error: "Repetition count should be non-negative" by changing - to + in line for constant paddingNeeded). I want to keep the column widths as long as the longest String in given column and adjust the spacing in other cells. I was trying to solve it within printTable function with conditional however did not manage to compare columnLabel and item as they are in different loops. Was trying also to set up new function within protocol to manage alignment. Could not figure it out, if you can advice.
protocol TabularDataSource {
var numberOfRows: Int { get }
var numberOfColumns: Int { get }
func label(forColumn column: Int) -> String
func itemFor(row: Int, column: Int) -> String
}
func printTable(_ dataSource: TabularDataSource & CustomStringConvertible) {
print("Tab. 1: \(dataSource)")
var headerRow = "|"
var columnWidths = [Int]()
for ii in 0 ..< dataSource.numberOfColumns {
let columnLabel = dataSource.label(forColumn: ii)
let columnHeader = " \(columnLabel) |"
headerRow += columnHeader
columnWidths.append(columnLabel.count)
}
print(headerRow)
for ii in 0 ..< dataSource.numberOfRows {
var out = "|"
for jj in 0 ..< dataSource.numberOfColumns {
let item = dataSource.itemFor(row: ii, column: jj)
let paddingNeeded = columnWidths[jj] - item.count
let padding = repeatElement(" ", count: paddingNeeded).joined(separator: "")
out += " \(padding)\(item) |"
}
print(out)
}
}
struct Book {
let title: String
let author: String
let rating: Int
}
struct BookCollection: TabularDataSource, CustomStringConvertible {
let name: String
var books = [Book]()
var description: String {
return ("\(name) book collection")
}
init(name: String) {
self.name = name
}
mutating func add(_ book: Book) {
books.append(book)
}
var numberOfRows: Int {
return books.count
}
var numberOfColumns: Int {
return 3
}
func label(forColumn column: Int) -> String {
switch column {
case 0: return "Title"
case 1: return "Author"
case 2: return "Rating"
default: fatalError("Invalid column!")
}
}
func itemFor(row: Int, column: Int) -> String {
let book = books[row]
switch column {
case 0: return book.title
case 1: return String(book.author)
case 2: return String(book.rating)
default: fatalError("Invalid column!")
}
}
}
var bookCollection = BookCollection(name: "Fantasy")
bookCollection.add(Book(title: "Ava", author: "Reno", rating: 7))
bookCollection.add(Book(title: "Vis", author: "Luc", rating: 7))
bookCollection.add(Book(title: "Te", author: "Julo", rating: 9))
printTable(bookCollection)
You could add a method to your datasource to know what's the max length of the column:
protocol TabularDataSource {
func width(for column: Int) -> Int
}
Implementation:
func width(for column: Int) -> Int {
var labels = (0..<numberOfRows).map { itemFor(row: $0, column: column )}
labels.append(label(forColumn: column))
let max = labels.max(by: { $0.count < $1.count })?.count ?? 0
return max + 2 //space before/after
}
Then:
func printTable(_ dataSource: TabularDataSource & CustomStringConvertible) {
var lines = "|"
for aColumn in 0..<dataSource.numberOfColumns {
let label = dataSource.label(forColumn: aColumn)
let totalOfSpaces = dataSource.width(for: aColumn) - label.count
let spaces = repeatElement(" ", count: totalOfSpaces / 2).joined(separator: "")
let additionalSpace = totalOfSpaces % 2 == 0 ? "" : " "
lines += "\(additionalSpace)\(spaces)\(label)\(spaces)|"
}
lines += "\n"
for aRow in 0..<dataSource.numberOfRows {
lines += "|"
for aColumn in 0..<dataSource.numberOfColumns {
let label = dataSource.itemFor(row: aRow, column: aColumn)
let totalOfSpaces = dataSource.width(for: aColumn) - label.count
let additionalSpace = totalOfSpaces % 2 == 0 ? "" : " "
let spaces = repeatElement(" ", count: totalOfSpaces / 2).joined(separator: "")
lines += "\(additionalSpace)\(spaces)\(label)\(spaces)|"
}
lines += "\n"
}
print(lines)
}
Which could be factorized into:
func printTable(_ dataSource: TabularDataSource & CustomStringConvertible) {
func text(text: String, for width: Int) -> String {
let totalOfSpaces = width - text.count
let spaces = repeatElement(" ", count: totalOfSpaces / 2).joined(separator: "")
let additionalSpace = totalOfSpaces % 2 == 0 ? "" : " "
return "\(additionalSpace)\(spaces)\(text)\(spaces)|"
}
lines = "|"
for aColumn in 0..<dataSource.numberOfColumns {
lines += text(text: dataSource.label(forColumn: aColumn), for: dataSource.width(for: aColumn))
}
var lines += "\n"
for aRow in 0..<dataSource.numberOfRows {
lines += "|"
for aColumn in 0..<dataSource.numberOfColumns {
lines += text(text: dataSource.itemFor(row: aRow, column: aColumn), for: dataSource.width(for: aColumn))
}
lines += "\n"
}
print(lines)
}

How to masking the last number in Swift?

How to mask the last string using swift, I have made the code as below. but the code only shows the last number, my expectation is that the code displays the first 5 digits
here my code:
extension StringProtocol {
var masked: String {
return String(repeating: "•", count: Swift.max(0, count-5)) + suffix(5)
} }
var name = "0123456789"
print(name.masked)
I get output: •••••56789
but my expectations: 01234•••••
Use a prefix instead of a suffix
extension StringProtocol {
var masked: String {
return prefix(5) + String(repeating: "•", count: Swift.max(0, count-5))
}
}
You could also create a function instead for parameterizing the number of digits and direction (or even the mask character)
extension StringProtocol {
func masked(_ n: Int = 5, reversed: Bool = false) -> String {
let mask = String(repeating: "•", count: Swift.max(0, count-n))
return reversed ? mask + suffix(n) : prefix(n) + mask
}
}
var name = "0123456789"
print(name.masked(5))
// 01234•••••
print(name.masked(5, reversed: true))
// •••••56789
If you have a case of wanting to mask an email address. here's the code
func maskingEmail(email: String) -> String {
let emailComponents = email.components(separatedBy: "#")
let emailDomainComponents = emailComponents[1].components(separatedBy: ".")
let maskedEmailName = String(repeating: "•", count: Swift.max(0, emailComponents[0].count-3)) + emailComponents[0].suffix(3)
let maskedEmailProvider = String(repeating: "•", count: Swift.max(0, emailDomainComponents[0].count-3)) + emailDomainComponents[0].suffix(3)
let emailDomain = emailDomainComponents[1]
return "\(maskedEmailName)#\(maskedEmailProvider).\(emailDomain)"
}
// The Output
print(maskingEmail(email: "pr1vaterelay#gmail.com")) // •••••••••lay#••ail.com
print(maskingEmail(email: "private_relay#bk.ru")) // ••••••••••lay#bk.ru
print(maskingEmail(email: "private.relay#protonmail.com")) // ••••••••••lay#•••••••ail.com

Split the string into spaces

I am try convert my double in string with space. Now i have number look like this 1234567.54 and i need get string like this "1 234 567.54" How i can do it in swift?
extension StringProtocol where Self: RangeReplaceableCollection {
mutating func insert(separator: String, every n: Int) {
indices.reversed().forEach {
if $0 != startIndex { if distance(from: startIndex, to: $0) % n == 0 { insert(contentsOf: separator, at: $0) } }
}
}
func inserting(separator: String, every n: Int) -> Self {
var string = self
string.insert(separator: separator, every: n)
return string
}
}
Its work but not as much as I need
You can use NumberFormatter :
let formatter = NumberFormatter()
formatter.groupingSeparator = " "
formatter.numberStyle = .decimal
let formattedNumber = formatter.string(from: 1234567.89)
print(formattedNumber)
printed: 1 234 567.89

Swift 4.0 Right Padding a String [duplicate]

I'm trying to print a list of Strings all padded to the same width.
In C, I would use something like printf("%40s", cstr), where cstr is a C string.
In Swift, the best I could come up is this:
line += String(format: "%40s",string.cStringUsingEncoding(<someEncoding>))
Is there a better way ?
In Swift 3 you can use:
let str = "Test string"
let paddedStr = str.padding(toLength: 20, withPad: " ", startingAt: 0)
Result string: "Test string "
If you need to pad to the left the text (right justify), you can write the following function as an extension to String:
extension String {
func leftPadding(toLength: Int, withPad character: Character) -> String {
let newLength = self.characters.count
if newLength < toLength {
return String(repeatElement(character, count: toLength - newLength)) + self
} else {
return self.substring(from: index(self.startIndex, offsetBy: newLength - toLength))
}
}
}
So if you write:
let str = "Test string"
let paddedStr = str.leftPadding(toLength: 20, withPad: " ")
Result string: " Test string"
In Swift 4.1 the substring method is deprecated and there are a number of new methods to obtain a substring. Either prefix, suffix or subscripting the String with a Range<String.Index>.
For the previous extension we can use the suffix method to accomplish the same result. Since the suffix method returns a String.SubSequence, it needs to be converted into a String before being returned.
extension String {
func leftPadding(toLength: Int, withPad character: Character) -> String {
let stringLength = self.count
if stringLength < toLength {
return String(repeatElement(character, count: toLength - stringLength)) + self
} else {
return String(self.suffix(toLength))
}
}
}
For Swift >= 3
line += string.padding(toLength: 40, withPad: " ", startingAt: 0)
For Swift < 3
NSString has the stringByPaddingToLength: method:
line += string.stringByPaddingToLength(40, withString: " ", startingAtIndex: 0)
extension RangeReplaceableCollection where Self: StringProtocol {
func paddingToLeft(upTo length: Int, using element: Element = " ") -> SubSequence {
return repeatElement(element, count: Swift.max(0, length-count)) + suffix(Swift.max(count, count-length))
}
}
"123".paddingToLeft(upTo: 5) // " 123"
"123".paddingToLeft(upTo: 5, using: "0") // "00123"
"123".paddingToLeft(upTo: 3, using: "0") // "123"
"$199.99".dropLast(3).paddingToLeft(upTo: 10, using: "_") // "______$199"
To replicate the same behaviour as padding(toLength:, withPad:, startingAt:) we can add rotateTo left functionality to RangeReplaceableCollection
extension RangeReplaceableCollection {
func rotatingLeft(positions: Int) -> SubSequence {
let index = self.index(startIndex, offsetBy: positions, limitedBy: endIndex) ?? endIndex
return self[index...] + self[..<index]
}
}
And implement it as follow:
extension RangeReplaceableCollection where Self: StringProtocol {
func paddingToLeft<S: StringProtocol & RangeReplaceableCollection>(upTo length: Int, with string: S, startingAt index: Int = 0) -> SubSequence {
let string = string.rotatingLeft(positions: index)
return repeatElement(string, count: length-count/string.count)
.joined().prefix(length-count) + suffix(Swift.max(count, count-length))
}
}
"123".paddingToLeft(upTo: 10, with: "abc", startingAt: 2) // "cabcabc123"
"123".padding(toLength: 10, withPad: "abc", startingAt: 2) // "123cabcabc"
Put all string-format-code into extension and reuse it wherever you want.
extension String {
func padding(length: Int) -> String {
return self.stringByPaddingToLength(length, withString: " ", startingAtIndex: 0)
}
func padding(length: Int, paddingString: String) -> String {
return self.stringByPaddingToLength(length, withString: paddingString, startingAtIndex: 0)
}
}
var str = "str"
print(str.padding(10)) // "str "
print(str.padding(10, paddingString: "+")) // "str+++++++"
The following two functions return a string padded to the given width, either left or right justified. It is pure Swift 4, no NSString, and no C string either. You may choose if a string longer than the padding width will be truncated or not.
extension String {
func rightJustified(width: Int, truncate: Bool = false) -> String {
guard width > count else {
return truncate ? String(suffix(width)) : self
}
return String(repeating: " ", count: width - count) + self
}
func leftJustified(width: Int, truncate: Bool = false) -> String {
guard width > count else {
return truncate ? String(prefix(width)) : self
}
return self + String(repeating: " ", count: width - count)
}
}
For left padding, you can use double-reverse trick:
String(String(s.reversed()).padding(toLength: 5, withPad: "0", startingAt: 0).reversed())
Of course, you can wrap it as an extension:
extension String {
func leftPadding(toLength: Int, withPad: String) -> String {
String(String(reversed()).padding(toLength: toLength, withPad: withPad, startingAt: 0).reversed())
}
}
Here's my solution, specific to String, but I'm sure someone smarter than I could make it more generic.
extension String {
func frontPadding(toLength length: Int, withPad pad: String, startingAt index: Int) -> String {
return String(String(self.reversed()).padding(toLength: length, withPad: pad, startingAt: index).reversed())
}
}
Swift 5:
I just spent an embarrassing amount of time playing with this same problem.
let short = "ab"
String(repeating: "0", count: 8 - short.count).appending(short)
// 000000ab
import Foundation // for NSString.padding()
/**
* Custom Extension's API
* ------------------------------
* • str.padEnd(_:_:)
* • str.padStart(_:_:)
* ------------------------------
* • int.padStart(_:_:forceSign:)
*/
extension String {
// str.padEnd(8, "-")
func padEnd(_ length: Int, _ pad: String) -> String {
return padding(toLength: length, withPad: pad, startingAt: 0)
}
// str.padStart(8, "*")
func padStart(_ length: Int, _ pad: String) -> String {
let str = String(self.reversed())
return String(str.padEnd(length, pad).reversed())
}
}
extension Int {
// int.padStart(8)
func padStart(
_ length: Int, // total length
_ pad: String = "0", // pad character
forceSign: Bool = false // force + sign if positive
) -> String {
let isNegative = self < 0
let n = abs(self)
let str = String(n).padStart(length, pad)
return
isNegative ? "- " + str :
forceSign ? "+ " + str :
str
}
}
// test run
let s1 = "abc"
[
s1.padEnd(15, "*"), // abc************
s1.padStart(15, "*"), // ************abc
3.padStart(8, forceSign: true), // + 00000003
(-125).padStart(8) // - 00000125
].forEach{ print($0) }
left padding is here
extension String {
func leftPadding(toLength: Int, withPad character: Character) -> String {
if count < toLength {
return String(repeating: character, count: toLength - count) + self
} else {
return self
}
}
}
result
"234".leftPadding(toLength: 1, withPad: "0") // 234
"234".leftPadding(toLength: 2, withPad: "0") // 234
"234".leftPadding(toLength: 3, withPad: "0") // 234
"234".leftPadding(toLength: 4, withPad: "0") // 0234
"234".leftPadding(toLength: 5, withPad: "0") // 00234

Swift find all occurrences of a substring

I have an extension here of the String class in Swift that returns the index of the first letter of a given substring.
Can anybody please help me make it so it will return an array of all occurrences instead of just the first one?
Thank you.
extension String {
func indexOf(string : String) -> Int {
var index = -1
if let range = self.range(of : string) {
if !range.isEmpty {
index = distance(from : self.startIndex, to : range.lowerBound)
}
}
return index
}
}
For example instead of a return value of 50 I would like something like [50, 74, 91, 103]
You just keep advancing the search range until you can't find any more instances of the substring:
extension String {
func indicesOf(string: String) -> [Int] {
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
}
let keyword = "a"
let html = "aaaa"
let indicies = html.indicesOf(string: keyword)
print(indicies) // [0, 1, 2, 3]
I know we aren't playing code golf here, but for anyone interested in a functional style one-line implementation that doesn't use vars or loops, this is another possible solution:
extension String {
func indices(of string: String) -> [Int] {
return indices.reduce([]) { $1.encodedOffset > ($0.last ?? -1) && self[$1...].hasPrefix(string) ? $0 + [$1.encodedOffset] : $0 }
}
}
Here are 2 functions. One returns [Range<String.Index>], the other returns [Range<Int>]. If you don't need the former, you can make it private. I've designed it to mimic the range(of:options:range:locale:) method, so it supports all the same features.
import Foundation
extension String {
public func allRanges(
of aString: String,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> [Range<String.Index>] {
// the slice within which to search
let slice = (range == nil) ? self[...] : self[range!]
var previousEnd = s.startIndex
var ranges = [Range<String.Index>]()
while let r = slice.range(
of: aString, options: options,
range: previousEnd ..< s.endIndex,
locale: locale
) {
if previousEnd != self.endIndex { // don't increment past the end
previousEnd = self.index(after: r.lowerBound)
}
ranges.append(r)
}
return ranges
}
public func allRanges(
of aString: String,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> [Range<Int>] {
return allRanges(of: aString, options: options, range: range, locale: locale)
.map(indexRangeToIntRange)
}
private func indexRangeToIntRange(_ range: Range<String.Index>) -> Range<Int> {
return indexToInt(range.lowerBound) ..< indexToInt(range.upperBound)
}
private func indexToInt(_ index: String.Index) -> Int {
return self.distance(from: self.startIndex, to: index)
}
}
let s = "abc abc abc abc abc"
print(s.allRanges(of: "abc") as [Range<String.Index>])
print()
print(s.allRanges(of: "abc") as [Range<Int>])
There's not really a built-in function to do this, but we can implement a modified Knuth-Morris-Pratt algorithm to get all the indices of the string we want to match. It should also be very performant as we don't need to repeatedly call range on the string.
extension String {
func indicesOf(string: String) -> [Int] {
// Converting to an array of utf8 characters makes indicing and comparing a lot easier
let search = self.utf8.map { $0 }
let word = string.utf8.map { $0 }
var indices = [Int]()
// m - the beginning of the current match in the search string
// i - the position of the current character in the string we're trying to match
var m = 0, i = 0
while m + i < search.count {
if word[i] == search[m+i] {
if i == word.count - 1 {
indices.append(m)
m += i + 1
i = 0
} else {
i += 1
}
} else {
m += 1
i = 0
}
}
return indices
}
}
Please check the following answer for finding multiple items in multiple locations
func indicesOf(string: String) -> [Int] {
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
func attributedStringWithColor(_ strings: [String], color: UIColor, characterSpacing: UInt? = nil) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: self)
for string in strings {
let indexes = self.indicesOf(string: string)
for index in indexes {
let range = NSRange(location: index, length: string.count)
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range)
}
}
guard let characterSpacing = characterSpacing else {return attributedString}
attributedString.addAttribute(NSAttributedString.Key.kern, value: characterSpacing, range: NSRange(location: 0, length: attributedString.length))
return attributedString
}
can be used as follows :
let message = "Item 1 + Item 2 + Item 3"
message.attributedStringWithColor(["Item", "+"], color: UIColor.red)
and gets the result
This could be done with recursive method. I used a numeric string to test it. It returns an optional array of Int, meaning it will be nil if no substring can be found.
extension String {
func indexes(of string: String, offset: Int = 0) -> [Int]? {
if let range = self.range(of : string) {
if !range.isEmpty {
let index = distance(from : self.startIndex, to : range.lowerBound) + offset
var result = [index]
let substr = self.substring(from: range.upperBound)
if let substrIndexes = substr.indexes(of: string, offset: index + distance(from: range.lowerBound, to: range.upperBound)) {
result.append(contentsOf: substrIndexes)
}
return result
}
}
return nil
}
}
let numericString = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
numericString.indexes(of: "3456")
I have tweaked the accepted answer so that case sensitivity can be configured
extension String {
func allIndexes(of subString: String, caseSensitive: Bool = true) -> [Int] {
let subString = caseSensitive ? subString : subString.lowercased()
let mainString = caseSensitive ? self : self.lowercased()
var indices = [Int]()
var searchStartIndex = mainString.startIndex
while searchStartIndex < mainString.endIndex,
let range = mainString.range(of: subString, range: searchStartIndex..<mainString.endIndex),
!range.isEmpty
{
let index = distance(from: mainString.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
}