Extension.swift 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323
  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. extension Date {
  12. public func currentTimeMillis() -> Int {
  13. return Int(self.timeIntervalSince1970 * 1000)
  14. }
  15. func format(dateFormat: String) -> String {
  16. let formatter = DateFormatter()
  17. formatter.dateFormat = dateFormat
  18. return formatter.string(from: self)
  19. }
  20. var millisecondsSince1970:Int64 {
  21. return Int64((self.timeIntervalSince1970 * 1000.0).rounded())
  22. }
  23. public init(milliseconds:Int64) {
  24. self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
  25. }
  26. }
  27. extension String {
  28. func toNormalString() -> String {
  29. let _source = self.replacingOccurrences(of: "+", with: "%20")
  30. if var result = _source.removingPercentEncoding {
  31. result = result.replacingOccurrences(of: "<NL>", with: "\n")
  32. result = result.replacingOccurrences(of: "<CR>", with: "\r")
  33. return decrypt(source: result)
  34. }
  35. return self
  36. }
  37. func toStupidString() -> String {
  38. if var result = self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
  39. result = result.replacingOccurrences(of: "\n", with: "<NL>")
  40. result = result.replacingOccurrences(of: "\r", with: "<CR>")
  41. result = result.replacingOccurrences(of: "+", with: "%2B")
  42. return result
  43. }
  44. return self
  45. }
  46. private func decrypt(source : String) -> String {
  47. if let result = source.removingPercentEncoding {
  48. return result
  49. }
  50. return source
  51. }
  52. public func matches(_ regex: String) -> Bool {
  53. return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
  54. }
  55. }
  56. extension Int {
  57. func toHex() -> String {
  58. return String(format: "%02X", self)
  59. }
  60. }
  61. extension UIApplication {
  62. public static var appVersion: String? {
  63. return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
  64. }
  65. var rootViewController: UIViewController? {
  66. return UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
  67. }
  68. public var visibleViewController: UIViewController? {
  69. let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
  70. if var topController = keyWindow?.rootViewController {
  71. while let presentedViewController = topController.presentedViewController {
  72. topController = presentedViewController
  73. }
  74. return topController
  75. }
  76. return nil
  77. }
  78. }
  79. extension UIView {
  80. public func anchor(top: NSLayoutYAxisAnchor? = nil,
  81. left: NSLayoutXAxisAnchor? = nil,
  82. bottom: NSLayoutYAxisAnchor? = nil,
  83. right: NSLayoutXAxisAnchor? = nil,
  84. paddingTop: CGFloat = 0,
  85. paddingLeft: CGFloat = 0,
  86. paddingBottom: CGFloat = 0,
  87. paddingRight: CGFloat = 0,
  88. centerX: NSLayoutXAxisAnchor? = nil,
  89. centerY: NSLayoutYAxisAnchor? = nil,
  90. width: CGFloat = 0,
  91. height: CGFloat = 0,
  92. minHeight: CGFloat = 0,
  93. maxHeight: CGFloat = 0,
  94. minWidth: CGFloat = 0,
  95. maxWidth: CGFloat = 0,
  96. dynamicLeft: Bool = false,
  97. dynamicRight: Bool = false) {
  98. translatesAutoresizingMaskIntoConstraints = false
  99. if let top = top {
  100. topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
  101. }
  102. if let left = left {
  103. if dynamicLeft {
  104. leftAnchor.constraint(greaterThanOrEqualTo: left, constant: paddingLeft).isActive = true
  105. } else {
  106. leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
  107. }
  108. }
  109. if let right = right {
  110. if dynamicRight {
  111. leftAnchor.constraint(lessThanOrEqualTo: right, constant: -paddingRight).isActive = true
  112. } else {
  113. rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
  114. }
  115. }
  116. if let bottom = bottom {
  117. bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
  118. }
  119. if let centerX = centerX {
  120. centerXAnchor.constraint(equalTo: centerX).isActive = true
  121. }
  122. if let centerY = centerY {
  123. centerYAnchor.constraint(equalTo: centerY).isActive = true
  124. }
  125. if height != 0 || minHeight != 0 || maxHeight != 0 {
  126. if minHeight != 0 && maxHeight != 0 {
  127. heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight).isActive = true
  128. heightAnchor.constraint(lessThanOrEqualToConstant: maxHeight).isActive = true
  129. } else if minHeight != 0 && maxHeight == 0 {
  130. heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight).isActive = true
  131. } else if minHeight == 0 && maxHeight != 0 {
  132. heightAnchor.constraint(lessThanOrEqualToConstant: maxHeight).isActive = true
  133. } else {
  134. heightAnchor.constraint(equalToConstant: height).isActive = true
  135. }
  136. }
  137. if width != 0 || minWidth != 0 || maxWidth != 0 {
  138. if minWidth != 0 && maxWidth != 0 {
  139. heightAnchor.constraint(greaterThanOrEqualToConstant: minWidth).isActive = true
  140. heightAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true
  141. } else if minWidth != 0 && maxWidth == 0 {
  142. heightAnchor.constraint(greaterThanOrEqualToConstant: minWidth).isActive = true
  143. } else if minWidth == 0 && maxWidth != 0 {
  144. heightAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true
  145. } else {
  146. widthAnchor.constraint(equalToConstant: width).isActive = true
  147. }
  148. }
  149. }
  150. public func addTopBorder(with color: UIColor?, andWidth borderWidth: CGFloat, view: UIView = UIView()) {
  151. let border = view
  152. border.backgroundColor = color
  153. border.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
  154. border.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: borderWidth)
  155. addSubview(border)
  156. }
  157. public func addBottomBorder(with color: UIColor?, andWidth borderWidth: CGFloat, x: CGFloat = 0, view: UIView = UIView()) {
  158. let border = view
  159. border.backgroundColor = color
  160. border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
  161. border.frame = CGRect(x: x, y: frame.size.height - borderWidth, width: frame.size.width, height: borderWidth)
  162. addSubview(border)
  163. }
  164. public func addLeftBorder(with color: UIColor?, andWidth borderWidth: CGFloat) {
  165. let border = UIView()
  166. border.backgroundColor = color
  167. border.frame = CGRect(x: 0, y: 0, width: borderWidth, height: frame.size.height)
  168. border.autoresizingMask = [.flexibleHeight, .flexibleRightMargin]
  169. addSubview(border)
  170. }
  171. public func addRightBorder(with color: UIColor?, andWidth borderWidth: CGFloat) {
  172. let border = UIView()
  173. border.backgroundColor = color
  174. border.autoresizingMask = [.flexibleHeight, .flexibleLeftMargin]
  175. border.frame = CGRect(x: frame.size.width - borderWidth, y: 0, width: borderWidth, height: frame.size.height)
  176. addSubview(border)
  177. }
  178. }
  179. extension UIViewController {
  180. var previousViewController: UIViewController? {
  181. guard let navigationController = navigationController else { return nil }
  182. let count = navigationController.viewControllers.count
  183. return count < 2 ? nil : navigationController.viewControllers[count - 2]
  184. }
  185. }
  186. extension UIImage {
  187. func resize(target: CGSize) -> UIImage {
  188. // Determine the scale factor that preserves aspect ratio
  189. let widthRatio = target.width / size.width
  190. let heightRatio = target.height / size.height
  191. let scaleFactor = min(widthRatio, heightRatio)
  192. // Compute the new image size that preserves aspect ratio
  193. let scaledImageSize = CGSize(
  194. width: size.width * scaleFactor,
  195. height: size.height * scaleFactor
  196. )
  197. // Draw and return the resized UIImage
  198. let renderer = UIGraphicsImageRenderer(
  199. size: scaledImageSize
  200. )
  201. let scaledImage = renderer.image { _ in
  202. self.draw(in: CGRect(
  203. origin: .zero,
  204. size: scaledImageSize
  205. ))
  206. }
  207. return scaledImage
  208. }
  209. }
  210. extension UIImage {
  211. var isPortrait: Bool { size.height > size.width }
  212. var isLandscape: Bool { size.width > size.height }
  213. var breadth: CGFloat { min(size.width, size.height) }
  214. var breadthSize: CGSize { .init(width: breadth, height: breadth) }
  215. var breadthRect: CGRect { .init(origin: .zero, size: breadthSize) }
  216. public var circleMasked: UIImage? {
  217. guard let cgImage = cgImage?
  218. .cropping(to: .init(origin: .init(x: isLandscape ? ((size.width-size.height)/2).rounded(.down) : 0,
  219. y: isPortrait ? ((size.height-size.width)/2).rounded(.down) : 0),
  220. size: breadthSize)) else { return nil }
  221. let format = imageRendererFormat
  222. format.opaque = false
  223. return UIGraphicsImageRenderer(size: breadthSize, format: format).image { _ in
  224. UIBezierPath(ovalIn: breadthRect).addClip()
  225. UIImage(cgImage: cgImage, scale: format.scale, orientation: imageOrientation)
  226. .draw(in: .init(origin: .zero, size: breadthSize))
  227. }
  228. }
  229. }
  230. extension NSObject {
  231. private static var urlStore = [String:String]()
  232. public func getImage(name url: String, placeholderImage: UIImage? = nil, isCircle: Bool = false, tableView: UITableView? = nil, indexPath: IndexPath? = nil, completion: @escaping (Bool, Bool, UIImage?)->()) {
  233. let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
  234. type(of: self).urlStore[tmpAddress] = url
  235. if url.isEmpty {
  236. completion(false, false, placeholderImage)
  237. return
  238. }
  239. do {
  240. let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
  241. let file = documentDir.appendingPathComponent(url)
  242. if FileManager().fileExists(atPath: file.path) {
  243. let image = UIImage(contentsOfFile: file.path)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  244. completion(true, false, isCircle ? image?.circleMasked : image)
  245. } else {
  246. completion(false, false, placeholderImage)
  247. Download().startHTTP(forKey: url) { (name, progress) in
  248. guard progress == 100 else {
  249. return
  250. }
  251. DispatchQueue.main.async {
  252. if tableView != nil {
  253. tableView!.beginUpdates()
  254. tableView!.reloadRows(at: [indexPath!], with: .none)
  255. tableView!.endUpdates()
  256. }
  257. if type(of: self).urlStore[tmpAddress] == name {
  258. let image = UIImage(contentsOfFile: file.path)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  259. completion(true, true, isCircle ? image?.circleMasked : image)
  260. }
  261. }
  262. }
  263. }
  264. } catch {}
  265. }
  266. func loadImage(named: String, placeholderImage: UIImage?, completion: @escaping (UIImage?, Bool) -> ()) {
  267. guard !named.isEmpty else {
  268. completion(placeholderImage, true)
  269. return
  270. }
  271. SDWebImageManager.shared.loadImage(with: URL.palioImage(named: named), options: .highPriority, progress: .none) { image, data, error, type, finish, url in
  272. completion(image, finish)
  273. }
  274. }
  275. public func deleteAllRecordDatabase() {
  276. Database.shared.database?.inTransaction({ fmdb, rollback in
  277. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "BUDDY", _where: "")
  278. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ", _where: "")
  279. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "")
  280. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ_MEMBER", _where: "")
  281. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "DISCUSSION_FORUM", _where: "")
  282. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "POST", _where: "")
  283. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_STATUS", _where: "")
  284. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "")
  285. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "OUTGOING", _where: "")
  286. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "FOLLOW", _where: "")
  287. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_FAVORITE", _where: "")
  288. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "LINK_PREVIEW", _where: "")
  289. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "PULL_DB", _where: "")
  290. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "PREFS", _where: "")
  291. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", _where: "")
  292. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "FORM_DATA", _where: "")
  293. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "TASK_PIC", _where: "")
  294. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "TASK_DETAIL", _where: "")
  295. })
  296. }
  297. }
  298. extension URL {
  299. static func palioImage(named: String) -> URL? {
  300. return URL(string: "https://newuniverse.io/filepalio/image/\(named)")
  301. }
  302. }
  303. extension UIColor {
  304. public static var mainColor: UIColor {
  305. renderColor(hex: "#3669ad")
  306. }
  307. public static var secondaryColor: UIColor {
  308. return renderColor(hex: "#FAFAFF")
  309. }
  310. public static var orangeColor: UIColor {
  311. return renderColor(hex: "#FFA03E")
  312. }
  313. public static var orangeBNI: UIColor {
  314. return renderColor(hex: "#EE6600")
  315. }
  316. public static var greenColor: UIColor {
  317. return renderColor(hex: "#C7EA46")
  318. }
  319. public static var grayColor: UIColor {
  320. return renderColor(hex: "#F5F5F5")
  321. }
  322. public static var docColor: UIColor {
  323. return renderColor(hex: "#798F9A")
  324. }
  325. public static var mentionColor: UIColor {
  326. return renderColor(hex: "#53bdea")
  327. }
  328. public static var blueBubbleColor: UIColor {
  329. return renderColor(hex: "#C5D1E1")
  330. }
  331. public static var officialColor: UIColor {
  332. return renderColor(hex: "#4c87ef")
  333. }
  334. public static var verifiedColor: UIColor {
  335. return renderColor(hex: "#00b333")
  336. }
  337. public static var ccColor: UIColor {
  338. return renderColor(hex: "#FFF1A353")
  339. }
  340. public static var internalColor: UIColor {
  341. return renderColor(hex: "#ff0000")
  342. }
  343. public class func renderColor(hex: String) -> UIColor {
  344. var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
  345. if (cString.hasPrefix("#")) {
  346. cString.remove(at: cString.startIndex)
  347. }
  348. if ((cString.count) != 6) {
  349. return UIColor.gray
  350. }
  351. var rgbValue:UInt64 = 0
  352. Scanner(string: cString).scanHexInt64(&rgbValue)
  353. return UIColor(
  354. red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
  355. green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
  356. blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
  357. alpha: CGFloat(1.0)
  358. )
  359. }
  360. }
  361. extension UIView {
  362. public func circle() {
  363. layer.cornerRadius = 0.5 * bounds.size.width
  364. clipsToBounds = true
  365. }
  366. public func maxCornerRadius() -> CGFloat {
  367. return (self.frame.width > self.frame.height) ? self.frame.height / 2 : self.frame.width / 2
  368. }
  369. }
  370. extension String {
  371. public func localized(uppercased: Bool = false) -> String {
  372. if let _ = UserDefaults.standard.string(forKey: "i18n_language") {} else {
  373. // we set a default, just in case
  374. let langDefault = UserDefaults.standard.stringArray(forKey: "AppleLanguages")
  375. if langDefault![0].contains("id") {
  376. UserDefaults.standard.set("id", forKey: "i18n_language")
  377. UserDefaults.standard.synchronize()
  378. } else {
  379. UserDefaults.standard.set("en", forKey: "i18n_language")
  380. UserDefaults.standard.synchronize()
  381. }
  382. }
  383. let lang = UserDefaults.standard.string(forKey: "i18n_language")
  384. let bundle = Bundle.resourceBundle(for: Nexilis.self).path(forResource: lang, ofType: "lproj")
  385. let bundlePath = Bundle(path: bundle!)
  386. let result = NSLocalizedString(
  387. self,
  388. tableName: "Localizable",
  389. bundle: bundlePath!,
  390. value: self,
  391. comment: self)
  392. if uppercased {
  393. return result.uppercased()
  394. }
  395. return result
  396. }
  397. }
  398. extension UIViewController {
  399. public func showToast(message : String, font: UIFont = UIFont.systemFont(ofSize: 12, weight: .medium), controller: UIViewController) {
  400. let toastContainer = UIView(frame: CGRect())
  401. toastContainer.backgroundColor = UIColor.mainColor.withAlphaComponent(0.6)
  402. toastContainer.alpha = 0.0
  403. toastContainer.layer.cornerRadius = 25;
  404. toastContainer.clipsToBounds = true
  405. let toastLabel = UILabel(frame: CGRect())
  406. toastLabel.textColor = UIColor.white
  407. toastLabel.textAlignment = .center;
  408. toastLabel.font = font
  409. toastLabel.text = message
  410. toastLabel.clipsToBounds = true
  411. toastLabel.numberOfLines = 0
  412. toastContainer.addSubview(toastLabel)
  413. controller.view.addSubview(toastContainer)
  414. controller.view.bringSubviewToFront(toastContainer)
  415. toastLabel.translatesAutoresizingMaskIntoConstraints = false
  416. toastContainer.translatesAutoresizingMaskIntoConstraints = false
  417. let a1 = NSLayoutConstraint(item: toastLabel, attribute: .leading, relatedBy: .equal, toItem: toastContainer, attribute: .leading, multiplier: 1, constant: 15)
  418. let a2 = NSLayoutConstraint(item: toastLabel, attribute: .trailing, relatedBy: .equal, toItem: toastContainer, attribute: .trailing, multiplier: 1, constant: -15)
  419. let a3 = NSLayoutConstraint(item: toastLabel, attribute: .bottom, relatedBy: .equal, toItem: toastContainer, attribute: .bottom, multiplier: 1, constant: -15)
  420. let a4 = NSLayoutConstraint(item: toastLabel, attribute: .top, relatedBy: .equal, toItem: toastContainer, attribute: .top, multiplier: 1, constant: 15)
  421. toastContainer.addConstraints([a1, a2, a3, a4])
  422. let c1 = NSLayoutConstraint(item: toastContainer, attribute: .leading, relatedBy: .equal, toItem: controller.view, attribute: .leading, multiplier: 1, constant: 65)
  423. let c2 = NSLayoutConstraint(item: toastContainer, attribute: .trailing, relatedBy: .equal, toItem: controller.view, attribute: .trailing, multiplier: 1, constant: -65)
  424. let c3 = NSLayoutConstraint(item: toastContainer, attribute: .bottom, relatedBy: .equal, toItem: controller.view, attribute: .bottom, multiplier: 1, constant: -75)
  425. controller.view.addConstraints([c1, c2, c3])
  426. UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseIn, animations: {
  427. toastContainer.alpha = 1.0
  428. }, completion: { _ in
  429. UIView.animate(withDuration: 0.5, delay: 1.5, options: .curveEaseOut, animations: {
  430. toastContainer.alpha = 0.0
  431. }, completion: {_ in
  432. toastContainer.removeFromSuperview()
  433. })
  434. })
  435. }
  436. public func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
  437. UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0);
  438. image.draw(in: CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height))
  439. let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
  440. UIGraphicsEndImageContext()
  441. return newImage
  442. }
  443. }
  444. extension UITextView {
  445. enum ShouldChangeCursor {
  446. case incrementCursor
  447. case preserveCursor
  448. }
  449. func preserveCursorPosition(withChanges mutatingFunction: (UITextPosition?) -> (ShouldChangeCursor)) {
  450. //save the cursor positon
  451. var cursorPosition: UITextPosition? = nil
  452. if let selectedRange = self.selectedTextRange {
  453. let offset = self.offset(from: self.beginningOfDocument, to: selectedRange.start)
  454. cursorPosition = self.position(from: self.beginningOfDocument, offset: offset)
  455. }
  456. //make mutaing changes that may reset the cursor position
  457. let shouldChangeCursor = mutatingFunction(cursorPosition)
  458. //restore the cursor
  459. if var cursorPosition = cursorPosition {
  460. if shouldChangeCursor == .incrementCursor {
  461. cursorPosition = self.position(from: cursorPosition, offset: 1) ?? cursorPosition
  462. }
  463. if let range = self.textRange(from: cursorPosition, to: cursorPosition) {
  464. self.selectedTextRange = range
  465. }
  466. }
  467. }
  468. }
  469. extension String {
  470. public func substring(from: Int?, to: Int?) -> String {
  471. if let start = from {
  472. guard start < self.count else {
  473. return ""
  474. }
  475. }
  476. if let end = to {
  477. guard end >= 0 else {
  478. return ""
  479. }
  480. }
  481. if let start = from, let end = to {
  482. guard end - start >= 0 else {
  483. return ""
  484. }
  485. }
  486. let startIndex: String.Index
  487. if let start = from, start >= 0 {
  488. startIndex = self.index(self.startIndex, offsetBy: start)
  489. } else {
  490. startIndex = self.startIndex
  491. }
  492. let endIndex: String.Index
  493. if let end = to, end >= 0, end < self.count {
  494. endIndex = self.index(self.startIndex, offsetBy: end + 1)
  495. } else {
  496. endIndex = self.endIndex
  497. }
  498. return String(self[startIndex ..< endIndex])
  499. }
  500. func countEmojiCharacter() -> Int {
  501. func isEmoji(s:NSString) -> Bool {
  502. let high:Int = Int(s.character(at: 0))
  503. if 0xD800 <= high && high <= 0xDBFF {
  504. let low:Int = Int(s.character(at: 1))
  505. let codepoint: Int = ((high - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000
  506. return (0x1D000 <= codepoint && codepoint <= 0x1F9FF)
  507. }
  508. else {
  509. return (0x2100 <= high && high <= 0x27BF)
  510. }
  511. }
  512. let nsString = self as NSString
  513. var length = 0
  514. nsString.enumerateSubstrings(in: NSMakeRange(0, nsString.length), options: NSString.EnumerationOptions.byComposedCharacterSequences) { (subString, substringRange, enclosingRange, stop) -> Void in
  515. if isEmoji(s: subString! as NSString) {
  516. length+=1
  517. }
  518. }
  519. return length
  520. }
  521. public func richText(isEditing: Bool = false, isSearching: Bool = false, textSearch: String = "", group_id: String = "", listMentionInTextField: [User] = []) -> NSMutableAttributedString {
  522. let font = UIFont.systemFont(ofSize: 12)
  523. let boldFont = UIFont.boldSystemFont(ofSize: 12)
  524. let italicFont = UIFont.italicSystemFont(ofSize: 12)
  525. let boldItalicFont = UIFont.systemFont(ofSize: 12, weight: .semibold)
  526. let textUTF8 = String(self.utf8)
  527. let finalText = NSMutableAttributedString(string: textUTF8, attributes: [NSAttributedString.Key.font: font])
  528. let boldSign: Character = "*"
  529. let italicSign: Character = "_"
  530. let underlineSign: Character = "^"
  531. let strikethroughSign: Character = "~"
  532. var locationBold: [NSRange] = []
  533. //Bold
  534. let rangeBold = getRangeOfWordWithSign(sentence: textUTF8, sign: boldSign)
  535. if rangeBold.count > 0 {
  536. var lastFirstRange = -1
  537. var countRemoveBoldSign = 0
  538. var continueCheckingBold = false
  539. var totalEmoji = 0
  540. for i in 0..<rangeBold.count {
  541. if rangeBold[i].startIndex > lastFirstRange {
  542. let charStart: Character = rangeBold[i].startIndex != 0 ? Array(textUTF8.substring(from: rangeBold[i].startIndex - 1, to: rangeBold[i].startIndex - 1))[0] : Array("0")[0]
  543. if (rangeBold[i].startIndex == 0 || (!charStart.isLetter && !charStart.isNumber)) {
  544. lastFirstRange = rangeBold[i].startIndex
  545. continueCheckingBold = true
  546. } else {
  547. continueCheckingBold = false
  548. }
  549. }
  550. if !continueCheckingBold {
  551. continue
  552. }
  553. if rangeBold[i].endIndex != (textUTF8.count-1) {
  554. let char: Character = Array(textUTF8.substring(from: rangeBold[i].endIndex + 1, to: rangeBold[i].endIndex + 1))[0]
  555. if char.isLetter || char.isNumber {
  556. continue
  557. }
  558. }
  559. let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveBoldSign)).countEmojiCharacter()
  560. let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveBoldSign), to: rangeBold[i].endIndex - (2*countRemoveBoldSign)).countEmojiCharacter()
  561. totalEmoji = countEmoji + countEmojiBefore
  562. locationBold.append(NSRange(location: lastFirstRange - (2*countRemoveBoldSign) + countEmojiBefore, length: (rangeBold[i].endIndex + countEmoji + 1) - lastFirstRange))
  563. finalText.addAttribute(.font, value: boldFont, range: NSRange(location: lastFirstRange - (2*countRemoveBoldSign) + countEmojiBefore, length: (rangeBold[i].endIndex + countEmoji + 1) - lastFirstRange))
  564. if !isEditing{
  565. finalText.mutableString.replaceOccurrences(of: "\(boldSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveBoldSign), length: 1))
  566. finalText.mutableString.replaceOccurrences(of: "\(boldSign)", with: "", options: .literal, range: NSRange(location: rangeBold[i].endIndex + totalEmoji - (2*countRemoveBoldSign) - 1, length: 1))
  567. countRemoveBoldSign += 1
  568. } else {
  569. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
  570. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeBold[i].endIndex + totalEmoji, length: 1))
  571. }
  572. lastFirstRange = rangeBold[i].endIndex
  573. continueCheckingBold = false
  574. }
  575. }
  576. //Italic
  577. let textAfterbold = finalText.string
  578. let rangeItalic = getRangeOfWordWithSign(sentence: textAfterbold, sign: italicSign)
  579. if rangeItalic.count > 0 {
  580. var lastFirstRange = -1
  581. var countRemoveItalicSign = 0
  582. var continueCheckingItalic = false
  583. var totalEmoji = 0
  584. for i in 0..<rangeItalic.count {
  585. if rangeItalic[i].startIndex > lastFirstRange {
  586. let charStart: Character = rangeItalic[i].startIndex != 0 ? Array(textUTF8.substring(from: rangeItalic[i].startIndex - 1, to: rangeItalic[i].startIndex - 1))[0] : Array("0")[0]
  587. if (rangeItalic[i].startIndex == 0 || (!charStart.isLetter && !charStart.isNumber)) {
  588. lastFirstRange = rangeItalic[i].startIndex
  589. continueCheckingItalic = true
  590. } else {
  591. continueCheckingItalic = false
  592. }
  593. }
  594. if !continueCheckingItalic {
  595. continue
  596. }
  597. if rangeItalic[i].endIndex != (textAfterbold.count-1) {
  598. let char: Character = Array(textAfterbold.substring(from: rangeItalic[i].endIndex + 1, to: rangeItalic[i].endIndex + 1))[0]
  599. if char.isLetter || char.isNumber {
  600. continue
  601. }
  602. }
  603. let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveItalicSign)).countEmojiCharacter()
  604. let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveItalicSign), to: rangeItalic[i].endIndex - (2*countRemoveItalicSign)).countEmojiCharacter()
  605. totalEmoji = countEmoji + countEmojiBefore
  606. if isIntInRangeList((rangeItalic[i].endIndex + countEmoji + 1) - lastFirstRange - lastFirstRange - (2*countRemoveItalicSign) + countEmojiBefore, rangeList: locationBold) {
  607. finalText.addAttribute(.font, value: boldItalicFont, range: NSRange(location: lastFirstRange - (2*countRemoveItalicSign) + countEmojiBefore, length: (rangeItalic[i].endIndex + countEmoji + 1) - lastFirstRange))
  608. } else {
  609. finalText.addAttribute(.font, value: italicFont, range: NSRange(location: lastFirstRange - (2*countRemoveItalicSign) + countEmojiBefore, length: (rangeItalic[i].endIndex + countEmoji + 1) - lastFirstRange))
  610. }
  611. if !isEditing{
  612. finalText.mutableString.replaceOccurrences(of: "\(italicSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveItalicSign), length: 1))
  613. finalText.mutableString.replaceOccurrences(of: "\(italicSign)", with: "", options: .literal, range: NSRange(location: rangeItalic[i].endIndex + totalEmoji - (2*countRemoveItalicSign) - 1, length: 1))
  614. countRemoveItalicSign += 1
  615. } else {
  616. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
  617. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeItalic[i].endIndex + totalEmoji, length: 1))
  618. }
  619. lastFirstRange = rangeItalic[i].endIndex
  620. continueCheckingItalic = false
  621. }
  622. }
  623. //Underline
  624. let textAfterItalic = finalText.string
  625. let rangeUnderline = getRangeOfWordWithSign(sentence: textAfterItalic, sign: underlineSign)
  626. if rangeUnderline.count > 0 {
  627. var lastFirstRange = -1
  628. var countRemoveUnderlineSign = 0
  629. var continueCheckingUnderline = false
  630. var totalEmoji = 0
  631. for i in 0..<rangeUnderline.count {
  632. if rangeUnderline[i].startIndex > lastFirstRange {
  633. let charStart: Character = rangeUnderline[i].startIndex != 0 ? Array(textUTF8.substring(from: rangeUnderline[i].startIndex - 1, to: rangeUnderline[i].startIndex - 1))[0] : Array("0")[0]
  634. if (rangeUnderline[i].startIndex == 0 || (!charStart.isLetter && !charStart.isNumber)) {
  635. lastFirstRange = rangeUnderline[i].startIndex
  636. continueCheckingUnderline = true
  637. } else {
  638. continueCheckingUnderline = false
  639. }
  640. }
  641. if !continueCheckingUnderline {
  642. continue
  643. }
  644. if rangeUnderline[i].endIndex != (textAfterItalic.count-1) {
  645. let char: Character = Array(textAfterItalic.substring(from: rangeUnderline[i].endIndex + 1, to: rangeUnderline[i].endIndex + 1))[0]
  646. if char.isLetter || char.isNumber {
  647. continue
  648. }
  649. }
  650. let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveUnderlineSign)).countEmojiCharacter()
  651. let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveUnderlineSign), to: rangeUnderline[i].endIndex - (2*countRemoveUnderlineSign)).countEmojiCharacter()
  652. totalEmoji = countEmoji + countEmojiBefore
  653. finalText.addAttribute(.underlineStyle, value: NSUnderlineStyle.thick.rawValue, range: NSRange(location: lastFirstRange - (2*countRemoveUnderlineSign) + countEmojiBefore + 1, length: (rangeUnderline[i].endIndex + countEmoji - 1) - lastFirstRange))
  654. if !isEditing{
  655. finalText.mutableString.replaceOccurrences(of: "\(underlineSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveUnderlineSign), length: 1))
  656. finalText.mutableString.replaceOccurrences(of: "\(underlineSign)", with: "", options: .literal, range: NSRange(location: rangeUnderline[i].endIndex + totalEmoji - (2*countRemoveUnderlineSign) - 1, length: 1))
  657. countRemoveUnderlineSign += 1
  658. } else {
  659. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
  660. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeUnderline[i].endIndex + totalEmoji, length: 1))
  661. }
  662. lastFirstRange = rangeUnderline[i].endIndex
  663. continueCheckingUnderline = false
  664. }
  665. }
  666. //Strikethrough
  667. let textAfterUnderline = finalText.string
  668. let rangeStrikethrough = getRangeOfWordWithSign(sentence: textAfterUnderline, sign: strikethroughSign)
  669. if rangeStrikethrough.count > 0 {
  670. var lastFirstRange = -1
  671. var countRemoveStrikethroughSign = 0
  672. var continueCheckingStrikethrough = false
  673. var totalEmoji = 0
  674. for i in 0..<rangeStrikethrough.count {
  675. if rangeStrikethrough[i].startIndex > lastFirstRange {
  676. let charStart: Character = rangeStrikethrough[i].startIndex != 0 ? Array(textUTF8.substring(from: rangeStrikethrough[i].startIndex - 1, to: rangeStrikethrough[i].startIndex - 1))[0] : Array("0")[0]
  677. if (rangeStrikethrough[i].startIndex == 0 || (!charStart.isLetter && !charStart.isNumber)) {
  678. lastFirstRange = rangeStrikethrough[i].startIndex
  679. continueCheckingStrikethrough = true
  680. } else {
  681. continueCheckingStrikethrough = false
  682. }
  683. }
  684. if !continueCheckingStrikethrough {
  685. continue
  686. }
  687. if rangeStrikethrough[i].endIndex != (textAfterUnderline.count-1) {
  688. let char: Character = Array(textAfterUnderline.substring(from: rangeStrikethrough[i].endIndex + 1, to: rangeStrikethrough[i].endIndex + 1))[0]
  689. if char.isLetter || char.isNumber {
  690. continue
  691. }
  692. }
  693. let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveStrikethroughSign)).countEmojiCharacter()
  694. let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveStrikethroughSign), to: rangeStrikethrough[i].endIndex - (2*countRemoveStrikethroughSign)).countEmojiCharacter()
  695. totalEmoji = countEmoji + countEmojiBefore
  696. finalText.addAttribute(.strikethroughStyle, value: 2, range: NSRange(location: lastFirstRange - (2*countRemoveStrikethroughSign) + countEmojiBefore + 1, length: (rangeStrikethrough[i].endIndex + countEmoji - 1) - lastFirstRange))
  697. if !isEditing{
  698. finalText.mutableString.replaceOccurrences(of: "\(strikethroughSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveStrikethroughSign), length: 1))
  699. finalText.mutableString.replaceOccurrences(of: "\(strikethroughSign)", with: "", options: .literal, range: NSRange(location: rangeStrikethrough[i].endIndex + totalEmoji - (2*countRemoveStrikethroughSign) - 1, length: 1))
  700. countRemoveStrikethroughSign += 1
  701. } else {
  702. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
  703. finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeStrikethrough[i].endIndex + totalEmoji, length: 1))
  704. }
  705. lastFirstRange = rangeStrikethrough[i].endIndex
  706. continueCheckingStrikethrough = false
  707. }
  708. }
  709. //Check Mention
  710. let finalTextAfterRichText = finalText.string
  711. if finalTextAfterRichText.contains("@") {
  712. let listTextEnter = finalText.string.split(separator: "\n")
  713. for j in 0...listTextEnter.count - 1 {
  714. let listText = listTextEnter[j].split(separator: " ")
  715. let listMention = listText.filter({ $0.starts(with: "@")})
  716. if listMention.count > 0 {
  717. for i in 0..<listMention.count {
  718. let f_pin = (String(listMention[i])).substring(from: 1, to: listMention[i].count)
  719. let member = Member.getMember(f_pin: f_pin)
  720. if member != nil {
  721. let name = (member!.firstName + " " + member!.lastName).trimmingCharacters(in: .whitespaces)
  722. finalText.mutableString.replaceOccurrences(of: f_pin, with: name, options: .literal, range: NSString(string: finalText.string).range(of: f_pin))
  723. if !group_id.isEmpty && Member.getMemberInGroup(f_pin: f_pin, group_id: group_id) != nil {
  724. finalText.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSString(string: finalText.string).range(of: "@\(name)"))
  725. }
  726. }
  727. }
  728. }
  729. }
  730. }
  731. if isSearching {
  732. let range = NSString(string: finalText.string).range(of: textSearch, options: .caseInsensitive) // 2
  733. let highlightColor = UIColor.systemYellow // 3
  734. let highlightedAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.backgroundColor: highlightColor] // 4
  735. finalText.addAttributes(highlightedAttributes, range: range) // 5
  736. }
  737. return finalText
  738. }
  739. func isIntInRangeList(_ intValue: Int, rangeList: [NSRange]) -> Bool {
  740. for range in rangeList {
  741. if intValue >= range.location && intValue < range.location + range.length {
  742. return true
  743. }
  744. }
  745. return false
  746. }
  747. func checkCharBefore(char: String) -> Bool {
  748. return char == " " || char == "\n"
  749. }
  750. func checkCharRich(char: String) -> Bool {
  751. return char == "*" || char == "_" || char == "^" || char == "~"
  752. }
  753. func checkStartWithLink() -> Bool {
  754. return self.starts(with: "https://") || self.starts(with: "http://") || self.starts(with: "www.")
  755. //|| 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.")
  756. }
  757. func getRangeOfWordWithSign(sentence: String, sign: Character) -> [Range<Int>] {
  758. let asterisk: Character = sign
  759. var ranges = [Range<Int>]()
  760. var startIndex = sentence.startIndex
  761. while let startRange = sentence[startIndex...].firstIndex(of: asterisk),
  762. let endRange = sentence[startRange...].dropFirst().firstIndex(of: asterisk) {
  763. let range = startRange..<endRange
  764. let word = sentence[range].replacingOccurrences(of: "\(sign)", with: "")
  765. if !word.isEmpty {
  766. let lower = sentence.distance(from: sentence.startIndex, to: startRange)
  767. let upper = sentence.distance(from: sentence.startIndex, to: endRange)
  768. ranges.append(Range(uncheckedBounds: (lower: lower, upper: upper)))
  769. }
  770. startIndex = endRange
  771. }
  772. return ranges
  773. }
  774. }
  775. extension UIFont {
  776. var bold: UIFont {
  777. return with(traits: .traitBold)
  778. } // bold
  779. var italic: UIFont {
  780. return with(traits: .traitItalic)
  781. } // italic
  782. func with(traits: UIFontDescriptor.SymbolicTraits) -> UIFont {
  783. guard let descriptor = self.fontDescriptor.withSymbolicTraits(traits) else {
  784. return self
  785. } // guard
  786. return UIFont(descriptor: descriptor, size: 0)
  787. } // with(traits:)
  788. }
  789. extension UILabel {
  790. public func set(image: UIImage, with text: String, size: CGFloat, y: CGFloat) {
  791. let attachment = NSTextAttachment()
  792. attachment.image = image
  793. attachment.bounds = CGRect(x: 0, y: y, width: size, height: size)
  794. let attachmentStr = NSAttributedString(attachment: attachment)
  795. let mutableAttributedString = NSMutableAttributedString()
  796. mutableAttributedString.append(attachmentStr)
  797. let textString = NSAttributedString(string: text, attributes: [.font: self.font!])
  798. mutableAttributedString.append(textString)
  799. self.attributedText = mutableAttributedString
  800. }
  801. public func setAttributeText(image: UIImage, with textMutable: NSAttributedString, size: CGFloat, y: CGFloat) {
  802. let attachment = NSTextAttachment()
  803. attachment.image = image
  804. attachment.bounds = CGRect(x: 0, y: y, width: size, height: size)
  805. let attachmentStr = NSAttributedString(attachment: attachment)
  806. let mutableAttributedString = NSMutableAttributedString()
  807. mutableAttributedString.append(attachmentStr)
  808. mutableAttributedString.append(textMutable)
  809. self.attributedText = mutableAttributedString
  810. }
  811. }
  812. extension Bundle {
  813. public static func resourceBundle(for frameworkClass: AnyClass) -> Bundle {
  814. let frameworkBundle = Bundle(for: frameworkClass)
  815. guard let resourceBundleURL = frameworkBundle.url(forResource: "NexilisLite", withExtension: "bundle"),
  816. let resourceBundle = Bundle(url: resourceBundleURL) else {
  817. return frameworkBundle
  818. }
  819. return resourceBundle
  820. }
  821. public static func resourcesMediaBundle(for frameworkClass: AnyClass) -> Bundle {
  822. let frameworkBundle = Bundle(for: frameworkClass)
  823. guard let resourceBundleURL = frameworkBundle.url(forResource: "NexilisLiteResources", withExtension: "bundle"),
  824. let resourceBundle = Bundle(url: resourceBundleURL) else {
  825. //print("\(moduleName).bundle not found in \(frameworkBundle)")
  826. return frameworkBundle
  827. }
  828. return resourceBundle
  829. }
  830. }
  831. //extension UIFont {
  832. //
  833. // static func register(from url: URL) throws {
  834. // guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
  835. // throw fatalError("Could not create font data provider for \(url).")
  836. // }
  837. // let font = CGFont(fontDataProvider)
  838. // var error: Unmanaged<CFError>?
  839. // guard CTFontManagerRegisterGraphicsFont(font!, &error) else {
  840. // throw error!.takeUnretainedValue()
  841. // }
  842. // }
  843. //
  844. //}
  845. extension UIButton {
  846. private func actionHandleBlock(action:(() -> Void)? = nil) {
  847. struct __ {
  848. static var action :(() -> Void)?
  849. }
  850. if action != nil {
  851. __.action = action
  852. } else {
  853. __.action?()
  854. }
  855. }
  856. @objc private func triggerActionHandleBlock() {
  857. self.actionHandleBlock()
  858. }
  859. public func actionHandle(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
  860. self.actionHandleBlock(action: action)
  861. self.addTarget(self, action: #selector(self.triggerActionHandleBlock), for: control)
  862. }
  863. }
  864. extension UINavigationController {
  865. func replaceAllViewController(with viewController: UIViewController, animated: Bool) {
  866. pushViewController(viewController, animated: animated)
  867. viewControllers.removeSubrange(1...viewControllers.count - 2)
  868. }
  869. var rootViewController : UIViewController? {
  870. return viewControllers.first
  871. }
  872. func popViewController(animated: Bool, completion: @escaping () -> Void) {
  873. popViewController(animated: animated)
  874. if animated, let coordinator = transitionCoordinator {
  875. coordinator.animate(alongsideTransition: nil) { _ in
  876. completion()
  877. }
  878. } else {
  879. completion()
  880. }
  881. }
  882. }
  883. extension UIImageView {
  884. private static var taskKey = 0
  885. private static var urlKey = 0
  886. private var currentTask: URLSessionTask? {
  887. get { return objc_getAssociatedObject(self, &UIImageView.taskKey) as? URLSessionTask }
  888. set { objc_setAssociatedObject(self, &UIImageView.taskKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  889. }
  890. private var currentURL: URL? {
  891. get { return objc_getAssociatedObject(self, &UIImageView.urlKey) as? URL }
  892. set { objc_setAssociatedObject(self, &UIImageView.urlKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  893. }
  894. func loadImageAsync(with urlString: String?) {
  895. // cancel prior task, if any
  896. weak var oldTask = currentTask
  897. currentTask = nil
  898. oldTask?.cancel()
  899. // reset imageview's image
  900. self.image = nil
  901. // allow supplying of `nil` to remove old image and then return immediately
  902. guard let urlString = urlString else { return }
  903. // check cache
  904. if let cachedImage = ImageCache.shared.image(forKey: urlString) {
  905. self.image = cachedImage
  906. return
  907. }
  908. // download
  909. let url = URL(string: urlString)!
  910. currentURL = url
  911. let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
  912. self?.currentTask = nil
  913. //error handling
  914. if let error = error {
  915. // don't bother reporting cancelation errors
  916. if (error as NSError).domain == NSURLErrorDomain && (error as NSError).code == NSURLErrorCancelled {
  917. return
  918. }
  919. //print(error)
  920. return
  921. }
  922. guard let data = data, let downloadedImage = UIImage(data: data) else {
  923. //print("unable to extract image")
  924. return
  925. }
  926. ImageCache.shared.save(image: downloadedImage, forKey: urlString)
  927. if url == self?.currentURL {
  928. DispatchQueue.main.async {
  929. self?.image = downloadedImage
  930. }
  931. }
  932. }
  933. // save and start new task
  934. currentTask = task
  935. task.resume()
  936. }
  937. private func actionHandleBlock(action:(() -> Void)? = nil) {
  938. struct __ {
  939. static var action :(() -> Void)?
  940. }
  941. if action != nil {
  942. __.action = action
  943. } else {
  944. __.action?()
  945. }
  946. }
  947. @objc private func triggerActionHandleBlock() {
  948. self.actionHandleBlock()
  949. }
  950. func actionHandle(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
  951. self.actionHandleBlock(action: action)
  952. self.isUserInteractionEnabled = true
  953. self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.triggerActionHandleBlock)))
  954. }
  955. }
  956. extension UITextField {
  957. public enum PaddingSide {
  958. case left(CGFloat)
  959. case right(CGFloat)
  960. case both(CGFloat)
  961. }
  962. public func addPadding(_ padding: PaddingSide) {
  963. self.leftViewMode = .always
  964. self.layer.masksToBounds = true
  965. switch padding {
  966. case .left(let spacing):
  967. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  968. self.leftView = paddingView
  969. self.rightViewMode = .always
  970. case .right(let spacing):
  971. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  972. self.rightView = paddingView
  973. self.rightViewMode = .always
  974. case .both(let spacing):
  975. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  976. // left
  977. self.leftView = paddingView
  978. self.leftViewMode = .always
  979. // right
  980. self.rightView = paddingView
  981. self.rightViewMode = .always
  982. }
  983. }
  984. }
  985. class ImageCache {
  986. private let cache = NSCache<NSString, UIImage>()
  987. private var observer: NSObjectProtocol!
  988. static let shared = ImageCache()
  989. private init() {
  990. // make sure to purge cache on memory pressure
  991. observer = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: nil) { [weak self] notification in
  992. self?.cache.removeAllObjects()
  993. }
  994. }
  995. deinit {
  996. NotificationCenter.default.removeObserver(observer as Any)
  997. }
  998. func image(forKey key: String) -> UIImage? {
  999. return cache.object(forKey: key as NSString)
  1000. }
  1001. func save(image: UIImage, forKey key: String) {
  1002. cache.setObject(image, forKey: key as NSString)
  1003. }
  1004. }
  1005. public class LibAlertController: UIAlertController {
  1006. public override func viewWillAppear(_ animated: Bool) {
  1007. super.viewWillAppear(animated)
  1008. // Customize the title's font
  1009. let titleFont = UIFont.boldSystemFont(ofSize: 16)
  1010. let titleAttributes = [NSAttributedString.Key.font: titleFont]
  1011. setValue(NSAttributedString(string: self.title ?? "", attributes: titleAttributes), forKey: "attributedTitle")
  1012. // Change the font for the message
  1013. let messageFont = UIFont.systemFont(ofSize: 14)
  1014. let messageAttributes = [NSAttributedString.Key.font: messageFont]
  1015. setValue(NSAttributedString(string: self.message ?? "", attributes: messageAttributes), forKey: "attributedMessage")
  1016. for i in self.actions {
  1017. let attributedText = NSAttributedString(string: i.title ?? "", attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)])
  1018. guard let label = (i.value(forKey: "__representer") as AnyObject).value(forKey: "label") as? UILabel else { return }
  1019. label.attributedText = attributedText
  1020. }
  1021. }
  1022. }
  1023. extension UISearchBar
  1024. {
  1025. func setMagnifyingGlassColorTo(color: UIColor)
  1026. {
  1027. // Search Icon
  1028. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1029. let glassIconView = textFieldInsideSearchBar?.leftView as? UIImageView
  1030. glassIconView?.image = glassIconView?.image?.withRenderingMode(.alwaysTemplate)
  1031. glassIconView?.tintColor = color
  1032. }
  1033. func setClearButtonColorTo(color: UIColor)
  1034. {
  1035. // Clear Button
  1036. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1037. let crossIconView = textFieldInsideSearchBar?.value(forKey: "clearButton") as? UIButton
  1038. crossIconView?.setImage(crossIconView?.currentImage?.withRenderingMode(.alwaysTemplate), for: .normal)
  1039. crossIconView?.tintColor = color
  1040. }
  1041. func setPlaceholderTextColorTo(color: UIColor)
  1042. {
  1043. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1044. textFieldInsideSearchBar?.textColor = color
  1045. let textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.value(forKey: "placeholderLabel") as? UILabel
  1046. textFieldInsideSearchBarLabel?.textColor = color
  1047. }
  1048. }
  1049. extension String {
  1050. init(unicodeScalar: UnicodeScalar) {
  1051. self.init(Character(unicodeScalar))
  1052. }
  1053. init?(unicodeCodepoint: Int) {
  1054. if let unicodeScalar = UnicodeScalar(unicodeCodepoint) {
  1055. self.init(unicodeScalar: unicodeScalar)
  1056. } else {
  1057. return nil
  1058. }
  1059. }
  1060. static func +(lhs: String, rhs: Int) -> String {
  1061. return lhs + String(unicodeCodepoint: rhs)!
  1062. }
  1063. static func +=(lhs: inout String, rhs: Int) {
  1064. lhs = lhs + rhs
  1065. }
  1066. }
  1067. extension UIGraphicsRenderer {
  1068. static func renderImagesAt(urls: [NSURL], size: CGSize, scale: CGFloat = 1) -> UIImage {
  1069. let renderer = UIGraphicsImageRenderer(size: size)
  1070. let options: [NSString: Any] = [
  1071. kCGImageSourceThumbnailMaxPixelSize: max(size.width * scale, size.height * scale),
  1072. kCGImageSourceCreateThumbnailFromImageAlways: true
  1073. ]
  1074. let thumbnails = try urls.map { url -> CGImage in
  1075. let imageSource = CGImageSourceCreateWithURL(url, nil)
  1076. let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource!, 0, options as CFDictionary)
  1077. return scaledImage!
  1078. }
  1079. // Translate Y-axis down because cg images are flipped and it falls out of the frame (see bellow)
  1080. let rect = CGRect(x: 0,
  1081. y: -size.height,
  1082. width: size.width,
  1083. height: size.height)
  1084. let resizedImage = renderer.image { ctx in
  1085. let context = ctx.cgContext
  1086. context.scaleBy(x: 1, y: -1) //Flip it ( cg y-axis is flipped)
  1087. for image in thumbnails {
  1088. context.draw(image, in: rect)
  1089. }
  1090. }
  1091. return resizedImage
  1092. }
  1093. static func renderImageAt(url: NSURL, size: CGSize, scale: CGFloat = 1) -> UIImage {
  1094. return renderImagesAt(urls: [url], size: size, scale: scale)
  1095. }
  1096. }