422 lines
15 KiB
Swift
422 lines
15 KiB
Swift
|
|
//
|
|||
|
|
// 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..<length {
|
|||
|
|
let digit = Int.random(in: 0...9)
|
|||
|
|
result.append("\(digit)")
|
|||
|
|
}
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 支持动态键的翻译方法
|
|||
|
|
func localized(_ bundle: Bundle = .main, tableName: String = "Localizable") -> 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<String.Index>
|
|||
|
|
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)
|
|||
|
|
}
|
|||
|
|
}
|