422 lines
15 KiB
Swift
Executable File
422 lines
15 KiB
Swift
Executable File
//
|
||
// 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)
|
||
}
|
||
}
|