NCCustomizableAlertController.swift 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. //
  2. // NCCustomizableAlertController.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 30/09/19.
  6. // Copyright © 2018 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. // Medium article
  24. //
  25. // https://medium.com/@Daniel_illescas/hacking-ios-alerts-with-swift-61aefce9736a
  26. import UIKit
  27. final class DarkAlertController: NCCustomizableAlertController {
  28. override func viewDidLoad() {
  29. super.viewDidLoad()
  30. self.visualEffectView?.effect = UIBlurEffect(style: .dark)
  31. self.tintColor = UIColor(red: 0.4, green: 0.5, blue: 1.0, alpha: 1.0)
  32. self.contentView?.backgroundColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 0.7)
  33. let whiteStringAttribute = StringAttribute(key: .foregroundColor, value: UIColor.white)
  34. self.titleAttributes = [whiteStringAttribute]
  35. self.messageAttributes = [whiteStringAttribute]
  36. }
  37. }
  38. //
  39. open class NCCustomizableAlertController: UIAlertController {
  40. open lazy var visualEffectView: UIVisualEffectView? = {
  41. return self.view.visualEffectView
  42. }()
  43. open lazy var lazyContentView: UIView? = {
  44. return self.contentView
  45. }()
  46. open lazy var tintColor: UIColor? = {
  47. return self.view.tintColor
  48. }()
  49. override open func viewWillAppear(_ animated: Bool) {
  50. super.viewDidAppear(animated)
  51. self.view.tintColor = self.tintColor
  52. }
  53. func addParallaxEffect(x: Int = 20, y: Int = 20) {
  54. let horizontal = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.x", type: .tiltAlongHorizontalAxis)
  55. horizontal.minimumRelativeValue = -x
  56. horizontal.maximumRelativeValue = x
  57. let vertical = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.y", type: .tiltAlongVerticalAxis)
  58. vertical.minimumRelativeValue = -y
  59. vertical.maximumRelativeValue = y
  60. let motionEffectsGroup = UIMotionEffectGroup()
  61. motionEffectsGroup.motionEffects = [horizontal, vertical]
  62. self.view.addMotionEffect(motionEffectsGroup)
  63. }
  64. }
  65. //
  66. extension UIAlertController {
  67. private var visualEffectView: UIVisualEffectView? {
  68. return self.view.visualEffectView
  69. }
  70. var contentView: UIView? {
  71. return self.view.subviews.first?.subviews.first?.subviews.first
  72. }
  73. var titleAttributes: [StringAttribute] {
  74. get { return self.attributedTitle_?.attributes ?? [] }
  75. set { self.attributedTitle_ = newValue.suitableAttributedText(forText: self.title) }
  76. }
  77. var messageAttributes: [StringAttribute] {
  78. get { return self.attributedMessage_?.attributes ?? [] }
  79. set { self.attributedMessage_ = newValue.suitableAttributedText(forText: self.message) }
  80. }
  81. }
  82. extension UIAlertAction {
  83. var label: UILabel? {
  84. return (self.value(forKey: "__representer") as? NSObject)?.value(forKey: "label") as? UILabel
  85. }
  86. var titleAttributes: [StringAttribute] {
  87. get { return self.label?.attributedText?.attributes ?? [] }
  88. set { self.label?.textAttributes = newValue }
  89. }
  90. var titleTextColor: UIColor? {
  91. get { return self.titleTextColor_ }
  92. set { self.titleTextColor_ = newValue }
  93. }
  94. var accessoryImage: UIImage? {
  95. get { return self.image_ }
  96. set { self.image_ = newValue }
  97. }
  98. var contentElementViewController: ElementViewController? {
  99. get { return self.contentViewController_ as? ElementViewController }
  100. set {
  101. if accessoryImage != nil {
  102. print("The accessory image might overlap with the content of the contentViewController")
  103. }
  104. self.contentViewController_ = newValue
  105. }
  106. }
  107. var contentViewController: UIViewController? {
  108. get { return self.contentViewController_ }
  109. set {
  110. if accessoryImage != nil {
  111. print("The accessory image might overlap with the content of the contentViewController")
  112. }
  113. self.contentViewController_ = newValue
  114. }
  115. }
  116. var tableViewController: UITableViewController? {
  117. get { return self.contentViewController_ as? UITableViewController }
  118. set {
  119. if accessoryImage != nil {
  120. print("The accessory image might overlap with the content of the contentViewController")
  121. }
  122. self.contentViewController_ = newValue
  123. }
  124. }
  125. var accessoryView: UIView? {
  126. get { return contentElementViewController?.elementView }
  127. set {
  128. let elementViewController = ElementViewController()
  129. elementViewController.elementView = newValue
  130. self.contentViewController = elementViewController
  131. }
  132. }
  133. }
  134. extension UILabel {
  135. var textAttributes: [StringAttribute] {
  136. get { return self.attributedText?.attributes ?? [] }
  137. set { self.attributedText = newValue.suitableAttributedText(forText: self.text) }
  138. }
  139. }
  140. extension NSAttributedString {
  141. struct StringAttribute {
  142. let key: NSAttributedString.Key
  143. let value: Any
  144. var range: NSRange? = nil
  145. init(key: NSAttributedString.Key, value: Any, range: NSRange? = nil) {
  146. self.key = key
  147. self.value = value
  148. self.range = range
  149. }
  150. }
  151. var attributes: [StringAttribute] {
  152. var savedAttributes: [StringAttribute] = []
  153. let rawAttributes = self.attributes(at: 0, longestEffectiveRange: nil, in: NSRange(location: 0, length: self.length))
  154. for rawAttribute in rawAttributes {
  155. savedAttributes.append(StringAttribute(key: rawAttribute.key, value: rawAttribute.value))
  156. }
  157. return savedAttributes
  158. }
  159. convenience init(string: String?, attributes: [StringAttribute]) {
  160. guard let validString = string else { self.init(string: "", attributes: [:]); return }
  161. var attributesDict: [NSAttributedString.Key: Any] = [:]
  162. for attribute in attributes {
  163. attributesDict[attribute.key] = attribute.value
  164. }
  165. self.init(string: validString, attributes: attributesDict)
  166. }
  167. }
  168. extension NSMutableAttributedString {
  169. var mutableAttributes: [StringAttribute] {
  170. get { return self.attributes }
  171. set {
  172. let defaultRange = NSRange(location: 0, length: self.length)
  173. for attribute in newValue {
  174. self.addAttribute(attribute.key, value: attribute.value, range: attribute.range ?? defaultRange)
  175. }
  176. }
  177. }
  178. convenience init(string: String?, mutableAttributes: [StringAttribute]) {
  179. guard let text = string else { self.init(string: ""); return }
  180. self.init(string: text)
  181. self.mutableAttributes = mutableAttributes
  182. }
  183. }
  184. class ElementViewController: UIViewController {
  185. var elementView: UIView? = nil
  186. override func viewDidLoad() {
  187. super.viewDidLoad()
  188. if let validView = self.elementView {
  189. self.view.addSubview(validView)
  190. validView.translatesAutoresizingMaskIntoConstraints = false
  191. let margins = self.view.layoutMarginsGuide
  192. validView.centerYAnchor.constraint(equalTo: margins.topAnchor, constant: validView.frame.height).isActive = true
  193. validView.centerXAnchor.constraint(equalTo: margins.centerXAnchor).isActive = true
  194. validView.widthAnchor.constraint(equalTo: margins.widthAnchor).isActive = true
  195. validView.heightAnchor.constraint(equalTo: margins.heightAnchor).isActive = true
  196. }
  197. }
  198. }
  199. //
  200. typealias StringAttribute = NSAttributedString.StringAttribute
  201. extension Array where Element == StringAttribute {
  202. func suitableAttributedText(forText text: String?) -> NSAttributedString {
  203. if self.compactMap({ $0.range }).isEmpty {
  204. return NSAttributedString(string: text, attributes: self)
  205. }
  206. return NSMutableAttributedString(string: text, mutableAttributes: self)
  207. }
  208. }
  209. extension UIView {
  210. func mapEverySubview(predicate: (UIView) -> Void) {
  211. predicate(self)
  212. for subview in self.subviews {
  213. subview.mapEverySubview(predicate: predicate)
  214. }
  215. }
  216. }
  217. private extension UIView {
  218. var visualEffectView: UIVisualEffectView? {
  219. if self is UIVisualEffectView {
  220. return self as? UIVisualEffectView
  221. }
  222. for subview in self.subviews {
  223. if let validView = subview.visualEffectView {
  224. return validView
  225. }
  226. }
  227. return nil
  228. }
  229. }
  230. private extension UIAlertController {
  231. private var attributedTitle_: NSAttributedString? {
  232. get {
  233. return self.value(forKey: "attributedTitle") as? NSAttributedString
  234. } set {
  235. self.setValue(newValue, forKey: "attributedTitle")
  236. }
  237. }
  238. private var attributedMessage_: NSAttributedString? {
  239. get {
  240. return self.value(forKey: "attributedMessage") as? NSAttributedString
  241. } set {
  242. self.setValue(newValue, forKey: "attributedMessage")
  243. }
  244. }
  245. }
  246. private extension UIAlertAction {
  247. // idea from: https://medium.com/@maximbilan/ios-uialertcontroller-customization-5cfd88140db8
  248. private var image_: UIImage? {
  249. get {
  250. return self.value(forKey: "image") as? UIImage
  251. } set {
  252. let imageWithGoodDimensions = NCUtility.sharedInstance.resizeImage(image: newValue!, newWidth: 30)
  253. self.setValue(imageWithGoodDimensions.withRenderingMode(.alwaysOriginal), forKey: "image")
  254. }
  255. }
  256. private var contentViewController_: UIViewController? {
  257. get {
  258. return self.value(forKey: "contentViewController") as? UIViewController
  259. } set {
  260. self.setValue(newValue, forKey: "contentViewController")
  261. }
  262. }
  263. private var titleTextColor_: UIColor? {
  264. get {
  265. return self.value(forKey: "titleTextColor") as? UIColor
  266. } set {
  267. self.setValue(newValue, forKey: "titleTextColor")
  268. }
  269. }
  270. }