MultiSliderExtensions.swift 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. //
  2. // MultiSliderExtensions.swift
  3. // MultiSlider
  4. //
  5. // Created by Yonat Sharon on 20.05.2018.
  6. //
  7. import SweeterSwift
  8. import UIKit
  9. extension MultiSlider.Snap {
  10. func snap(value: CGFloat) -> CGFloat {
  11. switch self {
  12. case .never:
  13. return value
  14. case let .stepSize(stepSize):
  15. guard stepSize.isNormal && value.isNormal else { return value }
  16. return (value / stepSize).rounded() * stepSize
  17. case let .values(values):
  18. return values.closest(to: value)
  19. }
  20. }
  21. }
  22. extension Array where Element: SignedNumeric & Comparable {
  23. func closest(to number: Element) -> Element {
  24. guard !isEmpty else { return number }
  25. return self.min { abs($0 - number) < abs($1 - number) }!
  26. }
  27. }
  28. extension Array where Element: SignedNumeric & Comparable & Hashable {
  29. /// Distribute new `count` values evenly among `allowedValues`, skipping sender's values if possible.
  30. func distributedNewValues(count newValuesCount: Int, allowedValues: Self = []) -> Self {
  31. guard allowedValues.count > 1 else { return self }
  32. guard newValuesCount > 0 else { return [] }
  33. var ret: Self = []
  34. var availableSpots = Set(allowedValues).subtracting(self).sorted()
  35. var needingSpotsCount = newValuesCount
  36. while availableSpots.count <= needingSpotsCount { // fill all spots
  37. ret += availableSpots
  38. needingSpotsCount -= availableSpots.count
  39. availableSpots = allowedValues
  40. }
  41. if needingSpotsCount > 1 { // distribute evenly over spotsToFill
  42. let spotsToSkip = Double(availableSpots.count - 1) / Double(needingSpotsCount - 1)
  43. for i in 0 ..< needingSpotsCount {
  44. let spotIndex = (Double(i) * spotsToSkip).rounded()
  45. ret.append(availableSpots[Int(spotIndex)])
  46. }
  47. } else if needingSpotsCount == 1 {
  48. let spotIndex = availableSpots.count / 2
  49. ret.append(availableSpots[spotIndex])
  50. }
  51. return ret.sorted()
  52. }
  53. }
  54. extension Array where Element: FloatingPoint {
  55. /// Distribute new `count` values evenly between sender's values and `max`.
  56. func distributedNewValues(count newValuesCount: Int, min: Element, max: Element) -> Self {
  57. guard newValuesCount > 0, min < max else { return [] }
  58. let step: Element
  59. if let last = last {
  60. step = (max - last) / Element(newValuesCount)
  61. } else {
  62. if newValuesCount == 1 {
  63. return [(max + min) / 2]
  64. }
  65. step = (max - min) / Element(newValuesCount - 1)
  66. }
  67. return sequence(first: max, next: { $0 - step })
  68. .prefix(newValuesCount)
  69. .reversed()
  70. }
  71. }
  72. extension CGPoint {
  73. func coordinate(in axis: NSLayoutConstraint.Axis) -> CGFloat {
  74. switch axis {
  75. case .vertical:
  76. return y
  77. default:
  78. return x
  79. }
  80. }
  81. }
  82. extension CGRect {
  83. func size(in axis: NSLayoutConstraint.Axis) -> CGFloat {
  84. switch axis {
  85. case .vertical:
  86. return height
  87. default:
  88. return width
  89. }
  90. }
  91. func bottom(in axis: NSLayoutConstraint.Axis) -> CGFloat {
  92. switch axis {
  93. case .vertical:
  94. return maxY
  95. default:
  96. return minX
  97. }
  98. }
  99. func top(in axis: NSLayoutConstraint.Axis) -> CGFloat {
  100. switch axis {
  101. case .vertical:
  102. return minY
  103. default:
  104. return maxX
  105. }
  106. }
  107. }
  108. extension UIView {
  109. var diagonalSize: CGFloat { return hypot(frame.width, frame.height) }
  110. func removeFirstConstraint(where: (_: NSLayoutConstraint) -> Bool) {
  111. if let constrainIndex = constraints.firstIndex(where: `where`) {
  112. removeConstraint(constraints[constrainIndex])
  113. }
  114. }
  115. func addShadow() {
  116. layer.shadowColor = UIColor.gray.cgColor
  117. layer.shadowOpacity = 0.25
  118. layer.shadowOffset = CGSize(width: 0, height: 4)
  119. layer.shadowRadius = 0.5
  120. }
  121. }
  122. extension Array where Element: UIView {
  123. mutating func removeAllViews() {
  124. forEach { $0.removeFromSuperview() }
  125. removeAll()
  126. }
  127. }
  128. extension UIImageView {
  129. func applyTint(color: UIColor?) {
  130. image = image?.withRenderingMode(nil == color ? .alwaysOriginal : .alwaysTemplate)
  131. tintColor = color
  132. }
  133. func blur(_ on: Bool) {
  134. if on {
  135. guard nil == viewWithTag(UIImageView.blurViewTag) else { return }
  136. let blurImage = image?.withRenderingMode(.alwaysTemplate)
  137. let blurView = UIImageView(image: blurImage)
  138. blurView.tag = UIImageView.blurViewTag
  139. blurView.tintColor = .white
  140. blurView.alpha = 0.5
  141. addConstrainedSubview(blurView, constrain: .top, .bottom, .left, .right)
  142. layer.shadowOpacity /= 2
  143. } else {
  144. guard let blurView = viewWithTag(UIImageView.blurViewTag) else { return }
  145. blurView.removeFromSuperview()
  146. layer.shadowOpacity *= 2
  147. }
  148. }
  149. static var blurViewTag: Int { return 898_989 } // swiftlint:disable:this numbers_smell
  150. }
  151. extension NSLayoutConstraint.Attribute {
  152. var opposite: NSLayoutConstraint.Attribute {
  153. switch self {
  154. case .left: return .right
  155. case .right: return .left
  156. case .top: return .bottom
  157. case .bottom: return .top
  158. case .leading: return .trailing
  159. case .trailing: return .leading
  160. case .leftMargin: return .rightMargin
  161. case .rightMargin: return .leftMargin
  162. case .topMargin: return .bottomMargin
  163. case .bottomMargin: return .topMargin
  164. case .leadingMargin: return .trailingMargin
  165. case .trailingMargin: return .leadingMargin
  166. default: return self
  167. }
  168. }
  169. var inwardSign: CGFloat {
  170. switch self {
  171. case .top, .topMargin: return 1
  172. case .bottom, .bottomMargin: return -1
  173. case .left, .leading, .leftMargin, .leadingMargin: return 1
  174. case .right, .trailing, .rightMargin, .trailingMargin: return -1
  175. default: return 1
  176. }
  177. }
  178. var perpendicularCenter: NSLayoutConstraint.Attribute {
  179. switch self {
  180. case .left, .leading, .leftMargin, .leadingMargin, .right, .trailing, .rightMargin, .trailingMargin, .centerX:
  181. return .centerY
  182. default:
  183. return .centerX
  184. }
  185. }
  186. static func center(in axis: NSLayoutConstraint.Axis) -> NSLayoutConstraint.Attribute {
  187. switch axis {
  188. case .vertical:
  189. return .centerY
  190. default:
  191. return .centerX
  192. }
  193. }
  194. static func top(in axis: NSLayoutConstraint.Axis) -> NSLayoutConstraint.Attribute {
  195. switch axis {
  196. case .vertical:
  197. return .top
  198. default:
  199. return .right
  200. }
  201. }
  202. static func bottom(in axis: NSLayoutConstraint.Axis) -> NSLayoutConstraint.Attribute {
  203. switch axis {
  204. case .vertical:
  205. return .bottom
  206. default:
  207. return .left
  208. }
  209. }
  210. }
  211. extension CACornerMask {
  212. static func direction(_ attribute: NSLayoutConstraint.Attribute) -> CACornerMask {
  213. switch attribute {
  214. case .bottom:
  215. return [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
  216. case .top:
  217. return [.layerMinXMinYCorner, .layerMaxXMinYCorner]
  218. case .leading, .left:
  219. return [.layerMinXMinYCorner, .layerMinXMaxYCorner]
  220. case .trailing, .right:
  221. return [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  222. default:
  223. return []
  224. }
  225. }
  226. }
  227. extension UIImage {
  228. static func circle(diameter: CGFloat = 29, width: CGFloat = 0.5, color: UIColor? = UIColor.lightGray.withAlphaComponent(0.5), fill: UIColor? = .white) -> UIImage? {
  229. let circleLayer = CAShapeLayer()
  230. circleLayer.fillColor = fill?.cgColor
  231. circleLayer.strokeColor = color?.cgColor
  232. circleLayer.lineWidth = width
  233. let margin = width * 2
  234. let circle = UIBezierPath(ovalIn: CGRect(x: margin, y: margin, width: diameter, height: diameter))
  235. circleLayer.bounds = CGRect(x: 0, y: 0, width: diameter + margin * 2, height: diameter + margin * 2)
  236. circleLayer.path = circle.cgPath
  237. UIGraphicsBeginImageContextWithOptions(circleLayer.bounds.size, false, 0)
  238. guard let context = UIGraphicsGetCurrentContext() else { return nil }
  239. circleLayer.render(in: context)
  240. let image = UIGraphicsGetImageFromCurrentImageContext()
  241. UIGraphicsEndImageContext()
  242. return image
  243. }
  244. }
  245. extension NSObject {
  246. func addObserverForAllProperties(
  247. observer: NSObject,
  248. options: NSKeyValueObservingOptions = [],
  249. context: UnsafeMutableRawPointer? = nil
  250. ) {
  251. performForAllKeyPaths { keyPath in
  252. addObserver(observer, forKeyPath: keyPath, options: options, context: context)
  253. }
  254. }
  255. func removeObserverForAllProperties(
  256. observer: NSObject,
  257. context: UnsafeMutableRawPointer? = nil
  258. ) {
  259. performForAllKeyPaths { keyPath in
  260. removeObserver(observer, forKeyPath: keyPath, context: context)
  261. }
  262. }
  263. func performForAllKeyPaths(_ action: (String) -> Void) {
  264. var count: UInt32 = 0
  265. guard let properties = class_copyPropertyList(object_getClass(self), &count) else { return }
  266. defer { free(properties) }
  267. for i in 0 ..< Int(count) {
  268. let keyPath = String(cString: property_getName(properties[i]))
  269. action(keyPath)
  270. }
  271. }
  272. }