// // UIWindow+Ext.swift // DouYinSwift5 // // Created by lym on 2020/7/23. // Copyright © 2020 lym. All rights reserved. // import Foundation import UIKit public extension String { static func randomString(_ length: Int) -> String { let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" return String((0 ..< length).map { _ in letters.randomElement()! }) } init(deviceToken: Data) { self = deviceToken.map { String(format: "%.2hhx", $0) }.joined() } /// 是否包含 // func contains(_ regular: String) -> Bool { // return range(of: regular, options: .regularExpression, range: nil, locale: nil) != nil // } /// 去掉字符串首尾的空格换行,中间的空格和换行忽略 var trimmed: String { return trimmingCharacters(in: .whitespacesAndNewlines) } func removeSpaceAndNewLine() -> String { return trimmingCharacters(in: .whitespacesAndNewlines) } var removingAllWhitespace: String { return replacingOccurrences(of: " ", with: "") } /// 是否不为空 /// /// "", " ", "\n", " \n "都视为空 /// 不为空返回true, 为空返回false var isNotBlank: Bool { return !trimmed.isEmpty } /// 字符串的全部范围 var rangeOfAll: NSRange { return NSRange(location: 0, length: count) } /// 是否为 nil 或者为 "" static func realEmpty(str: String?) -> Bool { return (str == nil || str == "") } /// JSON 字符串 转换为 字典 func toDictionary() -> [String: Any]? { guard let jsonData = data(using: .utf8), let dict = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) else { return nil } return (dict as! [String: Any]) } /// JSON 字符串 转换为 Array func jsonStringToArray() -> [Any]? { guard let jsonData: Data = data(using: .utf8), let array = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) else { return nil } return (array as! [Any]) } /// 获取范围 @discardableResult func matchStrRange(_ matchStr: String) -> [NSRange] { var selfStr = self as NSString var withStr = Array(repeating: "X", count: (matchStr as NSString).length).joined(separator: "") // 辅助字符串 if matchStr == withStr { withStr = withStr.lowercased() } // 临时处理辅助字符串差错 var allRange = [NSRange]() while selfStr.range(of: matchStr).location != NSNotFound { let range = selfStr.range(of: matchStr) allRange.append(NSRange(location: range.location, length: range.length)) selfStr = selfStr.replacingCharacters(in: NSMakeRange(range.location, range.length), with: withStr) as NSString } return allRange } /// 数字和字母混合 static func randomAlphaNumericString(length: Int) -> String { let allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" let allowedCharsCount = UInt32(allowedChars.count) var randomString = "" for _ in 0 ..< length { let randomNum = Int(arc4random_uniform(allowedCharsCount)) let randomIndex = allowedChars.index(allowedChars.startIndex, offsetBy: randomNum) let newCharacter = allowedChars[randomIndex] randomString += String(newCharacter) } return randomString } /// 生成指定长度的随机数字字符串 static func randomNumber(length: Int) -> String { var result = "" for _ in 0.. String { return NSLocalizedString(self, tableName: tableName, bundle: bundle, comment: "") } } // MAKR : 正则分割 // 定义一个结构体来承载分割后的结果 struct SplitedResult { let fragment: String let isMatched: Bool let captures: [String?] } extension String { /// 正则分割字符串 func split( usingRegex pattern: String, options: NSRegularExpression.Options = .dotMatchesLineSeparators ) -> [SplitedResult] { do { let regex = try NSRegularExpression(pattern: pattern, options: options) let matches = regex.matches(in: self, options: [], range: NSRange(location: 0, length: utf16.count)) var currentIndex = startIndex var range: Range var captures: [String?] = [] var results: [SplitedResult] = [] for match in matches { range = Range(match.range, in: self)! if range.lowerBound > currentIndex { results.append(SplitedResult(fragment: String(self[currentIndex ..< range.lowerBound]), isMatched: false, captures: [])) } if match.numberOfRanges > 1 { for i in 1 ..< match.numberOfRanges { if let _range = Range(match.range(at: i), in: self) { captures.append(String(self[_range])) } else { captures.append(nil) } } } results.append(SplitedResult(fragment: String(self[range]), isMatched: true, captures: captures)) currentIndex = range.upperBound captures.removeAll() } if endIndex > currentIndex { results.append(SplitedResult(fragment: String(self[currentIndex ..< endIndex]), isMatched: false, captures: [])) } return results } catch { fatalError("正则表达式有误,请更正后再试!") } } } extension String { // base64 func encodeBase64() -> String { let plainData = data(using: String.Encoding.utf8) let base64String = plainData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) return base64String! } // func decodeBase64() -> String { let decodedData = NSData(base64Encoded: self, options: NSData.Base64DecodingOptions(rawValue: 0)) let decodedString = NSString(data: decodedData! as Data, encoding: String.Encoding.utf8.rawValue)! as String return decodedString } } extension String { /// 计算字体宽度 字体大小 public func textWidth(font: UIFont) -> CGFloat { let str = self as NSString let size = CGSize(width: 20000, height: 100) let attributes = [NSAttributedString.Key.font: font] let labelSize = str.boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil).size return labelSize.width } /// 计算指定字体的尺寸 /// /// - Parameters: /// - font: 字体 /// - size: 区域大小 /// - lineBreakMode: 换行模式 /// - Returns: 尺寸 func size(for font: UIFont, size: CGSize, lineBreakMode: NSLineBreakMode) -> CGSize { var attr: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font] if lineBreakMode != .byWordWrapping { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineBreakMode = lineBreakMode attr[.paragraphStyle] = paragraphStyle } let rect = (self as NSString).boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attr, context: nil) return rect.size } } // MARK: - Extensions extension String { var isValidEmail: Bool { let emailPattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" let emailPred = NSPredicate(format: "SELF MATCHES %@", emailPattern) return emailPred.evaluate(with: self) } var isValidNickname: Bool{ return self.trimmed.count >= 2 } } extension String { /// Displays a number with B, M, K suffixes based on magnitude static func displayNumber(_ num: NSNumber, scale: Int) -> String { if num.doubleValue == 0 { return "0" } let dm = NSDecimalNumber(decimal: num.decimalValue) let handler = NSDecimalNumberHandler( roundingMode: .down, scale: Int16(scale), raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: true ) let dm_1 = NSDecimalNumber(string: "1.0") let dm_1000 = NSDecimalNumber(string: "1000.0") let dm_1000000 = NSDecimalNumber(string: "1000000.0") let dm_1000000000 = NSDecimalNumber(string: "1000000000.0") let formatter = NumberFormatter() formatter.maximumFractionDigits = scale formatter.numberStyle = .decimal formatter.roundingMode = .down formatter.locale = Locale.current // Assuming LanguagesUtils.locale() returns current locale switch num.doubleValue { case let value where value >= 1000000000: let number = dm.dividing(by: dm_1000000000, withBehavior: handler) guard let res = formatter.string(from: number) else { return "" } return "\(res)B" case let value where value >= 1000000: let number = dm.dividing(by: dm_1000000, withBehavior: handler) guard let res = formatter.string(from: number) else { return "" } return "\(res)M" case let value where value >= 1000: let number = dm.dividing(by: dm_1000, withBehavior: handler) guard let res = formatter.string(from: number) else { return "" } return "\(res)K" default: let number = dm.dividing(by: dm_1, withBehavior: handler) guard let res = formatter.string(from: number) else { return "" } return res } } /// Displays double with K rule, showing one decimal place for large numbers static func displayDouble(_ num: Double) -> String { if num < 1000.0 { return displayNumber(NSNumber(value: num), scale: 0) } return displayNumber(NSNumber(value: num), scale: 1) } /// Displays integer with K rule, defaultscale is 0 static func displayInteger(_ num: Int) -> String { displayNumber(NSNumber(value: num), scale: 0) } /// Displays timer from milliseconds static func displayTimer(_ ms: Int) -> String { let day = ms / (1000 * 60 * 60 * 24) let hour = (ms % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) let minute = (ms % (1000 * 60 * 60)) / (1000 * 60) let second = (ms % (1000 * 60)) / 1000 switch (day, hour, minute) { case let (d, _, _) where d >= 1: return String(format: "%ldd %ldh", day, hour) case let (0, h, m) where h > 0: return String(format: "%ldh %ldm", hour, m) case let (0, 0, m) where m >= 0: return String(format: "%02ld:%02ld", m, second) default: return String(format: "%02ld:%02ld", minute, second) } } /// Formats integer with thousand separator static func thousandString(_ num: Int) -> String { let formatter = NumberFormatter() formatter.maximumFractionDigits = 0 formatter.numberStyle = .decimal formatter.locale = Locale.current return formatter.string(from: NSNumber(value: num)) ?? "" } /// Formats double with thousand separator, two decimal places static func thousandString(float num: Double) -> String { thousandString(float: num, maxDigits: 2) } /// Converts formatted string back to double static func float(fromThousandString string: String) -> Double { let formatter = NumberFormatter() formatter.maximumFractionDigits = 2 formatter.numberStyle = .decimal formatter.locale = Locale.current return formatter.number(from: string)?.doubleValue ?? 0.0 } /// Formats double with thousand separator and specified decimal places static func thousandString(float num: Double, maxDigits: Int) -> String { thousandString(float: num, maxDigits: maxDigits, roundingMode: .down) } /// Formats double with thousand separator, specified decimal places and rounding mode static func thousandString(float num: Double, maxDigits: Int, roundingMode: NumberFormatter.RoundingMode) -> String { thousandString(float: num, maxDigits: maxDigits, locale: Locale.current, roundingMode: roundingMode) } /// Formats double with thousand separator, specified decimal places, locale, and rounding mode static func thousandString(float num: Double, maxDigits: Int, locale: Locale, roundingMode: NumberFormatter.RoundingMode) -> String { let formatter = NumberFormatter() formatter.maximumFractionDigits = maxDigits formatter.numberStyle = .decimal formatter.roundingMode = roundingMode formatter.locale = locale return formatter.string(from: NSNumber(value: num)) ?? "" } /// Formats number with ordinal suffix (st, nd, rd, th) static func numberSequence(_ num: Int) -> String { let subNum = num % 10 if num > 10 && num < 20 { return "\(num)th" } switch subNum { case 1: return "\(num)st" case 2: return "\(num)nd" case 3: return "\(num)rd" default: return "\(num)th" } } /// 将括号以及括号内的文字的range全部输出 static func findBracketRanges(in text: String) -> [NSRange] { // 匹配英文 () 或中文 () let pattern = "(\\([^)]*\\))|(([^)]*))" guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { print("Invalid regex pattern") return [] } let nsString = text as NSString let range = NSRange(location: 0, length: nsString.length) var ranges: [NSRange] = [] regex.enumerateMatches(in: text, options: [], range: range) { (match, _, _) in if let matchRange = match?.range { ranges.append(matchRange) } } return ranges } /// 将()()中的文字删除后,剩余的文字 static func removeBracketContents(from text: String) -> String { let ranges = findBracketRanges(in: text) guard !ranges.isEmpty else { return text } let nsString = text as NSString let mutable = NSMutableString(string: text) // 倒序删除,避免 range 偏移 for range in ranges.reversed() { mutable.deleteCharacters(in: range) } return String(mutable) } }