Word & Character Counter — Swift source
Live word, character, sentence, and paragraph counts plus reading-time estimate as you type.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// word-counter — Swift port: word / character / sentence / paragraph counts + 200-wpm reading-time estimate.
//
// Display snippet: ports the counters from the TypeScript lib (src/lib/wordCount.ts).
// String.count counts Characters (grapheme clusters); the TS reference counts
// UTF-16 code units — the two agree for all BMP text.
import Foundation
enum WordCounter {
// Words: split on whitespace-and-newlines, dropping empty pieces.
// CharacterSet.whitespacesAndNewlines covers the JS /\s/ class.
static func countWords(_ t: String) -> Int {
t.components(separatedBy: .whitespacesAndNewlines)
.filter { !$0.isEmpty }
.count
}
// Total character count, whitespace included.
static func countChars(_ t: String) -> Int { t.count }
// Character count with all whitespace removed.
static func countCharsNoSpaces(_ t: String) -> Int {
t.filter { !$0.isWhitespace }.count
}
// Sentences: maximal runs of non-terminators each closed by one or more
// of . ! ? — the TS regex /[^.!?]+[.!?]+/g, as a single scan.
static func countSentences(_ t: String) -> Int {
var n = 0
var body = false
for ch in t {
if ch == "." || ch == "!" || ch == "?" {
if body { n += 1; body = false }
} else {
body = true
}
}
return n
}
// Paragraphs: segments split by runs of 2+ newlines; blank segments
// don't count (the TS split(/\n{2,}/).filter(s => s.trim())).
static func countParagraphs(_ t: String) -> Int {
var n = 0
var run = 0
var blank = true
for ch in t {
if ch == "\n" {
run += 1
if run == 2 { // blank line closes the paragraph
if !blank { n += 1 }
blank = true
}
continue
}
run = 0
if !ch.isWhitespace { blank = false }
}
return blank ? n : n + 1
}
// Reading time at 200 wpm: 0 for empty input, otherwise >= 1 minute.
// (words + 100) / 200 reproduces round(words / 200.0).
static func readingTimeMin(_ words: Int) -> Int {
words <= 0 ? 0 : max(1, (words + 100) / 200)
}
}
let text = "CosmoDev counts words. Live totals update as you type!\n\n" +
"Second paragraph? Two sentences - each with words. Zero logins needed."
print("words: \(WordCounter.countWords(text))") // 20
print("chars: \(WordCounter.countChars(text))") // 126
print("no spaces: \(WordCounter.countCharsNoSpaces(text))") // 106
print("sentences: \(WordCounter.countSentences(text))") // 5
print("paragraphs: \(WordCounter.countParagraphs(text))") // 2
print("reading min: \(WordCounter.readingTimeMin(WordCounter.countWords(text)))") // 1
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →