Visual_Novel_iOS/crush/Crush/Src/Utils/Extensions/StringExt.swift

422 lines
15 KiB
Swift
Raw Normal View History

2025-10-09 10:29:35 +00:00
//
// 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)
}
}