Extension.swift 58 KB

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