Extension.swift 70 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675
  1. //
  2. // StringUtil.swift
  3. // Runner
  4. //
  5. // Created by Yayan Dwi on 20/04/20.
  6. // Copyright © 2020 The Chromium Authors. All rights reserved.
  7. //
  8. import Foundation
  9. import UIKit
  10. import SDWebImage
  11. import ImageIO
  12. import MobileCoreServices
  13. import CommonCrypto
  14. extension Date {
  15. public func currentTimeMillis() -> Int {
  16. return Int(self.timeIntervalSince1970 * 1000)
  17. }
  18. func format(dateFormat: String) -> String {
  19. let formatter = DateFormatter()
  20. formatter.dateFormat = dateFormat
  21. return formatter.string(from: self)
  22. }
  23. var millisecondsSince1970:Int64 {
  24. return Int64((self.timeIntervalSince1970 * 1000.0).rounded())
  25. }
  26. public init(milliseconds:Int64) {
  27. self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
  28. }
  29. }
  30. extension String {
  31. func toNormalString() -> String {
  32. let _source = self.replacingOccurrences(of: "+", with: "%20")
  33. if var result = _source.removingPercentEncoding {
  34. result = result.replacingOccurrences(of: "<NL>", with: "\n")
  35. result = result.replacingOccurrences(of: "<CR>", with: "\r")
  36. return decrypt(source: result)
  37. }
  38. return self
  39. }
  40. func toStupidString() -> String {
  41. if var result = self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
  42. result = result.replacingOccurrences(of: "\n", with: "<NL>")
  43. result = result.replacingOccurrences(of: "\r", with: "<CR>")
  44. result = result.replacingOccurrences(of: "+", with: "%2B")
  45. return result
  46. }
  47. return self
  48. }
  49. private func decrypt(source : String) -> String {
  50. if let result = source.removingPercentEncoding {
  51. return result
  52. }
  53. return source
  54. }
  55. public func matches(_ regex: String) -> Bool {
  56. return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
  57. }
  58. }
  59. extension Int {
  60. func toHex() -> String {
  61. return String(format: "%02X", self)
  62. }
  63. }
  64. extension UIApplication {
  65. public static var appVersion: String? {
  66. return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
  67. }
  68. var rootViewController: UIViewController? {
  69. return UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
  70. }
  71. public var visibleViewController: UIViewController? {
  72. let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
  73. if var topController = keyWindow?.rootViewController {
  74. while let presentedViewController = topController.presentedViewController {
  75. topController = presentedViewController
  76. }
  77. return topController
  78. }
  79. return nil
  80. }
  81. }
  82. extension UIView {
  83. public func anchor(top: NSLayoutYAxisAnchor? = nil,
  84. left: NSLayoutXAxisAnchor? = nil,
  85. bottom: NSLayoutYAxisAnchor? = nil,
  86. right: NSLayoutXAxisAnchor? = nil,
  87. paddingTop: CGFloat = 0,
  88. paddingLeft: CGFloat = 0,
  89. paddingBottom: CGFloat = 0,
  90. paddingRight: CGFloat = 0,
  91. centerX: NSLayoutXAxisAnchor? = nil,
  92. centerY: NSLayoutYAxisAnchor? = nil,
  93. width: CGFloat = 0,
  94. height: CGFloat = 0,
  95. minHeight: CGFloat = 0,
  96. maxHeight: CGFloat = 0,
  97. minWidth: CGFloat = 0,
  98. maxWidth: CGFloat = 0,
  99. dynamicLeft: Bool = false,
  100. dynamicRight: Bool = false) {
  101. translatesAutoresizingMaskIntoConstraints = false
  102. if let top = top {
  103. topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
  104. }
  105. if let left = left {
  106. if dynamicLeft {
  107. leftAnchor.constraint(greaterThanOrEqualTo: left, constant: paddingLeft).isActive = true
  108. } else {
  109. leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
  110. }
  111. }
  112. if let right = right {
  113. if dynamicRight {
  114. leftAnchor.constraint(lessThanOrEqualTo: right, constant: -paddingRight).isActive = true
  115. } else {
  116. rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
  117. }
  118. }
  119. if let bottom = bottom {
  120. bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
  121. }
  122. if let centerX = centerX {
  123. centerXAnchor.constraint(equalTo: centerX).isActive = true
  124. }
  125. if let centerY = centerY {
  126. centerYAnchor.constraint(equalTo: centerY).isActive = true
  127. }
  128. if height != 0 || minHeight != 0 || maxHeight != 0 {
  129. if minHeight != 0 && maxHeight != 0 {
  130. heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight).isActive = true
  131. heightAnchor.constraint(lessThanOrEqualToConstant: maxHeight).isActive = true
  132. } else if minHeight != 0 && maxHeight == 0 {
  133. heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight).isActive = true
  134. } else if minHeight == 0 && maxHeight != 0 {
  135. heightAnchor.constraint(lessThanOrEqualToConstant: maxHeight).isActive = true
  136. } else {
  137. heightAnchor.constraint(equalToConstant: height).isActive = true
  138. }
  139. }
  140. if width != 0 || minWidth != 0 || maxWidth != 0 {
  141. if minWidth != 0 && maxWidth != 0 {
  142. heightAnchor.constraint(greaterThanOrEqualToConstant: minWidth).isActive = true
  143. heightAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true
  144. } else if minWidth != 0 && maxWidth == 0 {
  145. heightAnchor.constraint(greaterThanOrEqualToConstant: minWidth).isActive = true
  146. } else if minWidth == 0 && maxWidth != 0 {
  147. heightAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true
  148. } else {
  149. widthAnchor.constraint(equalToConstant: width).isActive = true
  150. }
  151. }
  152. }
  153. public func addTopBorder(with color: UIColor?, andWidth borderWidth: CGFloat, view: UIView = UIView()) {
  154. let border = view
  155. border.backgroundColor = color
  156. border.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
  157. border.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: borderWidth)
  158. addSubview(border)
  159. }
  160. public func addBottomBorder(with color: UIColor?, andWidth borderWidth: CGFloat, x: CGFloat = 0, view: UIView = UIView()) {
  161. let border = view
  162. border.backgroundColor = color
  163. border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
  164. border.frame = CGRect(x: x, y: frame.size.height - borderWidth, width: frame.size.width, height: borderWidth)
  165. addSubview(border)
  166. }
  167. public func addLeftBorder(with color: UIColor?, andWidth borderWidth: CGFloat) {
  168. let border = UIView()
  169. border.backgroundColor = color
  170. border.frame = CGRect(x: 0, y: 0, width: borderWidth, height: frame.size.height)
  171. border.autoresizingMask = [.flexibleHeight, .flexibleRightMargin]
  172. addSubview(border)
  173. }
  174. public func addRightBorder(with color: UIColor?, andWidth borderWidth: CGFloat) {
  175. let border = UIView()
  176. border.backgroundColor = color
  177. border.autoresizingMask = [.flexibleHeight, .flexibleLeftMargin]
  178. border.frame = CGRect(x: frame.size.width - borderWidth, y: 0, width: borderWidth, height: frame.size.height)
  179. addSubview(border)
  180. }
  181. public func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
  182. UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0);
  183. image.draw(in: CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height))
  184. let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
  185. UIGraphicsEndImageContext()
  186. return newImage
  187. }
  188. }
  189. extension UIViewController {
  190. var previousViewController: UIViewController? {
  191. guard let navigationController = navigationController else { return nil }
  192. let count = navigationController.viewControllers.count
  193. return count < 2 ? nil : navigationController.viewControllers[count - 2]
  194. }
  195. }
  196. extension UIImage {
  197. class func gifImageWithData(_ data: Data) -> UIImage? {
  198. guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
  199. return nil
  200. }
  201. let frameCount = CGImageSourceGetCount(source)
  202. var images: [UIImage] = []
  203. for i in 0..<frameCount {
  204. if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
  205. let image = UIImage(cgImage: cgImage)
  206. images.append(image)
  207. }
  208. }
  209. return UIImage.animatedImage(with: images, duration: 0.0)
  210. }
  211. func gifData() -> Data? {
  212. guard let cgImages = self.images?.compactMap({ $0.cgImage }) else {
  213. return nil
  214. }
  215. let frameProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: 0.1]] // Adjust delay time if necessary
  216. let destinationData = NSMutableData()
  217. guard let destination = CGImageDestinationCreateWithData(destinationData as CFMutableData, kUTTypeGIF, cgImages.count, nil) else {
  218. return nil
  219. }
  220. let framePropertiesPointer = UnsafeMutablePointer<NSDictionary>.allocate(capacity: cgImages.count)
  221. defer {
  222. framePropertiesPointer.deallocate()
  223. }
  224. for i in 0..<cgImages.count {
  225. framePropertiesPointer[i] = frameProperties as NSDictionary
  226. }
  227. CGImageDestinationSetProperties(destination, [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: 0]] as CFDictionary)
  228. for image in cgImages {
  229. CGImageDestinationAddImage(destination, image, framePropertiesPointer.pointee)
  230. }
  231. guard CGImageDestinationFinalize(destination) else {
  232. return nil
  233. }
  234. return destinationData as Data
  235. }
  236. public func resize(target: CGSize) -> UIImage {
  237. // Determine the scale factor that preserves aspect ratio
  238. let widthRatio = target.width / size.width
  239. let heightRatio = target.height / size.height
  240. let scaleFactor = min(widthRatio, heightRatio)
  241. // Compute the new image size that preserves aspect ratio
  242. let scaledImageSize = CGSize(
  243. width: size.width * scaleFactor,
  244. height: size.height * scaleFactor
  245. )
  246. // Draw and return the resized UIImage
  247. let renderer = UIGraphicsImageRenderer(
  248. size: scaledImageSize
  249. )
  250. let scaledImage = renderer.image { _ in
  251. self.draw(in: CGRect(
  252. origin: .zero,
  253. size: scaledImageSize
  254. ))
  255. }
  256. return scaledImage
  257. }
  258. static func imageWithColor(color: UIColor, size: CGSize) -> UIImage? {
  259. let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
  260. UIGraphicsBeginImageContextWithOptions(size, false, 0)
  261. color.setFill()
  262. UIRectFill(rect)
  263. guard let image: UIImage = UIGraphicsGetImageFromCurrentImageContext() else {
  264. return nil
  265. }
  266. UIGraphicsEndImageContext()
  267. return image
  268. }
  269. func mimeType() -> String? {
  270. guard let imageData = self.pngData() else {
  271. return nil
  272. }
  273. let byteArray = [UInt8](imageData)
  274. if byteArray.starts(with: [0xFF, 0xD8, 0xFF]) {
  275. return "image/jpeg"
  276. } else if byteArray.starts(with: [0x89, 0x50, 0x4E, 0x47]) {
  277. return "image/png"
  278. } else if byteArray.starts(with: [0x47, 0x49, 0x46, 0x38]) {
  279. return "image/gif"
  280. } else if byteArray.starts(with: [0x49, 0x49, 0x2A, 0x00]) || byteArray.starts(with: [0x4D, 0x4D, 0x00, 0x2A]) {
  281. return "image/tiff"
  282. }
  283. return nil
  284. }
  285. }
  286. extension Data {
  287. func mimeType() -> String? {
  288. let byteArray = [UInt8](self)
  289. if byteArray.starts(with: [0xFF, 0xD8, 0xFF]) {
  290. return "image/jpeg"
  291. } else if byteArray.starts(with: [0x89, 0x50, 0x4E, 0x47]) {
  292. return "image/png"
  293. } else if byteArray.starts(with: [0x47, 0x49, 0x46, 0x38]) {
  294. return "image/gif"
  295. } else if byteArray.starts(with: [0x49, 0x49, 0x2A, 0x00]) || byteArray.starts(with: [0x4D, 0x4D, 0x00, 0x2A]) {
  296. return "image/tiff"
  297. } else if byteArray.starts(with: [0x25, 0x50, 0x44, 0x46]) {
  298. return "application/pdf"
  299. } else if byteArray.starts(with: [0x50, 0x4B, 0x03, 0x04]) {
  300. return "application/zip"
  301. }
  302. return nil
  303. }
  304. }
  305. extension UIImage {
  306. var isPortrait: Bool { size.height > size.width }
  307. var isLandscape: Bool { size.width > size.height }
  308. var breadth: CGFloat { min(size.width, size.height) }
  309. var breadthSize: CGSize { .init(width: breadth, height: breadth) }
  310. var breadthRect: CGRect { .init(origin: .zero, size: breadthSize) }
  311. public var circleMasked: UIImage? {
  312. guard let cgImage = cgImage?
  313. .cropping(to: .init(origin: .init(x: isLandscape ? ((size.width-size.height)/2).rounded(.down) : 0,
  314. y: isPortrait ? ((size.height-size.width)/2).rounded(.down) : 0),
  315. size: breadthSize)) else { return nil }
  316. let format = imageRendererFormat
  317. format.opaque = false
  318. return UIGraphicsImageRenderer(size: breadthSize, format: format).image { _ in
  319. UIBezierPath(ovalIn: breadthRect).addClip()
  320. UIImage(cgImage: cgImage, scale: format.scale, orientation: imageOrientation)
  321. .draw(in: .init(origin: .zero, size: breadthSize))
  322. }
  323. }
  324. }
  325. extension NSObject {
  326. private static var urlStore = [String:String]()
  327. public func getImage(name url: String, placeholderImage: UIImage? = nil, isCircle: Bool = false, tableView: UITableView? = nil, indexPath: IndexPath? = nil, completion: @escaping (Bool, Bool, UIImage?)->()) {
  328. let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
  329. type(of: self).urlStore[tmpAddress] = url
  330. if url.isEmpty {
  331. completion(false, false, placeholderImage)
  332. return
  333. }
  334. do {
  335. let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
  336. let file = documentDir.appendingPathComponent(url)
  337. if FileManager().fileExists(atPath: file.path) {
  338. let image = UIImage(contentsOfFile: file.path)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  339. completion(true, false, isCircle ? image?.circleMasked : image)
  340. } else if var tempData = try FileEncryption.shared.readSecure(filename: url) {
  341. let image = UIImage(data: tempData)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  342. FileEncryption.shared.wipeData(&tempData)
  343. completion(true, true, isCircle ? image?.circleMasked : image)
  344. } else {
  345. completion(false, false, placeholderImage)
  346. Download().startHTTP(forKey: url) { (name, progress) in
  347. guard progress == 100 else {
  348. return
  349. }
  350. DispatchQueue.main.async {
  351. if tableView != nil {
  352. tableView!.beginUpdates()
  353. tableView!.reloadRows(at: [indexPath!], with: .none)
  354. tableView!.endUpdates()
  355. }
  356. if type(of: self).urlStore[tmpAddress] == name && tableView == nil {
  357. if FileManager().fileExists(atPath: file.path) {
  358. let image = UIImage(contentsOfFile: file.path)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  359. completion(true, true, isCircle ? image?.circleMasked : image)
  360. } else if FileEncryption.shared.isSecureExists(filename: url) {
  361. do {
  362. if let imageData = try FileEncryption.shared.readSecure(filename: url) {
  363. let image = UIImage(data: imageData)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  364. completion(true, true, isCircle ? image?.circleMasked : image)
  365. }
  366. } catch {
  367. }
  368. }
  369. }
  370. }
  371. }
  372. }
  373. } catch {
  374. completion(false, false, placeholderImage)
  375. Download().startHTTP(forKey: url) { (name, progress) in
  376. guard progress == 100 else {
  377. return
  378. }
  379. DispatchQueue.main.async {
  380. if tableView != nil {
  381. tableView!.beginUpdates()
  382. tableView!.reloadRows(at: [indexPath!], with: .none)
  383. tableView!.endUpdates()
  384. }
  385. }
  386. }
  387. }
  388. }
  389. func loadImage(named: String, placeholderImage: UIImage?, completion: @escaping (UIImage?, Bool) -> ()) {
  390. guard !named.isEmpty else {
  391. completion(placeholderImage, true)
  392. return
  393. }
  394. SDWebImageManager.shared.loadImage(with: URL.palioImage(named: named), options: .highPriority, progress: .none) { image, data, error, type, finish, url in
  395. completion(image, finish)
  396. }
  397. }
  398. public func deleteAllRecordDatabase() {
  399. Database.shared.database?.inTransaction({ fmdb, rollback in
  400. do {
  401. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "BUDDY", _where: "")
  402. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ", _where: "")
  403. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "")
  404. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ_MEMBER", _where: "")
  405. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "DISCUSSION_FORUM", _where: "")
  406. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "POST", _where: "")
  407. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_STATUS", _where: "")
  408. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "")
  409. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "OUTGOING", _where: "")
  410. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "FOLLOW", _where: "")
  411. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_FAVORITE", _where: "")
  412. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "LINK_PREVIEW", _where: "")
  413. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "PULL_DB", _where: "")
  414. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "PREFS", _where: "")
  415. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", _where: "")
  416. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "FORM_DATA", _where: "")
  417. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "TASK_PIC", _where: "")
  418. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "TASK_DETAIL", _where: "")
  419. } catch {
  420. rollback.pointee = true
  421. print("Access database error: \(error.localizedDescription)")
  422. }
  423. })
  424. Utils.setFinishInitPrefs(value: false)
  425. }
  426. }
  427. extension URL {
  428. static func palioImage(named: String) -> URL? {
  429. return URL(string: Utils.getURLBase() + "filepalio/image/\(named)")
  430. }
  431. }
  432. extension UIColor {
  433. public static var mainColor: UIColor {
  434. return renderColor(hex: "#046cfc")
  435. }
  436. public static var borderTabColor: UIColor {
  437. return renderColor(hex: "#c4e4f4")
  438. }
  439. public static var nxColor: UIColor {
  440. return renderColor(hex: "#04ecfc")
  441. }
  442. public static var secondaryColor: UIColor {
  443. return renderColor(hex: "#FAFAFF")
  444. }
  445. public static var blackDarkMode: UIColor {
  446. return renderColor(hex: "#262626")
  447. }
  448. public static var orangeColor: UIColor {
  449. return renderColor(hex: "#FFA03E")
  450. }
  451. public static var orangeBNI: UIColor {
  452. return renderColor(hex: "#EE6600")
  453. }
  454. public static var greenColor: UIColor {
  455. return renderColor(hex: "#C7EA46")
  456. }
  457. public static var whiteBubbleColor: UIColor {
  458. if UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark {
  459. return blackDarkMode
  460. }
  461. return renderColor(hex: "#F5F5F5")
  462. }
  463. public static var docColor: UIColor {
  464. return renderColor(hex: "#798F9A")
  465. }
  466. public static var mentionColor: UIColor {
  467. return UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? renderColor(hex: "#f6fcae") : renderColor(hex: "#53bdea")
  468. }
  469. public static var blueBubbleColor: UIColor {
  470. if UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark {
  471. return renderColor(hex: "#367dd9")
  472. }
  473. return renderColor(hex: "#C5D1E1")
  474. }
  475. public static var officialColor: UIColor {
  476. return renderColor(hex: "#4c87ef")
  477. }
  478. public static var verifiedColor: UIColor {
  479. return renderColor(hex: "#00b333")
  480. }
  481. public static var ccColor: UIColor {
  482. return renderColor(hex: "#FFF1A353")
  483. }
  484. public static var internalColor: UIColor {
  485. return renderColor(hex: "#ff0000")
  486. }
  487. public static var blueTextField: UIColor {
  488. return renderColor(hex: "#4c92d2")
  489. }
  490. public class func renderColor(hex: String) -> UIColor {
  491. var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
  492. if (cString.hasPrefix("#")) {
  493. cString.remove(at: cString.startIndex)
  494. }
  495. if ((cString.count) != 6) {
  496. return UIColor.gray
  497. }
  498. var rgbValue:UInt64 = 0
  499. Scanner(string: cString).scanHexInt64(&rgbValue)
  500. return UIColor(
  501. red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
  502. green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
  503. blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
  504. alpha: CGFloat(1.0)
  505. )
  506. }
  507. }
  508. extension UIView {
  509. public func circle() {
  510. layer.cornerRadius = 0.5 * bounds.size.width
  511. clipsToBounds = true
  512. }
  513. public func maxCornerRadius() -> CGFloat {
  514. return (self.frame.width > self.frame.height) ? self.frame.height / 2 : self.frame.width / 2
  515. }
  516. }
  517. extension String {
  518. public func localized(uppercased: Bool = false) -> String {
  519. if let _ : String = SecureUserDefaults.shared.value(forKey: "i18n_language") {} else {
  520. // we set a default, just in case
  521. let langDefault = UserDefaults.standard.stringArray(forKey: "AppleLanguages")
  522. if langDefault![0].contains("id") {
  523. SecureUserDefaults.shared.set("id", forKey: "i18n_language")
  524. } else {
  525. SecureUserDefaults.shared.set("en", forKey: "i18n_language")
  526. }
  527. }
  528. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? ""
  529. let bundle = Bundle.resourceBundle(for: Nexilis.self).path(forResource: lang, ofType: "lproj")
  530. let bundlePath = Bundle(path: bundle!)
  531. let result = NSLocalizedString(
  532. self,
  533. tableName: "Localizable",
  534. bundle: bundlePath!,
  535. value: self,
  536. comment: self)
  537. if uppercased {
  538. return result.uppercased()
  539. }
  540. return result
  541. }
  542. }
  543. extension UIViewController {
  544. public func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
  545. UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0);
  546. image.draw(in: CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height))
  547. let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
  548. UIGraphicsEndImageContext()
  549. return newImage
  550. }
  551. }
  552. extension UITextView {
  553. enum ShouldChangeCursor {
  554. case incrementCursor
  555. case preserveCursor
  556. }
  557. func preserveCursorPosition(withChanges mutatingFunction: (UITextPosition?) -> (ShouldChangeCursor)) {
  558. //save the cursor positon
  559. var cursorPosition: UITextPosition? = nil
  560. if let selectedRange = self.selectedTextRange {
  561. let offset = self.offset(from: self.beginningOfDocument, to: selectedRange.start)
  562. cursorPosition = self.position(from: self.beginningOfDocument, offset: offset)
  563. }
  564. //make mutaing changes that may reset the cursor position
  565. let shouldChangeCursor = mutatingFunction(cursorPosition)
  566. //restore the cursor
  567. if var cursorPosition = cursorPosition {
  568. if shouldChangeCursor == .incrementCursor {
  569. cursorPosition = self.position(from: cursorPosition, offset: 1) ?? cursorPosition
  570. }
  571. if let range = self.textRange(from: cursorPosition, to: cursorPosition) {
  572. self.selectedTextRange = range
  573. }
  574. }
  575. }
  576. }
  577. extension String {
  578. public func substring(from: Int?, to: Int?) -> String {
  579. if let start = from {
  580. guard start < self.count else {
  581. return ""
  582. }
  583. }
  584. if let end = to {
  585. guard end >= 0 else {
  586. return ""
  587. }
  588. }
  589. if let start = from, let end = to {
  590. guard end - start >= 0 else {
  591. return ""
  592. }
  593. }
  594. let startIndex: String.Index
  595. if let start = from, start >= 0 {
  596. startIndex = self.index(self.startIndex, offsetBy: start)
  597. } else {
  598. startIndex = self.startIndex
  599. }
  600. let endIndex: String.Index
  601. if let end = to, end >= 0, end < self.count {
  602. endIndex = self.index(self.startIndex, offsetBy: end + 1)
  603. } else {
  604. endIndex = self.endIndex
  605. }
  606. return String(self[startIndex ..< endIndex])
  607. }
  608. func countEmojiCharacter() -> Int {
  609. func isEmoji(s:NSString) -> Bool {
  610. let high:Int = Int(s.character(at: 0))
  611. if 0xD800 <= high && high <= 0xDBFF {
  612. let low:Int = Int(s.character(at: 1))
  613. let codepoint: Int = ((high - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000
  614. return (0x1D000 <= codepoint && codepoint <= 0x1F9FF)
  615. }
  616. else {
  617. return (0x2100 <= high && high <= 0x27BF)
  618. }
  619. }
  620. let nsString = self as NSString
  621. var length = 0
  622. nsString.enumerateSubstrings(in: NSMakeRange(0, nsString.length), options: NSString.EnumerationOptions.byComposedCharacterSequences) { (subString, substringRange, enclosingRange, stop) -> Void in
  623. if isEmoji(s: subString! as NSString) {
  624. length+=1
  625. }
  626. }
  627. return length
  628. }
  629. public func richText(isEditing: Bool = false, isSearching: Bool = false, textSearch: String = "", group_id: String = "", listMentionInTextField: [User] = []) -> NSMutableAttributedString {
  630. let font = UIFont.systemFont(ofSize: 12)
  631. let boldFont = UIFont.boldSystemFont(ofSize: 12)
  632. let italicFont = UIFont.italicSystemFont(ofSize: 12)
  633. let boldItalicFont = UIFont.systemFont(ofSize: 12, weight: .semibold)
  634. let textUTF8 = String(self.utf8)
  635. let finalText = NSMutableAttributedString(string: textUTF8, attributes: [NSAttributedString.Key.font: font])
  636. // finalText.addAttribute(.foregroundColor, value: UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? UIColor.white : UIColor.black, range: NSRange(location: 0, length: finalText.length + textUTF8.countEmojiCharacter()))
  637. let boldSign: Character = "*"
  638. let italicSign: Character = "_"
  639. let underlineSign: Character = "^"
  640. let strikethroughSign: Character = "~"
  641. let italicGreySign: Character = "%"
  642. var locationBold: [NSRange] = []
  643. //Bold
  644. let rangeBold = getRangeOfWordWithSign(sentence: textUTF8, sign: boldSign)
  645. if rangeBold.count > 0 {
  646. var lastFirstRange = -1
  647. var countRemoveBoldSign = 0
  648. var continueCheckingBold = false
  649. var totalEmoji = 0
  650. for i in 0..<rangeBold.count {
  651. if rangeBold[i].startIndex > lastFirstRange {
  652. let charStart: Character = rangeBold[i].startIndex != 0 ? Array(textUTF8.substring(from: rangeBold[i].startIndex - 1, to: rangeBold[i].startIndex - 1))[0] : Array("0")[0]
  653. if (rangeBold[i].startIndex == 0 || (!charStart.isLetter && !charStart.isNumber)) {
  654. lastFirstRange = rangeBold[i].startIndex
  655. continueCheckingBold = true
  656. } else {
  657. continueCheckingBold = false
  658. }
  659. }
  660. if !continueCheckingBold {
  661. continue
  662. }
  663. if rangeBold[i].endIndex != (textUTF8.count-1) {
  664. let char: Character = Array(textUTF8.substring(from: rangeBold[i].endIndex + 1, to: rangeBold[i].endIndex + 1))[0]
  665. if char.isLetter || char.isNumber {
  666. continue
  667. }
  668. }
  669. let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveBoldSign)).countEmojiCharacter()
  670. let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveBoldSign), to: rangeBold[i].endIndex - (2*countRemoveBoldSign)).countEmojiCharacter()
  671. totalEmoji = countEmoji + countEmojiBefore
  672. locationBold.append(NSRange(location: lastFirstRange - (2*countRemoveBoldSign) + countEmojiBefore, length: (rangeBold[i].endIndex + countEmoji + 1) - lastFirstRange))
  673. finalText.addAttribute(.font, value: boldFont, range: NSRange(location: lastFirstRange - (2*countRemoveBoldSign) + countEmojiBefore, length: (rangeBold[i].endIndex + countEmoji + 1) - lastFirstRange))
  674. if !isEditing{
  675. finalText.mutableString.replaceOccurrences(of: "\(boldSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveBoldSign), length: 1))
  676. finalText.mutableString.replaceOccurrences(of: "\(boldSign)", with: "", options: .literal, range: NSRange(location: rangeBold[i].endIndex + totalEmoji - (2*countRemoveBoldSign) - 1, length: 1))
  677. countRemoveBoldSign += 1
  678. } else {
  679. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
  680. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeBold[i].endIndex + totalEmoji, length: 1))
  681. }
  682. lastFirstRange = rangeBold[i].endIndex
  683. continueCheckingBold = false
  684. }
  685. }
  686. //Italic
  687. let textAfterbold = finalText.string
  688. let rangeItalic = getRangeOfWordWithSign(sentence: textAfterbold, sign: italicSign)
  689. if rangeItalic.count > 0 {
  690. var lastFirstRange = -1
  691. var countRemoveItalicSign = 0
  692. var continueCheckingItalic = false
  693. var totalEmoji = 0
  694. for i in 0..<rangeItalic.count {
  695. if rangeItalic[i].startIndex > lastFirstRange {
  696. let charStart: Character = rangeItalic[i].startIndex != 0 ? Array(textAfterbold.substring(from: rangeItalic[i].startIndex - 1, to: rangeItalic[i].startIndex - 1))[0] : Array("0")[0]
  697. if (rangeItalic[i].startIndex == 0 || (!charStart.isLetter && !charStart.isNumber)) {
  698. lastFirstRange = rangeItalic[i].startIndex
  699. continueCheckingItalic = true
  700. } else {
  701. continueCheckingItalic = false
  702. }
  703. }
  704. if !continueCheckingItalic {
  705. continue
  706. }
  707. if rangeItalic[i].endIndex != (textAfterbold.count-1) {
  708. let char: Character = Array(textAfterbold.substring(from: rangeItalic[i].endIndex + 1, to: rangeItalic[i].endIndex + 1))[0]
  709. if char.isLetter || char.isNumber {
  710. continue
  711. }
  712. }
  713. let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveItalicSign)).countEmojiCharacter()
  714. let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveItalicSign), to: rangeItalic[i].endIndex - (2*countRemoveItalicSign)).countEmojiCharacter()
  715. totalEmoji = countEmoji + countEmojiBefore
  716. if isIntInRangeList((rangeItalic[i].endIndex + countEmoji + 1) - lastFirstRange - lastFirstRange - (2*countRemoveItalicSign) + countEmojiBefore, rangeList: locationBold) {
  717. finalText.addAttribute(.font, value: boldItalicFont, range: NSRange(location: lastFirstRange - (2*countRemoveItalicSign) + countEmojiBefore, length: (rangeItalic[i].endIndex + countEmoji + 1) - lastFirstRange))
  718. } else {
  719. finalText.addAttribute(.font, value: italicFont, range: NSRange(location: lastFirstRange - (2*countRemoveItalicSign) + countEmojiBefore, length: (rangeItalic[i].endIndex + countEmoji + 1) - lastFirstRange))
  720. }
  721. if !isEditing{
  722. finalText.mutableString.replaceOccurrences(of: "\(italicSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveItalicSign), length: 1))
  723. finalText.mutableString.replaceOccurrences(of: "\(italicSign)", with: "", options: .literal, range: NSRange(location: rangeItalic[i].endIndex + totalEmoji - (2*countRemoveItalicSign) - 1, length: 1))
  724. countRemoveItalicSign += 1
  725. } else {
  726. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
  727. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeItalic[i].endIndex + totalEmoji, length: 1))
  728. }
  729. lastFirstRange = rangeItalic[i].endIndex
  730. continueCheckingItalic = false
  731. }
  732. }
  733. //Underline
  734. let textAfterItalic = finalText.string
  735. let rangeUnderline = getRangeOfWordWithSign(sentence: textAfterItalic, sign: underlineSign)
  736. if rangeUnderline.count > 0 {
  737. var lastFirstRange = -1
  738. var countRemoveUnderlineSign = 0
  739. var continueCheckingUnderline = false
  740. var totalEmoji = 0
  741. for i in 0..<rangeUnderline.count {
  742. if rangeUnderline[i].startIndex > lastFirstRange {
  743. let charStart: Character = rangeUnderline[i].startIndex != 0 ? Array(textAfterItalic.substring(from: rangeUnderline[i].startIndex - 1, to: rangeUnderline[i].startIndex - 1))[0] : Array("0")[0]
  744. if (rangeUnderline[i].startIndex == 0 || (!charStart.isLetter && !charStart.isNumber)) {
  745. lastFirstRange = rangeUnderline[i].startIndex
  746. continueCheckingUnderline = true
  747. } else {
  748. continueCheckingUnderline = false
  749. }
  750. }
  751. if !continueCheckingUnderline {
  752. continue
  753. }
  754. if rangeUnderline[i].endIndex != (textAfterItalic.count-1) {
  755. let char: Character = Array(textAfterItalic.substring(from: rangeUnderline[i].endIndex + 1, to: rangeUnderline[i].endIndex + 1))[0]
  756. if char.isLetter || char.isNumber {
  757. continue
  758. }
  759. }
  760. let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveUnderlineSign)).countEmojiCharacter()
  761. let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveUnderlineSign), to: rangeUnderline[i].endIndex - (2*countRemoveUnderlineSign)).countEmojiCharacter()
  762. totalEmoji = countEmoji + countEmojiBefore
  763. finalText.addAttribute(.underlineStyle, value: NSUnderlineStyle.thick.rawValue, range: NSRange(location: lastFirstRange - (2*countRemoveUnderlineSign) + countEmojiBefore + 1, length: (rangeUnderline[i].endIndex + countEmoji - 1) - lastFirstRange))
  764. if !isEditing{
  765. finalText.mutableString.replaceOccurrences(of: "\(underlineSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveUnderlineSign), length: 1))
  766. finalText.mutableString.replaceOccurrences(of: "\(underlineSign)", with: "", options: .literal, range: NSRange(location: rangeUnderline[i].endIndex + totalEmoji - (2*countRemoveUnderlineSign) - 1, length: 1))
  767. countRemoveUnderlineSign += 1
  768. } else {
  769. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
  770. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeUnderline[i].endIndex + totalEmoji, length: 1))
  771. }
  772. lastFirstRange = rangeUnderline[i].endIndex
  773. continueCheckingUnderline = false
  774. }
  775. }
  776. //Strikethrough
  777. let textAfterUnderline = finalText.string
  778. let rangeStrikethrough = getRangeOfWordWithSign(sentence: textAfterUnderline, sign: strikethroughSign)
  779. if rangeStrikethrough.count > 0 {
  780. var lastFirstRange = -1
  781. var countRemoveStrikethroughSign = 0
  782. var continueCheckingStrikethrough = false
  783. var totalEmoji = 0
  784. for i in 0..<rangeStrikethrough.count {
  785. if rangeStrikethrough[i].startIndex > lastFirstRange {
  786. let charStart: Character = rangeStrikethrough[i].startIndex != 0 ? Array(textAfterUnderline.substring(from: rangeStrikethrough[i].startIndex - 1, to: rangeStrikethrough[i].startIndex - 1))[0] : Array("0")[0]
  787. if (rangeStrikethrough[i].startIndex == 0 || (!charStart.isLetter && !charStart.isNumber)) {
  788. lastFirstRange = rangeStrikethrough[i].startIndex
  789. continueCheckingStrikethrough = true
  790. } else {
  791. continueCheckingStrikethrough = false
  792. }
  793. }
  794. if !continueCheckingStrikethrough {
  795. continue
  796. }
  797. if rangeStrikethrough[i].endIndex != (textAfterUnderline.count-1) {
  798. let char: Character = Array(textAfterUnderline.substring(from: rangeStrikethrough[i].endIndex + 1, to: rangeStrikethrough[i].endIndex + 1))[0]
  799. if char.isLetter || char.isNumber {
  800. continue
  801. }
  802. }
  803. let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveStrikethroughSign)).countEmojiCharacter()
  804. let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveStrikethroughSign), to: rangeStrikethrough[i].endIndex - (2*countRemoveStrikethroughSign)).countEmojiCharacter()
  805. totalEmoji = countEmoji + countEmojiBefore
  806. finalText.addAttribute(.strikethroughStyle, value: 2, range: NSRange(location: lastFirstRange - (2*countRemoveStrikethroughSign) + countEmojiBefore + 1, length: (rangeStrikethrough[i].endIndex + countEmoji - 1) - lastFirstRange))
  807. if !isEditing{
  808. finalText.mutableString.replaceOccurrences(of: "\(strikethroughSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveStrikethroughSign), length: 1))
  809. finalText.mutableString.replaceOccurrences(of: "\(strikethroughSign)", with: "", options: .literal, range: NSRange(location: rangeStrikethrough[i].endIndex + totalEmoji - (2*countRemoveStrikethroughSign) - 1, length: 1))
  810. countRemoveStrikethroughSign += 1
  811. } else {
  812. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
  813. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeStrikethrough[i].endIndex + totalEmoji, length: 1))
  814. }
  815. lastFirstRange = rangeStrikethrough[i].endIndex
  816. continueCheckingStrikethrough = false
  817. }
  818. }
  819. //ItalicGrey
  820. let textAfterStrikeThrough = finalText.string
  821. let rangeItalicGrey = getRangeOfWordWithSign(sentence: textAfterStrikeThrough, sign: italicGreySign)
  822. if rangeItalicGrey.count > 0 {
  823. var lastFirstRange = -1
  824. var countRemoveItalicGreySign = 0
  825. var continueCheckingItalicGrey = false
  826. var totalEmoji = 0
  827. for i in 0..<rangeItalicGrey.count {
  828. if rangeItalicGrey[i].startIndex > lastFirstRange {
  829. let charStart: Character = rangeItalicGrey[i].startIndex != 0 ? Array(textAfterStrikeThrough.substring(from: rangeItalicGrey[i].startIndex - 1, to: rangeItalicGrey[i].startIndex - 1))[0] : Array("0")[0]
  830. if (rangeItalicGrey[i].startIndex == 0 || (!charStart.isLetter && !charStart.isNumber)) {
  831. lastFirstRange = rangeItalicGrey[i].startIndex
  832. continueCheckingItalicGrey = true
  833. } else {
  834. continueCheckingItalicGrey = false
  835. }
  836. }
  837. if !continueCheckingItalicGrey {
  838. continue
  839. }
  840. if rangeItalicGrey[i].endIndex != (textAfterStrikeThrough.count-1) {
  841. let char: Character = Array(textAfterStrikeThrough.substring(from: rangeItalicGrey[i].endIndex + 1, to: rangeItalicGrey[i].endIndex + 1))[0]
  842. if char.isLetter || char.isNumber {
  843. continue
  844. }
  845. }
  846. let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveItalicGreySign)).countEmojiCharacter()
  847. let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveItalicGreySign), to: rangeItalicGrey[i].endIndex - (2*countRemoveItalicGreySign)).countEmojiCharacter()
  848. totalEmoji = countEmoji + countEmojiBefore
  849. finalText.addAttribute(.font, value: italicFont, range: NSRange(location: lastFirstRange - (2*countRemoveItalicGreySign) + countEmojiBefore, length: (rangeItalicGrey[i].endIndex + countEmoji + 1) - lastFirstRange))
  850. finalText.addAttribute(.foregroundColor, value: UIColor.darkGray, range: NSRange(location: lastFirstRange - (2*countRemoveItalicGreySign) + countEmojiBefore, length: (rangeItalicGrey[i].endIndex + countEmoji + 1) - lastFirstRange))
  851. if !isEditing{
  852. finalText.mutableString.replaceOccurrences(of: "\(italicGreySign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveItalicGreySign), length: 1))
  853. finalText.mutableString.replaceOccurrences(of: "\(italicGreySign)", with: "", options: .literal, range: NSRange(location: rangeItalicGrey[i].endIndex + totalEmoji - (2*countRemoveItalicGreySign) - 1, length: 1))
  854. countRemoveItalicGreySign += 1
  855. } else {
  856. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
  857. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeItalicGrey[i].endIndex + totalEmoji, length: 1))
  858. }
  859. lastFirstRange = rangeItalicGrey[i].endIndex
  860. continueCheckingItalicGrey = false
  861. }
  862. }
  863. //Check Mention
  864. let finalTextAfterRichText = finalText.string
  865. if finalTextAfterRichText.contains("@") {
  866. let listTextEnter = finalText.string.split(separator: "\n")
  867. for j in 0...listTextEnter.count - 1 {
  868. let listText = listTextEnter[j].split(separator: " ")
  869. let listMention = listText.filter({ $0.starts(with: "@")})
  870. if listMention.count > 0 {
  871. for i in 0..<listMention.count {
  872. let f_pin = (String(listMention[i])).substring(from: 1, to: listMention[i].count)
  873. let member = Member.getMember(f_pin: f_pin)
  874. if member != nil {
  875. let name = (member!.firstName + " " + member!.lastName).trimmingCharacters(in: .whitespaces)
  876. finalText.mutableString.replaceOccurrences(of: f_pin, with: name, options: .literal, range: NSString(string: finalText.string).range(of: f_pin))
  877. if !group_id.isEmpty && Member.getMemberInGroup(f_pin: f_pin, group_id: group_id) != nil {
  878. finalText.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSString(string: finalText.string).range(of: "@\(name)"))
  879. }
  880. }
  881. }
  882. }
  883. }
  884. }
  885. if isSearching {
  886. let range = NSString(string: finalText.string).range(of: textSearch, options: .caseInsensitive) // 2
  887. let highlightColor = UIColor.systemYellow // 3
  888. let highlightedAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.backgroundColor: highlightColor] // 4
  889. finalText.addAttributes(highlightedAttributes, range: range) // 5
  890. }
  891. return finalText
  892. }
  893. func isIntInRangeList(_ intValue: Int, rangeList: [NSRange]) -> Bool {
  894. for range in rangeList {
  895. if intValue >= range.location && intValue < range.location + range.length {
  896. return true
  897. }
  898. }
  899. return false
  900. }
  901. func checkCharBefore(char: String) -> Bool {
  902. return char == " " || char == "\n"
  903. }
  904. func checkCharRich(char: String) -> Bool {
  905. return char == "*" || char == "_" || char == "^" || char == "~"
  906. }
  907. func checkStartWithLink() -> Bool {
  908. return self.starts(with: "https://") || self.starts(with: "http://") || self.starts(with: "www.")
  909. //|| self.starts(with: "*https://") || self.starts(with: "*http://") || self.starts(with: "*www.") || self.starts(with: "_https://") || self.starts(with: "_http://") || self.starts(with: "_www.") || self.starts(with: "^https://") || self.starts(with: "^http://") || self.starts(with: "^www.") || self.starts(with: "~https://") || self.starts(with: "~http://") || self.starts(with: "~www.")
  910. }
  911. func getRangeOfWordWithSign(sentence: String, sign: Character) -> [Range<Int>] {
  912. let asterisk: Character = sign
  913. var ranges = [Range<Int>]()
  914. var startIndex = sentence.startIndex
  915. while let startRange = sentence[startIndex...].firstIndex(of: asterisk),
  916. let endRange = sentence[startRange...].dropFirst().firstIndex(of: asterisk) {
  917. let range = startRange..<endRange
  918. let word = sentence[range].replacingOccurrences(of: "\(sign)", with: "")
  919. if !word.isEmpty {
  920. let lower = sentence.distance(from: sentence.startIndex, to: startRange)
  921. let upper = sentence.distance(from: sentence.startIndex, to: endRange)
  922. ranges.append(Range(uncheckedBounds: (lower: lower, upper: upper)))
  923. }
  924. startIndex = endRange
  925. }
  926. return ranges
  927. }
  928. }
  929. extension UIFont {
  930. var bold: UIFont {
  931. return with(traits: .traitBold)
  932. } // bold
  933. var italic: UIFont {
  934. return with(traits: .traitItalic)
  935. } // italic
  936. func with(traits: UIFontDescriptor.SymbolicTraits) -> UIFont {
  937. guard let descriptor = self.fontDescriptor.withSymbolicTraits(traits) else {
  938. return self
  939. } // guard
  940. return UIFont(descriptor: descriptor, size: 0)
  941. } // with(traits:)
  942. }
  943. extension UILabel {
  944. public func set(image: UIImage, with text: String, size: CGFloat, y: CGFloat) {
  945. let attachment = NSTextAttachment()
  946. attachment.image = image
  947. attachment.bounds = CGRect(x: 0, y: y, width: size, height: size)
  948. let attachmentStr = NSAttributedString(attachment: attachment)
  949. let mutableAttributedString = NSMutableAttributedString()
  950. mutableAttributedString.append(attachmentStr)
  951. let textString = NSAttributedString(string: text, attributes: [.font: self.font!])
  952. mutableAttributedString.append(textString)
  953. self.attributedText = mutableAttributedString
  954. }
  955. public func setAttributeText(image: UIImage, with textMutable: NSAttributedString, size: CGFloat, y: CGFloat) {
  956. let attachment = NSTextAttachment()
  957. attachment.image = image
  958. attachment.bounds = CGRect(x: 0, y: y, width: size, height: size)
  959. let attachmentStr = NSAttributedString(attachment: attachment)
  960. let mutableAttributedString = NSMutableAttributedString()
  961. mutableAttributedString.append(attachmentStr)
  962. mutableAttributedString.append(textMutable)
  963. self.attributedText = mutableAttributedString
  964. }
  965. }
  966. extension Bundle {
  967. public static func resourceBundle(for frameworkClass: AnyClass) -> Bundle {
  968. let frameworkBundle = Bundle(for: frameworkClass)
  969. guard let resourceBundleURL = frameworkBundle.url(forResource: "NexilisLite", withExtension: "bundle"),
  970. let resourceBundle = Bundle(url: resourceBundleURL) else {
  971. return frameworkBundle
  972. }
  973. return resourceBundle
  974. }
  975. public static func resourcesMediaBundle(for frameworkClass: AnyClass) -> Bundle {
  976. let frameworkBundle = Bundle(for: frameworkClass)
  977. guard let resourceBundleURL = frameworkBundle.url(forResource: "NexilisLiteResources", withExtension: "bundle"),
  978. let resourceBundle = Bundle(url: resourceBundleURL) else {
  979. return frameworkBundle
  980. }
  981. return resourceBundle
  982. }
  983. }
  984. //extension UIFont {
  985. //
  986. // static func register(from url: URL) throws {
  987. // guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
  988. // throw fatalError("Could not create font data provider for \(url).")
  989. // }
  990. // let font = CGFont(fontDataProvider)
  991. // var error: Unmanaged<CFError>?
  992. // guard CTFontManagerRegisterGraphicsFont(font!, &error) else {
  993. // throw error!.takeUnretainedValue()
  994. // }
  995. // }
  996. //
  997. //}
  998. extension UIButton {
  999. private func actionHandleBlock(action:(() -> Void)? = nil) {
  1000. struct __ {
  1001. static var action :(() -> Void)?
  1002. }
  1003. if action != nil {
  1004. __.action = action
  1005. } else {
  1006. __.action?()
  1007. }
  1008. }
  1009. @objc private func triggerActionHandleBlock() {
  1010. self.actionHandleBlock()
  1011. }
  1012. public func actionHandle(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
  1013. self.actionHandleBlock(action: action)
  1014. self.addTarget(self, action: #selector(self.triggerActionHandleBlock), for: control)
  1015. }
  1016. public func setImageRightOfText(image: UIImage?, for state: UIControl.State) {
  1017. self.setImage(image, for: state)
  1018. self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -((image?.size.width ?? 0) + 5), bottom: 0, right: (image?.size.width ?? 0))
  1019. self.imageEdgeInsets = UIEdgeInsets(top: 10, left: (self.titleLabel?.frame.size.width ?? 0) + 90, bottom: 10, right: -((self.titleLabel?.frame.size.width ?? 0) + 5))
  1020. }
  1021. }
  1022. extension UINavigationController {
  1023. func replaceAllViewController(with viewController: UIViewController, animated: Bool) {
  1024. pushViewController(viewController, animated: animated)
  1025. viewControllers.removeSubrange(1...viewControllers.count - 2)
  1026. }
  1027. var rootViewController : UIViewController? {
  1028. return viewControllers.first
  1029. }
  1030. func popViewController(animated: Bool, completion: @escaping () -> Void) {
  1031. popViewController(animated: animated)
  1032. if animated, let coordinator = transitionCoordinator {
  1033. coordinator.animate(alongsideTransition: nil) { _ in
  1034. completion()
  1035. }
  1036. } else {
  1037. completion()
  1038. }
  1039. }
  1040. }
  1041. extension UIImageView {
  1042. private static var taskKey = 0
  1043. private static var urlKey = 0
  1044. private var currentTask: URLSessionTask? {
  1045. get { return objc_getAssociatedObject(self, &UIImageView.taskKey) as? URLSessionTask }
  1046. set { objc_setAssociatedObject(self, &UIImageView.taskKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  1047. }
  1048. private var currentURL: URL? {
  1049. get { return objc_getAssociatedObject(self, &UIImageView.urlKey) as? URL }
  1050. set { objc_setAssociatedObject(self, &UIImageView.urlKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  1051. }
  1052. public func loadImageAsync(with urlString: String?, isGif: Bool = false) {
  1053. // cancel prior task, if any
  1054. weak var oldTask = currentTask
  1055. currentTask = nil
  1056. oldTask?.cancel()
  1057. // reset imageview's image
  1058. self.image = nil
  1059. // allow supplying of `nil` to remove old image and then return immediately
  1060. guard let urlString = urlString else { return }
  1061. // check cache
  1062. if isGif, let cachedImageGif = ImageCache.shared.imageGif(forKey: urlString) {
  1063. guard let gifImage = UIImage.gifImageWithData(Data(referencing: cachedImageGif)) else {
  1064. print("Failed to create the GIF image.")
  1065. return
  1066. }
  1067. self.image = gifImage
  1068. return
  1069. }
  1070. if let cachedImage = ImageCache.shared.image(forKey: urlString) {
  1071. self.image = cachedImage
  1072. return
  1073. }
  1074. // download
  1075. let url = URL(string: urlString)!
  1076. currentURL = url
  1077. let urlConfig = URLSessionConfiguration.default
  1078. let sessionDelegate = SelfSignedURLSessionDelegate()
  1079. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  1080. let task = session.dataTask(with: url) { [weak self] data, response, error in
  1081. self?.currentTask = nil
  1082. //error handling
  1083. if let error = error {
  1084. // don't bother reporting cancelation errors
  1085. if (error as NSError).domain == NSURLErrorDomain && (error as NSError).code == NSURLErrorCancelled {
  1086. return
  1087. }
  1088. //print(error)
  1089. return
  1090. }
  1091. guard let data = data else {
  1092. //print("unable to extract image")
  1093. return
  1094. }
  1095. let downloadedImage = UIImage(data: data)
  1096. if isGif {
  1097. ImageCache.shared.saveGif(data: NSData(data: data), forKey: urlString)
  1098. } else {
  1099. if downloadedImage != nil {
  1100. ImageCache.shared.save(image: downloadedImage!, forKey: urlString)
  1101. }
  1102. }
  1103. if url == self?.currentURL {
  1104. DispatchQueue.main.async {
  1105. if isGif {
  1106. guard let gifImage = UIImage.gifImageWithData(data) else {
  1107. print("Failed to create the GIF image.")
  1108. return
  1109. }
  1110. self?.image = gifImage
  1111. } else {
  1112. self?.image = downloadedImage
  1113. }
  1114. }
  1115. }
  1116. }
  1117. // save and start new task
  1118. currentTask = task
  1119. task.resume()
  1120. }
  1121. private func actionHandleBlock(action:(() -> Void)? = nil) {
  1122. struct __ {
  1123. static var action :(() -> Void)?
  1124. }
  1125. if action != nil {
  1126. __.action = action
  1127. } else {
  1128. __.action?()
  1129. }
  1130. }
  1131. @objc private func triggerActionHandleBlock() {
  1132. self.actionHandleBlock()
  1133. }
  1134. func actionHandle(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
  1135. self.actionHandleBlock(action: action)
  1136. self.isUserInteractionEnabled = true
  1137. self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.triggerActionHandleBlock)))
  1138. }
  1139. }
  1140. extension UITextField {
  1141. public enum PaddingSide {
  1142. case left(CGFloat)
  1143. case right(CGFloat)
  1144. case both(CGFloat)
  1145. }
  1146. public func addPadding(_ padding: PaddingSide) {
  1147. self.leftViewMode = .always
  1148. self.layer.masksToBounds = true
  1149. switch padding {
  1150. case .left(let spacing):
  1151. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  1152. self.leftView = paddingView
  1153. self.rightViewMode = .always
  1154. case .right(let spacing):
  1155. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  1156. self.rightView = paddingView
  1157. self.rightViewMode = .always
  1158. case .both(let spacing):
  1159. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  1160. // left
  1161. self.leftView = paddingView
  1162. self.leftViewMode = .always
  1163. // right
  1164. self.rightView = paddingView
  1165. self.rightViewMode = .always
  1166. }
  1167. }
  1168. }
  1169. public class ImageCache {
  1170. public static let shared = ImageCache()
  1171. private let cache = NSCache<NSString, UIImage>()
  1172. private let cacheGif = NSCache<NSString, NSData>()
  1173. private var cacheKeyMap: [String: String] = [:]
  1174. private init() {
  1175. if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
  1176. let cacheDirectory = documentsDirectory.appendingPathComponent("fileCacheImage")
  1177. loadCacheFromDisk(directory: cacheDirectory)
  1178. let cacheGifDirectory = documentsDirectory.appendingPathComponent("fileCacheGif")
  1179. loadCacheFromDisk(directory: cacheGifDirectory)
  1180. }
  1181. }
  1182. public func save(image: UIImage, forKey key: String) {
  1183. let sanitizedKey = sanitizeKey(key)
  1184. cache.setObject(image, forKey: sanitizedKey as NSString)
  1185. cacheKeyMap[key] = sanitizedKey
  1186. if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
  1187. let cacheDirectory = documentsDirectory.appendingPathComponent("fileCacheImage")
  1188. saveCacheToDisk(directory: cacheDirectory)
  1189. }
  1190. }
  1191. public func saveGif(data: NSData, forKey key: String) {
  1192. let sanitizedKey = sanitizeKey(key)
  1193. cacheGif.setObject(data, forKey: sanitizedKey as NSString)
  1194. cacheKeyMap[key] = sanitizedKey
  1195. if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
  1196. let cacheDirectory = documentsDirectory.appendingPathComponent("fileCacheGif")
  1197. saveCacheToDisk(directory: cacheDirectory)
  1198. }
  1199. }
  1200. public func image(forKey key: String) -> UIImage? {
  1201. let sanitizedKey = sanitizeKey(key)
  1202. return cache.object(forKey: sanitizedKey as NSString)
  1203. }
  1204. public func imageGif(forKey key: String) -> NSData? {
  1205. let sanitizedKey = sanitizeKey(key)
  1206. return cacheGif.object(forKey: sanitizedKey as NSString)
  1207. }
  1208. func saveCacheToDisk(directory: URL) {
  1209. let fileManager = FileManager.default
  1210. if !fileManager.fileExists(atPath: directory.path) {
  1211. do {
  1212. try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)
  1213. } catch {
  1214. return
  1215. }
  1216. }
  1217. let mappingFilePath = directory.appendingPathComponent("keyMapping.json")
  1218. do {
  1219. let jsonData = try JSONSerialization.data(withJSONObject: cacheKeyMap, options: [])
  1220. try jsonData.write(to: mappingFilePath)
  1221. } catch {
  1222. return
  1223. }
  1224. for (originalKey, sanitizedKey) in cacheKeyMap {
  1225. if let image = cache.object(forKey: sanitizedKey as NSString),
  1226. let imageData = image.pngData() {
  1227. let filePath = directory.appendingPathComponent("\(sanitizedKey).png")
  1228. do {
  1229. try imageData.write(to: filePath)
  1230. } catch {
  1231. }
  1232. }
  1233. }
  1234. }
  1235. func loadCacheFromDisk(directory: URL) {
  1236. let fileManager = FileManager.default
  1237. // Load mapping
  1238. let mappingFilePath = directory.appendingPathComponent("keyMapping.json")
  1239. guard let jsonData = try? Data(contentsOf: mappingFilePath),
  1240. let loadedMapping = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] else {
  1241. return
  1242. }
  1243. if cacheKeyMap.count == 0 {
  1244. cacheKeyMap = loadedMapping
  1245. }
  1246. // Load images
  1247. do {
  1248. let fileURLs = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil)
  1249. for fileURL in fileURLs {
  1250. if fileURL.lastPathComponent == "keyMapping.json" { continue } // Skip mapping file
  1251. let sanitizedKey = fileURL.deletingPathExtension().lastPathComponent
  1252. if let imageData = try? Data(contentsOf: fileURL),
  1253. let image = UIImage(data: imageData) {
  1254. cache.setObject(image, forKey: sanitizedKey as NSString)
  1255. }
  1256. }
  1257. } catch {
  1258. // print("Failed to load cache from disk: \(error)")
  1259. }
  1260. }
  1261. private func sanitizeKey(_ key: String) -> String {
  1262. let data = Data(key.utf8)
  1263. var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
  1264. data.withUnsafeBytes {
  1265. _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
  1266. }
  1267. return hash.map { String(format: "%02x", $0) }.joined()
  1268. }
  1269. }
  1270. public class LibAlertController: UIAlertController {
  1271. public override func viewWillAppear(_ animated: Bool) {
  1272. super.viewWillAppear(animated)
  1273. // Customize the title's font
  1274. let titleFont = UIFont.boldSystemFont(ofSize: 16)
  1275. let titleAttributes = [NSAttributedString.Key.font: titleFont]
  1276. setValue(NSAttributedString(string: self.title ?? "", attributes: titleAttributes), forKey: "attributedTitle")
  1277. // Change the font for the message
  1278. let messageFont = UIFont.systemFont(ofSize: 14)
  1279. let messageAttributes = [NSAttributedString.Key.font: messageFont]
  1280. setValue(NSAttributedString(string: self.message ?? "", attributes: messageAttributes), forKey: "attributedMessage")
  1281. for i in self.actions {
  1282. let attributedText = NSAttributedString(string: i.title ?? "", attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)])
  1283. guard let label = (i.value(forKey: "__representer") as AnyObject).value(forKey: "label") as? UILabel else { return }
  1284. label.attributedText = attributedText
  1285. }
  1286. }
  1287. }
  1288. extension UISearchBar
  1289. {
  1290. public func setMagnifyingGlassColorTo(color: UIColor)
  1291. {
  1292. // Search Icon
  1293. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1294. let glassIconView = textFieldInsideSearchBar?.leftView as? UIImageView
  1295. glassIconView?.image = glassIconView?.image?.withRenderingMode(.alwaysTemplate)
  1296. glassIconView?.tintColor = color
  1297. }
  1298. func setClearButtonColorTo(color: UIColor)
  1299. {
  1300. // Clear Button
  1301. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1302. let crossIconView = textFieldInsideSearchBar?.value(forKey: "clearButton") as? UIButton
  1303. crossIconView?.setImage(crossIconView?.currentImage?.withRenderingMode(.alwaysTemplate), for: .normal)
  1304. crossIconView?.tintColor = color
  1305. }
  1306. func setPlaceholderTextColorTo(color: UIColor)
  1307. {
  1308. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1309. textFieldInsideSearchBar?.textColor = color
  1310. let textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.value(forKey: "placeholderLabel") as? UILabel
  1311. textFieldInsideSearchBarLabel?.textColor = color
  1312. }
  1313. public func setCustomBackgroundImage(image: UIImage)
  1314. {
  1315. setSearchFieldBackgroundImage(resizeImage(image: image, targetHeight: 30), for: .normal)
  1316. }
  1317. func resizeImage(image: UIImage, targetHeight: CGFloat) -> UIImage? {
  1318. let scaleFactor = targetHeight / image.size.height
  1319. let targetWidth = image.size.width * scaleFactor
  1320. let newSize = CGSize(width: targetWidth, height: targetHeight)
  1321. UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
  1322. defer { UIGraphicsEndImageContext() }
  1323. image.draw(in: CGRect(origin: .zero, size: newSize))
  1324. guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
  1325. return newImage
  1326. }
  1327. public func updateHeight(height: CGFloat, radius: CGFloat = 8.0, borderColor: CGColor = UIColor.white.cgColor) {
  1328. let image: UIImage? = UIImage.imageWithColor(color: UIColor.clear, size: CGSize(width: 1, height: height))
  1329. setSearchFieldBackgroundImage(image, for: .normal)
  1330. for subview in self.subviews {
  1331. for subSubViews in subview.subviews {
  1332. if #available(iOS 13.0, *) {
  1333. for child in subSubViews.subviews {
  1334. if let textField = child as? UISearchTextField {
  1335. textField.layer.cornerRadius = radius
  1336. textField.clipsToBounds = true
  1337. textField.layer.borderColor = borderColor
  1338. textField.layer.borderWidth = 1.0
  1339. }
  1340. }
  1341. continue
  1342. }
  1343. if let textField = subSubViews as? UITextField {
  1344. textField.layer.cornerRadius = radius
  1345. textField.clipsToBounds = true
  1346. textField.layer.borderColor = borderColor
  1347. textField.layer.borderWidth = 1.0
  1348. }
  1349. }
  1350. }
  1351. }
  1352. }
  1353. extension String {
  1354. init(unicodeScalar: UnicodeScalar) {
  1355. self.init(Character(unicodeScalar))
  1356. }
  1357. init?(unicodeCodepoint: Int) {
  1358. if let unicodeScalar = UnicodeScalar(unicodeCodepoint) {
  1359. self.init(unicodeScalar: unicodeScalar)
  1360. } else {
  1361. return nil
  1362. }
  1363. }
  1364. static func +(lhs: String, rhs: Int) -> String {
  1365. return lhs + String(unicodeCodepoint: rhs)!
  1366. }
  1367. static func +=(lhs: inout String, rhs: Int) {
  1368. lhs = lhs + rhs
  1369. }
  1370. }
  1371. extension UIGraphicsRenderer {
  1372. static func renderImagesAt(urls: [NSURL], size: CGSize, scale: CGFloat = 1) -> UIImage {
  1373. let renderer = UIGraphicsImageRenderer(size: size)
  1374. let options: [NSString: Any] = [
  1375. kCGImageSourceThumbnailMaxPixelSize: max(size.width * scale, size.height * scale),
  1376. kCGImageSourceCreateThumbnailFromImageAlways: true
  1377. ]
  1378. let thumbnails = try urls.map { url -> CGImage in
  1379. let imageSource = CGImageSourceCreateWithURL(url, nil)
  1380. let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource!, 0, options as CFDictionary)
  1381. return scaledImage!
  1382. }
  1383. // Translate Y-axis down because cg images are flipped and it falls out of the frame (see bellow)
  1384. let rect = CGRect(x: 0,
  1385. y: -size.height,
  1386. width: size.width,
  1387. height: size.height)
  1388. let resizedImage = renderer.image { ctx in
  1389. let context = ctx.cgContext
  1390. context.scaleBy(x: 1, y: -1) //Flip it ( cg y-axis is flipped)
  1391. for image in thumbnails {
  1392. context.draw(image, in: rect)
  1393. }
  1394. }
  1395. return resizedImage
  1396. }
  1397. static func renderImageAt(url: NSURL, size: CGSize, scale: CGFloat = 1) -> UIImage {
  1398. return renderImagesAt(urls: [url], size: size, scale: scale)
  1399. }
  1400. }
  1401. extension NSAttributedString {
  1402. convenience init(html: String) throws {
  1403. let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
  1404. .documentType: NSAttributedString.DocumentType.html,
  1405. .characterEncoding: String.Encoding.utf8.rawValue
  1406. ]
  1407. guard let data = html.data(using: .utf8) else {
  1408. throw NSError(domain: "Invalid HTML", code: 0, userInfo: nil)
  1409. }
  1410. try self.init(data: data, options: options, documentAttributes: nil)
  1411. }
  1412. }