NSString+Ex.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. //
  2. // NSString+Ex.swift
  3. // TSLiveWallpaper
  4. //
  5. // Created by 100Years on 2024/12/20.
  6. //
  7. import CommonCrypto
  8. public extension String {
  9. var uiColor: UIColor {
  10. if isEmpty {
  11. return .clear
  12. }
  13. return UIColor.fromHex(self)
  14. }
  15. var localFilePath: String {
  16. var path = self
  17. if path.lowercased().hasPrefix("file://") {
  18. path.removeFirst("file://".count)
  19. }
  20. return path
  21. }
  22. func toCgRect() -> CGRect {
  23. NSCoder.cgRect(for: self)
  24. }
  25. func toCgPoint() -> CGPoint {
  26. NSCoder.cgPoint(for: self)
  27. }
  28. func toRgba() -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat)? {
  29. var r:CGFloat = 0;
  30. var g:CGFloat = 0;
  31. var b:CGFloat = 0;
  32. var a:CGFloat = 0;
  33. var prefix: Int = 0;
  34. let color: String = trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
  35. if (color.hasPrefix("#")) {
  36. prefix = 1
  37. }
  38. else if (color.hasPrefix("0X")) {
  39. prefix = 2
  40. }
  41. let length: Int = color.count - prefix
  42. // RGB RGBA RRGGBB RRGGBBAA
  43. func componentFrom(string: String, start: Int, length: Int) -> CGFloat {
  44. guard start >= 0, start + length <= string.count else {
  45. return 0
  46. }
  47. let begin = string.index(startIndex, offsetBy: start)
  48. let end = string.index(startIndex, offsetBy: start + length)
  49. let sub = string[begin..<end]
  50. var full = String(sub)
  51. if length == 1 {
  52. full += full
  53. }
  54. var hex: UInt64 = 0
  55. Scanner(string: full).scanHexInt64(&hex)
  56. let float = CGFloat(hex) / 255.0
  57. return float
  58. }
  59. switch length {
  60. case 3: // RGB
  61. r = componentFrom(string: color, start: prefix, length: 1)
  62. g = componentFrom(string: color, start: prefix + 1, length: 1)
  63. b = componentFrom(string: color, start: prefix + 2, length: 1)
  64. a = 1
  65. case 4: // RGBA
  66. r = componentFrom(string: color, start: prefix, length: 1)
  67. g = componentFrom(string: color, start: prefix + 1, length: 1)
  68. b = componentFrom(string: color, start: prefix + 2, length: 1)
  69. a = componentFrom(string: color, start: prefix + 3, length: 1)
  70. case 6: // RRGGBB
  71. r = componentFrom(string: color, start: prefix, length: 2)
  72. g = componentFrom(string: color, start: prefix + 2, length: 2)
  73. b = componentFrom(string: color, start: prefix + 4, length: 2)
  74. a = 1
  75. case 8: // RRGGBBAA
  76. r = componentFrom(string: color, start: prefix, length: 2)
  77. g = componentFrom(string: color, start: prefix + 2, length: 2)
  78. b = componentFrom(string: color, start: prefix + 4, length: 2)
  79. a = componentFrom(string: color, start: prefix + 6, length: 2)
  80. default:
  81. return nil
  82. }
  83. return (r, g, b, a)
  84. }
  85. func toColor() -> UIColor? {
  86. if let rgba = toRgba() {
  87. return UIColor(red: rgba.r, green: rgba.g, blue: rgba.b, alpha: rgba.a)
  88. }
  89. return nil
  90. }
  91. }
  92. public extension String {
  93. var localized:String {
  94. return NSLocalizedString(self, comment: self)
  95. // return self
  96. }
  97. }
  98. public extension String {
  99. var doubleValue: Double? {
  100. Double(self)
  101. }
  102. var localNumber: String {
  103. if let value = Int(self), let numberString = value.localNumber {
  104. return numberString
  105. }
  106. return self
  107. }
  108. var md5:String {
  109. let data = Data(self.utf8)
  110. var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
  111. data.withUnsafeBytes {
  112. _ = CC_MD5($0.baseAddress, CC_LONG(data.count), &digest)
  113. }
  114. return digest.map { String(format: "%02x", $0) }.joined()
  115. }
  116. }
  117. public extension String {
  118. // 移除逗号
  119. func deleteDotSymbol() -> String {
  120. // 超过1000的数字,格式化成文本后会带 ','(en)/'٬'(ar)
  121. return replacingOccurrences(of: ",", with: "").replacingOccurrences(of: "٬", with: "")
  122. }
  123. }
  124. public extension String {
  125. /// 根据字符串动态创建类实例
  126. /// - Parameters:
  127. /// - type: 要创建的类类型(泛型)
  128. /// - bundle: 模块的命名空间(默认为 `nil`,即当前模块)
  129. /// - Returns: 创建的实例或 `nil`
  130. func toInstance<T:NSObject>(of type: T.Type, bundle: String? = nil) -> T? {
  131. // 获取完整的类名
  132. let namespace = bundle ?? Bundle.main.infoDictionary?["CFBundleName"] as? String ?? ""
  133. let className = "\(namespace).\(self)"
  134. // 通过类名获取类型
  135. guard let targetClass = NSClassFromString(className) as? T.Type else {
  136. return nil
  137. }
  138. // 创建实例
  139. return targetClass.init()
  140. }
  141. func toClass(bundle: String? = nil) -> AnyClass? {
  142. // 获取完整的类名
  143. let namespace = bundle ?? Bundle.main.infoDictionary?["CFBundleName"] as? String ?? ""
  144. let className = "\(namespace).\(self)"
  145. // 通过类名获取类型
  146. guard let targetClass = NSClassFromString(className) else {
  147. return nil
  148. }
  149. // 创建实例
  150. return targetClass
  151. }
  152. }
  153. public extension String {
  154. func boundingRect(ofAttributes attributes: [NSAttributedString.Key: Any], size: CGSize) -> CGRect {
  155. let boundingBox = boundingRect(
  156. with: size,
  157. options: [.usesLineFragmentOrigin, .usesFontLeading],
  158. attributes: attributes,
  159. context: nil
  160. )
  161. return boundingBox
  162. }
  163. func size(ofAttributes attributes: [NSAttributedString.Key: Any], maxWidth: CGFloat, maxHeight: CGFloat) -> CGSize {
  164. boundingRect(ofAttributes: attributes, size: .init(width: maxWidth, height: maxHeight)).size
  165. }
  166. func size(ofFont font: UIFont, maxWidth: CGFloat, maxHeight: CGFloat) -> CGSize {
  167. let constraintRect = CGSize(width: maxWidth, height: maxHeight)
  168. let boundingBox = boundingRect(
  169. with: constraintRect,
  170. options: [.usesLineFragmentOrigin, .usesFontLeading],
  171. attributes: [.font: font],
  172. context: nil
  173. )
  174. return boundingBox.size
  175. }
  176. func width(ofSize size: CGFloat, maxHeight: CGFloat) -> CGFloat {
  177. width(
  178. ofFont: UIFont.systemFont(ofSize: size),
  179. maxHeight: maxHeight
  180. )
  181. }
  182. func width(ofFont font: UIFont, maxHeight: CGFloat) -> CGFloat {
  183. size(
  184. ofAttributes: [NSAttributedString.Key.font: font],
  185. maxWidth: CGFloat(MAXFLOAT),
  186. maxHeight: maxHeight
  187. ).width
  188. }
  189. func height(ofSize size: CGFloat, maxWidth: CGFloat) -> CGFloat {
  190. height(
  191. ofFont: UIFont.systemFont(ofSize: size),
  192. maxWidth: maxWidth
  193. )
  194. }
  195. func height(ofFont font: UIFont, maxWidth: CGFloat) -> CGFloat {
  196. size(
  197. ofAttributes: [NSAttributedString.Key.font: font],
  198. maxWidth: maxWidth,
  199. maxHeight: CGFloat(MAXFLOAT)
  200. ).height
  201. }
  202. subscript(_ indexs: ClosedRange<Int>) -> String {
  203. let beginIndex = index(startIndex, offsetBy: indexs.lowerBound)
  204. let endIndex = index(startIndex, offsetBy: indexs.upperBound)
  205. return String(self[beginIndex...endIndex])
  206. }
  207. subscript(_ indexs: Range<Int>) -> String {
  208. let beginIndex = index(startIndex, offsetBy: indexs.lowerBound)
  209. let endIndex = index(startIndex, offsetBy: indexs.upperBound)
  210. return String(self[beginIndex..<endIndex])
  211. }
  212. subscript(_ indexs: PartialRangeThrough<Int>) -> String {
  213. let endIndex = index(startIndex, offsetBy: indexs.upperBound)
  214. return String(self[startIndex...endIndex])
  215. }
  216. subscript(_ indexs: PartialRangeFrom<Int>) -> String {
  217. let beginIndex = index(startIndex, offsetBy: indexs.lowerBound)
  218. return String(self[beginIndex..<endIndex])
  219. }
  220. subscript(_ indexs: PartialRangeUpTo<Int>) -> String {
  221. let endIndex = index(startIndex, offsetBy: indexs.upperBound)
  222. return String(self[startIndex..<endIndex])
  223. }
  224. var assetFormat: String? {
  225. let lowercased = lowercased()
  226. if lowercased.hasSuffix("heic") {
  227. return "heic"
  228. }
  229. if lowercased.hasSuffix("jpg") || lowercased.hasSuffix("jpeg") {
  230. return "jpeg"
  231. }
  232. if lowercased.hasSuffix("png") {
  233. return "png"
  234. }
  235. if lowercased.hasSuffix("gif") {
  236. return "gif"
  237. }
  238. return nil
  239. }
  240. }
  241. public extension String {
  242. func colors(separator:String) -> [UIColor] {
  243. let array = self.components(separatedBy: separator)
  244. var colors = [UIColor]()
  245. for string in array {
  246. colors.append(string.uiColor)
  247. }
  248. return colors
  249. }
  250. func cgColors(separator:String) -> [CGColor] {
  251. let array = self.components(separatedBy: separator)
  252. var colors = [CGColor]()
  253. for string in array {
  254. colors.append(string.uiColor.cgColor)
  255. }
  256. return colors
  257. }
  258. }
  259. public extension String {
  260. func jsonDict() -> [String:Any]? {
  261. // 将字符串转换为 Data
  262. if let jsonData = self.data(using: .utf8) {
  263. do {
  264. // 将 Data 解析为 JSON 对象
  265. let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
  266. // 尝试将 JSON 对象转换为字典
  267. if let jsonDict = jsonObject as? [String: Any] {
  268. // dePrint("转换后的字典: \(jsonDict)")
  269. return jsonDict
  270. } else {
  271. // dePrint("无法将 JSON 对象转换为字典。")
  272. }
  273. } catch {
  274. // dePrint("JSON 解析错误: \(error)")
  275. }
  276. } else {
  277. // dePrint("无法将字符串转换为 Data。")
  278. }
  279. return nil
  280. }
  281. }
  282. public extension String {
  283. func removeTimestampFromEnd() -> String {
  284. let string = self
  285. // // 正则表达式:匹配末尾的10位或13位数字
  286. // let pattern = "\\d{10}$|\\d{13}$"
  287. // guard let regex = try? NSRegularExpression(pattern: pattern) else {
  288. // return string
  289. // }
  290. // let range = NSRange(location: 0, length: string.utf16.count)
  291. // return regex.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: "")
  292. // 正则表达式:匹配(可选下划线)后跟10位或13位数字结尾
  293. let pattern = "_?\\d{10}$|_?\\d{13}$"
  294. guard let regex = try? NSRegularExpression(pattern: pattern) else {
  295. return string
  296. }
  297. let range = NSRange(location: 0, length: string.utf16.count)
  298. return regex.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: "")
  299. }
  300. }