// // NCCustomizableAlertController.swift // Nextcloud // // Created by Marino Faggiana on 30/09/19. // Copyright © 2018 Marino Faggiana. All rights reserved. // // Author Marino Faggiana // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // Medium article // // https://medium.com/@Daniel_illescas/hacking-ios-alerts-with-swift-61aefce9736a import UIKit final class DarkAlertController: NCCustomizableAlertController { override func viewDidLoad() { super.viewDidLoad() self.visualEffectView?.effect = UIBlurEffect(style: .dark) self.tintColor = UIColor(red: 0.4, green: 0.5, blue: 1.0, alpha: 1.0) self.contentView?.backgroundColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 0.7) let whiteStringAttribute = StringAttribute(key: .foregroundColor, value: UIColor.white) self.titleAttributes = [whiteStringAttribute] self.messageAttributes = [whiteStringAttribute] } } // open class NCCustomizableAlertController: UIAlertController { open lazy var visualEffectView: UIVisualEffectView? = { return self.view.visualEffectView }() open lazy var lazyContentView: UIView? = { return self.contentView }() open lazy var tintColor: UIColor? = { return self.view.tintColor }() override open func viewWillAppear(_ animated: Bool) { super.viewDidAppear(animated) self.view.tintColor = self.tintColor } func addParallaxEffect(x: Int = 20, y: Int = 20) { let horizontal = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.x", type: .tiltAlongHorizontalAxis) horizontal.minimumRelativeValue = -x horizontal.maximumRelativeValue = x let vertical = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.y", type: .tiltAlongVerticalAxis) vertical.minimumRelativeValue = -y vertical.maximumRelativeValue = y let motionEffectsGroup = UIMotionEffectGroup() motionEffectsGroup.motionEffects = [horizontal, vertical] self.view.addMotionEffect(motionEffectsGroup) } } // extension UIAlertController { private var visualEffectView: UIVisualEffectView? { return self.view.visualEffectView } var contentView: UIView? { return self.view.subviews.first?.subviews.first?.subviews.first } var titleAttributes: [StringAttribute] { get { return self.attributedTitle_?.attributes ?? [] } set { self.attributedTitle_ = newValue.suitableAttributedText(forText: self.title) } } var messageAttributes: [StringAttribute] { get { return self.attributedMessage_?.attributes ?? [] } set { self.attributedMessage_ = newValue.suitableAttributedText(forText: self.message) } } } extension UIAlertAction { var label: UILabel? { return (self.value(forKey: "__representer") as? NSObject)?.value(forKey: "label") as? UILabel } var titleAttributes: [StringAttribute] { get { return self.label?.attributedText?.attributes ?? [] } set { self.label?.textAttributes = newValue } } var titleTextColor: UIColor? { get { return self.titleTextColor_ } set { self.titleTextColor_ = newValue } } var accessoryImage: UIImage? { get { return self.image_ } set { self.image_ = newValue } } var contentElementViewController: ElementViewController? { get { return self.contentViewController_ as? ElementViewController } set { if accessoryImage != nil { print("The accessory image might overlap with the content of the contentViewController") } self.contentViewController_ = newValue } } var contentViewController: UIViewController? { get { return self.contentViewController_ } set { if accessoryImage != nil { print("The accessory image might overlap with the content of the contentViewController") } self.contentViewController_ = newValue } } var tableViewController: UITableViewController? { get { return self.contentViewController_ as? UITableViewController } set { if accessoryImage != nil { print("The accessory image might overlap with the content of the contentViewController") } self.contentViewController_ = newValue } } var accessoryView: UIView? { get { return contentElementViewController?.elementView } set { let elementViewController = ElementViewController() elementViewController.elementView = newValue self.contentViewController = elementViewController } } } extension UILabel { var textAttributes: [StringAttribute] { get { return self.attributedText?.attributes ?? [] } set { self.attributedText = newValue.suitableAttributedText(forText: self.text) } } } extension NSAttributedString { struct StringAttribute { let key: NSAttributedString.Key let value: Any var range: NSRange? = nil init(key: NSAttributedString.Key, value: Any, range: NSRange? = nil) { self.key = key self.value = value self.range = range } } var attributes: [StringAttribute] { var savedAttributes: [StringAttribute] = [] let rawAttributes = self.attributes(at: 0, longestEffectiveRange: nil, in: NSRange(location: 0, length: self.length)) for rawAttribute in rawAttributes { savedAttributes.append(StringAttribute(key: rawAttribute.key, value: rawAttribute.value)) } return savedAttributes } convenience init(string: String?, attributes: [StringAttribute]) { guard let validString = string else { self.init(string: "", attributes: [:]); return } var attributesDict: [NSAttributedString.Key: Any] = [:] for attribute in attributes { attributesDict[attribute.key] = attribute.value } self.init(string: validString, attributes: attributesDict) } } extension NSMutableAttributedString { var mutableAttributes: [StringAttribute] { get { return self.attributes } set { let defaultRange = NSRange(location: 0, length: self.length) for attribute in newValue { self.addAttribute(attribute.key, value: attribute.value, range: attribute.range ?? defaultRange) } } } convenience init(string: String?, mutableAttributes: [StringAttribute]) { guard let text = string else { self.init(string: ""); return } self.init(string: text) self.mutableAttributes = mutableAttributes } } class ElementViewController: UIViewController { var elementView: UIView? = nil override func viewDidLoad() { super.viewDidLoad() if let validView = self.elementView { self.view.addSubview(validView) validView.translatesAutoresizingMaskIntoConstraints = false let margins = self.view.layoutMarginsGuide validView.centerYAnchor.constraint(equalTo: margins.topAnchor, constant: validView.frame.height).isActive = true validView.centerXAnchor.constraint(equalTo: margins.centerXAnchor).isActive = true validView.widthAnchor.constraint(equalTo: margins.widthAnchor).isActive = true validView.heightAnchor.constraint(equalTo: margins.heightAnchor).isActive = true } } } // typealias StringAttribute = NSAttributedString.StringAttribute extension Array where Element == StringAttribute { func suitableAttributedText(forText text: String?) -> NSAttributedString { if self.compactMap({ $0.range }).isEmpty { return NSAttributedString(string: text, attributes: self) } return NSMutableAttributedString(string: text, mutableAttributes: self) } } extension UIView { func mapEverySubview(predicate: (UIView) -> Void) { predicate(self) for subview in self.subviews { subview.mapEverySubview(predicate: predicate) } } } private extension UIView { var visualEffectView: UIVisualEffectView? { if self is UIVisualEffectView { return self as? UIVisualEffectView } for subview in self.subviews { if let validView = subview.visualEffectView { return validView } } return nil } } private extension UIAlertController { private var attributedTitle_: NSAttributedString? { get { return self.value(forKey: "attributedTitle") as? NSAttributedString } set { self.setValue(newValue, forKey: "attributedTitle") } } private var attributedMessage_: NSAttributedString? { get { return self.value(forKey: "attributedMessage") as? NSAttributedString } set { self.setValue(newValue, forKey: "attributedMessage") } } } private extension UIAlertAction { // idea from: https://medium.com/@maximbilan/ios-uialertcontroller-customization-5cfd88140db8 private var image_: UIImage? { get { return self.value(forKey: "image") as? UIImage } set { let imageWithGoodDimensions = NCUtility.sharedInstance.resizeImage(image: newValue!, newWidth: 30) self.setValue(imageWithGoodDimensions.withRenderingMode(.alwaysOriginal), forKey: "image") } } private var contentViewController_: UIViewController? { get { return self.value(forKey: "contentViewController") as? UIViewController } set { self.setValue(newValue, forKey: "contentViewController") } } private var titleTextColor_: UIColor? { get { return self.value(forKey: "titleTextColor") as? UIColor } set { self.setValue(newValue, forKey: "titleTextColor") } } }