EZSwiftExtensions/Sources/StringExtensions.swift

521 lines
21 KiB
Swift

//
// StringExtensions.swift
// EZSwiftExtensions
//
// Created by Goktug Yilmaz on 15/07/15.
// Copyright (c) 2015 Goktug Yilmaz. All rights reserved.
//
// swiftlint:disable line_length
// swiftlint:disable trailing_whitespace
#if os(OSX)
import AppKit
#endif
#if os(iOS) || os(tvOS)
import UIKit
#endif
extension String {
/// EZSE: Init string with a base64 encoded string
init ? (base64: String) {
let pad = String(repeating: "=", count: base64.length % 4)
let base64Padded = base64 + pad
if let decodedData = Data(base64Encoded: base64Padded, options: NSData.Base64DecodingOptions(rawValue: 0)), let decodedString = NSString(data: decodedData, encoding: String.Encoding.utf8.rawValue) {
self.init(decodedString)
return
}
return nil
}
/// EZSE: base64 encoded of string
var base64: String {
let plainData = (self as NSString).data(using: String.Encoding.utf8.rawValue)
let base64String = plainData!.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
return base64String
}
/// EZSE: Cut string from integerIndex to the end
public subscript(integerIndex: Int) -> Character {
let index = self.index(startIndex, offsetBy: integerIndex)
return self[index]
}
/// EZSE: Cut string from range
public subscript(integerRange: Range<Int>) -> String {
let start = self.index(startIndex, offsetBy: integerRange.lowerBound)
let end = self.index(startIndex, offsetBy: integerRange.upperBound)
return String(self[start..<end])
}
/// EZSE: Cut string from closedrange
public subscript(integerClosedRange: ClosedRange<Int>) -> String {
return self[integerClosedRange.lowerBound..<(integerClosedRange.upperBound + 1)]
}
/// EZSE: Character count
public var length: Int {
return self.count
}
/// EZSE: Counts number of instances of the input inside String
public func count(_ substring: String) -> Int {
return components(separatedBy: substring).count - 1
}
/// EZSE: Capitalizes first character of String
public mutating func capitalizeFirst() {
guard self.count > 0 else { return }
self.replaceSubrange(startIndex...startIndex, with: String(self[startIndex]).capitalized)
}
/// EZSE: Capitalizes first character of String, returns a new string
public func capitalizedFirst() -> String {
guard self.count > 0 else { return self }
var result = self
result.replaceSubrange(startIndex...startIndex, with: String(self[startIndex]).capitalized)
return result
}
/// EZSE: Uppercases first 'count' characters of String
public mutating func uppercasePrefix(_ count: Int) {
guard self.count > 0 && count > 0 else { return }
self.replaceSubrange(startIndex..<self.index(startIndex, offsetBy: min(count, length)),
with: String(self[startIndex..<self.index(startIndex, offsetBy: min(count, length))]).uppercased())
}
/// EZSE: Uppercases first 'count' characters of String, returns a new string
public func uppercasedPrefix(_ count: Int) -> String {
guard self.count > 0 && count > 0 else { return self }
var result = self
result.replaceSubrange(startIndex..<self.index(startIndex, offsetBy: min(count, length)),
with: String(self[startIndex..<self.index(startIndex, offsetBy: min(count, length))]).uppercased())
return result
}
/// EZSE: Uppercases last 'count' characters of String
public mutating func uppercaseSuffix(_ count: Int) {
guard self.count > 0 && count > 0 else { return }
self.replaceSubrange(self.index(endIndex, offsetBy: -min(count, length))..<endIndex,
with: String(self[self.index(endIndex, offsetBy: -min(count, length))..<endIndex]).uppercased())
}
/// EZSE: Uppercases last 'count' characters of String, returns a new string
public func uppercasedSuffix(_ count: Int) -> String {
guard self.count > 0 && count > 0 else { return self }
var result = self
result.replaceSubrange(self.index(endIndex, offsetBy: -min(count, length))..<endIndex,
with: String(self[self.index(endIndex, offsetBy: -min(count, length))..<endIndex]).uppercased())
return result
}
/// EZSE: Uppercases string in range 'range' (from range.startIndex to range.endIndex)
public mutating func uppercase(range: CountableRange<Int>) {
let from = max(range.lowerBound, 0), to = min(range.upperBound, length)
guard self.count > 0 && (0..<length).contains(from) else { return }
self.replaceSubrange(self.index(startIndex, offsetBy: from)..<self.index(startIndex, offsetBy: to),
with: String(self[self.index(startIndex, offsetBy: from)..<self.index(startIndex, offsetBy: to)]).uppercased())
}
/// EZSE: Uppercases string in range 'range' (from range.startIndex to range.endIndex), returns new string
public func uppercased(range: CountableRange<Int>) -> String {
let from = max(range.lowerBound, 0), to = min(range.upperBound, length)
guard self.count > 0 && (0..<length).contains(from) else { return self }
var result = self
result.replaceSubrange(self.index(startIndex, offsetBy: from)..<self.index(startIndex, offsetBy: to),
with: String(self[self.index(startIndex, offsetBy: from)..<self.index(startIndex, offsetBy: to)]).uppercased())
return result
}
/// EZSE: Lowercases first character of String
public mutating func lowercaseFirst() {
guard self.count > 0 else { return }
self.replaceSubrange(startIndex...startIndex, with: String(self[startIndex]).lowercased())
}
/// EZSE: Lowercases first character of String, returns a new string
public func lowercasedFirst() -> String {
guard self.count > 0 else { return self }
var result = self
result.replaceSubrange(startIndex...startIndex, with: String(self[startIndex]).lowercased())
return result
}
/// EZSE: Lowercases first 'count' characters of String
public mutating func lowercasePrefix(_ count: Int) {
guard self.count > 0 && count > 0 else { return }
self.replaceSubrange(startIndex..<self.index(startIndex, offsetBy: min(count, length)),
with: String(self[startIndex..<self.index(startIndex, offsetBy: min(count, length))]).lowercased())
}
/// EZSE: Lowercases first 'count' characters of String, returns a new string
public func lowercasedPrefix(_ count: Int) -> String {
guard self.count > 0 && count > 0 else { return self }
var result = self
result.replaceSubrange(startIndex..<self.index(startIndex, offsetBy: min(count, length)),
with: String(self[startIndex..<self.index(startIndex, offsetBy: min(count, length))]).lowercased())
return result
}
/// EZSE: Lowercases last 'count' characters of String
public mutating func lowercaseSuffix(_ count: Int) {
guard self.count > 0 && count > 0 else { return }
self.replaceSubrange(self.index(endIndex, offsetBy: -min(count, length))..<endIndex,
with: String(self[self.index(endIndex, offsetBy: -min(count, length))..<endIndex]).lowercased())
}
/// EZSE: Lowercases last 'count' characters of String, returns a new string
public func lowercasedSuffix(_ count: Int) -> String {
guard self.count > 0 && count > 0 else { return self }
var result = self
result.replaceSubrange(self.index(endIndex, offsetBy: -min(count, length))..<endIndex,
with: String(self[self.index(endIndex, offsetBy: -min(count, length))..<endIndex]).lowercased())
return result
}
/// EZSE: Lowercases string in range 'range' (from range.startIndex to range.endIndex)
public mutating func lowercase(range: CountableRange<Int>) {
let from = max(range.lowerBound, 0), to = min(range.upperBound, length)
guard self.count > 0 && (0..<length).contains(from) else { return }
self.replaceSubrange(self.index(startIndex, offsetBy: from)..<self.index(startIndex, offsetBy: to),
with: String(self[self.index(startIndex, offsetBy: from)..<self.index(startIndex, offsetBy: to)]).lowercased())
}
/// EZSE: Lowercases string in range 'range' (from range.startIndex to range.endIndex), returns new string
public func lowercased(range: CountableRange<Int>) -> String {
let from = max(range.lowerBound, 0), to = min(range.upperBound, length)
guard self.count > 0 && (0..<length).contains(from) else { return self }
var result = self
result.replaceSubrange(self.index(startIndex, offsetBy: from)..<self.index(startIndex, offsetBy: to),
with: String(self[self.index(startIndex, offsetBy: from)..<self.index(startIndex, offsetBy: to)]).lowercased())
return result
}
/// EZSE: Counts whitespace & new lines
@available(*, deprecated: 1.6, renamed: "isBlank")
public func isOnlyEmptySpacesAndNewLineCharacters() -> Bool {
let characterSet = CharacterSet.whitespacesAndNewlines
let newText = self.trimmingCharacters(in: characterSet)
return newText.isEmpty
}
/// EZSE: Checks if string is empty or consists only of whitespace and newline characters
public var isBlank: Bool {
let trimmed = trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty
}
/// EZSE: Trims white space and new line characters
public mutating func trim() {
self = self.trimmed()
}
/// EZSE: Trims white space and new line characters, returns a new string
public func trimmed() -> String {
return self.trimmingCharacters(in: .whitespacesAndNewlines)
}
/// EZSE: Position of begining character of substing
public func positionOfSubstring(_ subString: String, caseInsensitive: Bool = false, fromEnd: Bool = false) -> Int {
if subString.isEmpty {
return -1
}
var searchOption = fromEnd ? NSString.CompareOptions.anchored : NSString.CompareOptions.backwards
if caseInsensitive {
searchOption.insert(NSString.CompareOptions.caseInsensitive)
}
if let range = self.range(of: subString, options: searchOption), !range.isEmpty {
return self.distance(from: self.startIndex, to: range.lowerBound)
}
return -1
}
/// EZSE: split string using a spearator string, returns an array of string
public func split(_ separator: String) -> [String] {
return self.components(separatedBy: separator).filter {
!$0.trimmed().isEmpty
}
}
/// EZSE: split string with delimiters, returns an array of string
public func split(_ characters: CharacterSet) -> [String] {
return self.components(separatedBy: characters).filter {
!$0.trimmed().isEmpty
}
}
/// EZSE : Returns count of words in string
public var countofWords: Int {
let regex = try? NSRegularExpression(pattern: "\\w+", options: NSRegularExpression.Options())
return regex?.numberOfMatches(in: self, options: NSRegularExpression.MatchingOptions(), range: NSRange(location: 0, length: self.length)) ?? 0
}
/// EZSE : Returns count of paragraphs in string
public var countofParagraphs: Int {
let regex = try? NSRegularExpression(pattern: "\\n", options: NSRegularExpression.Options())
let str = self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
return (regex?.numberOfMatches(in: str, options: NSRegularExpression.MatchingOptions(), range: NSRange(location: 0, length: str.length)) ?? -1) + 1
}
internal func rangeFromNSRange(_ nsRange: NSRange) -> Range<String.Index>? {
let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location)
let to16 = utf16.index(from16, offsetBy: nsRange.length)
if let from = String.Index(from16, within: self),
let to = String.Index(to16, within: self) {
return from ..< to
}
return nil
}
/// EZSE: Find matches of regular expression in string
public func matchesForRegexInText(_ regex: String!) -> [String] {
let regex = try? NSRegularExpression(pattern: regex, options: [])
let results = regex?.matches(in: self, options: [], range: NSRange(location: 0, length: self.length)) ?? []
return results.map { String(self[self.rangeFromNSRange($0.range)!]) }
}
/// EZSE: Checks if String contains Email
public var isEmail: Bool {
let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let firstMatch = dataDetector?.firstMatch(in: self, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSRange(location: 0, length: length))
return (firstMatch?.range.location != NSNotFound && firstMatch?.url?.scheme == "mailto")
}
/// EZSE: Returns if String is a number
public func isNumber() -> Bool {
return NumberFormatter().number(from: self) != nil ? true : false
}
/// EZSE: Extracts URLS from String
public var extractURLs: [URL] {
var urls: [URL] = []
let detector: NSDataDetector?
do {
detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
} catch _ as NSError {
detector = nil
}
let text = self
if let detector = detector {
detector.enumerateMatches(in: text,
options: [],
range: NSRange(location: 0, length: text.count),
using: { (result: NSTextCheckingResult?, _, _) -> Void in
if let result = result, let url = result.url {
urls.append(url)
}
})
}
return urls
}
/// EZSE: Checking if String contains input with comparing options
public func contains(_ find: String, compareOption: NSString.CompareOptions) -> Bool {
return self.range(of: find, options: compareOption) != nil
}
/// EZSE: Converts String to Int
public func toInt() -> Int? {
if let num = NumberFormatter().number(from: self) {
return num.intValue
} else {
return nil
}
}
/// EZSE: Converts String to Double
public func toDouble() -> Double? {
if let num = NumberFormatter().number(from: self) {
return num.doubleValue
} else {
return nil
}
}
/// EZSE: Converts String to Float
public func toFloat() -> Float? {
if let num = NumberFormatter().number(from: self) {
return num.floatValue
} else {
return nil
}
}
/// EZSE: Converts String to Bool
public func toBool() -> Bool? {
let trimmedString = trimmed().lowercased()
if trimmedString == "true" || trimmedString == "false" {
return (trimmedString as NSString).boolValue
}
return nil
}
///EZSE: Returns the first index of the occurency of the character in String
public func getIndexOf(_ char: Character) -> Int? {
for (index, c) in self.enumerated() where c == char {
return index
}
return nil
}
/// EZSE: Converts String to NSString
public var toNSString: NSString { return self as NSString }
#if os(iOS)
///EZSE: Returns bold NSAttributedString
public func bold() -> NSAttributedString {
let boldString = NSMutableAttributedString(string: self, attributes: [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)])
return boldString
}
#endif
#if os(iOS)
///EZSE: Returns underlined NSAttributedString
public func underline() -> NSAttributedString {
let underlineString = NSAttributedString(string: self, attributes: [NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue])
return underlineString
}
#endif
#if os(iOS)
///EZSE: Returns italic NSAttributedString
public func italic() -> NSAttributedString {
let italicString = NSMutableAttributedString(string: self, attributes: [NSAttributedStringKey.font: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)])
return italicString
}
#endif
#if os(iOS)
///EZSE: Returns hight of rendered string
public func height(_ width: CGFloat, font: UIFont, lineBreakMode: NSLineBreakMode?) -> CGFloat {
var attrib: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: font]
if lineBreakMode != nil {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = lineBreakMode!
attrib.updateValue(paragraphStyle, forKey: NSAttributedStringKey.paragraphStyle)
}
let size = CGSize(width: width, height: CGFloat(Double.greatestFiniteMagnitude))
return ceil((self as NSString).boundingRect(with: size, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attrib, context: nil).height)
}
#endif
#if os(iOS) || os(tvOS)
///EZSE: Returns NSAttributedString
public func color(_ color: UIColor) -> NSAttributedString {
let colorString = NSMutableAttributedString(string: self, attributes: [NSAttributedStringKey.foregroundColor: color])
return colorString
}
///EZSE: Returns NSAttributedString
public func colorSubString(_ subString: String, color: UIColor) -> NSMutableAttributedString {
var start = 0
var ranges: [NSRange] = []
while true {
let range = (self as NSString).range(of: subString, options: NSString.CompareOptions.literal, range: NSRange(location: start, length: (self as NSString).length - start))
if range.location == NSNotFound {
break
} else {
ranges.append(range)
start = range.location + range.length
}
}
let attrText = NSMutableAttributedString(string: self)
for range in ranges {
attrText.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: range)
}
return attrText
}
#endif
/// EZSE: Checks if String contains Emoji
public func includesEmoji() -> Bool {
for i in 0...length {
let c: unichar = (self as NSString).character(at: i)
if (0xD800 <= c && c <= 0xDBFF) || (0xDC00 <= c && c <= 0xDFFF) {
return true
}
}
return false
}
#if os(iOS)
/// EZSE: copy string to pasteboard
public func addToPasteboard() {
let pasteboard = UIPasteboard.general
pasteboard.string = self
}
#endif
// EZSE: URL encode a string (percent encoding special chars)
public func urlEncoded() -> String {
return self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
}
// EZSE: URL encode a string (percent encoding special chars) mutating version
mutating func urlEncode() {
self = urlEncoded()
}
// EZSE: Removes percent encoding from string
public func urlDecoded() -> String {
return removingPercentEncoding ?? self
}
// EZSE : Mutating versin of urlDecoded
mutating func urlDecode() {
self = urlDecoded()
}
}
extension String {
init(_ value: Float, precision: Int) {
let nFormatter = NumberFormatter()
nFormatter.numberStyle = .decimal
nFormatter.maximumFractionDigits = precision
self = nFormatter.string(from: NSNumber(value: value))!
}
init(_ value: Double, precision: Int) {
let nFormatter = NumberFormatter()
nFormatter.numberStyle = .decimal
nFormatter.maximumFractionDigits = precision
self = nFormatter.string(from: NSNumber(value: value))!
}
}
/// EZSE: Pattern matching of strings via defined functions
public func ~=<T> (pattern: ((T) -> Bool), value: T) -> Bool {
return pattern(value)
}
/// EZSE: Can be used in switch-case
public func hasPrefix(_ prefix: String) -> (_ value: String) -> Bool {
return { (value: String) -> Bool in
value.hasPrefix(prefix)
}
}
/// EZSE: Can be used in switch-case
public func hasSuffix(_ suffix: String) -> (_ value: String) -> Bool {
return { (value: String) -> Bool in
value.hasSuffix(suffix)
}
}