Auto.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. //
  2. // Auto.swift
  3. // ┌─┐ ┌───────┐ ┌───────┐
  4. // │ │ │ ┌─────┘ │ ┌─────┘
  5. // │ │ │ └─────┐ │ └─────┐
  6. // │ │ │ ┌─────┘ │ ┌─────┘
  7. // │ └─────┐│ └─────┐ │ └─────┐
  8. // └───────┘└───────┘ └───────┘
  9. //
  10. // Created by lee on 2018/11/2.
  11. // Copyright © 2018年 lee. All rights reserved.
  12. //
  13. #if canImport(Foundation)
  14. import Foundation
  15. #if os(iOS)
  16. import UIKit
  17. public enum Auto {
  18. /// 设置转换闭包
  19. ///
  20. /// - Parameter conversion: 转换闭包
  21. public static func set(conversion: @escaping ((Double) -> Double)) {
  22. conversionClosure = conversion
  23. }
  24. /// 转换 用于数值的等比例计算 如需自定义可重新设置
  25. static var conversionClosure: ((Double) -> Double) = { (origin) in
  26. guard UIDevice.current.userInterfaceIdiom == .phone else {
  27. return origin
  28. }
  29. let base = 375.0
  30. let screenWidth = Double(UIScreen.main.bounds.width)
  31. let screenHeight = Double(UIScreen.main.bounds.height)
  32. let width = min(screenWidth, screenHeight)
  33. let result = origin * (width / base)
  34. let scale = Double(UIScreen.main.scale)
  35. return (result * scale).rounded(.up) / scale
  36. }
  37. }
  38. extension Auto {
  39. static func conversion(_ value: Double) -> Double {
  40. return conversionClosure(value)
  41. }
  42. }
  43. protocol AutoCalculationable {
  44. /// 自动计算
  45. ///
  46. /// - Returns: 结果
  47. func auto() -> Self
  48. }
  49. extension Double: AutoCalculationable {
  50. func auto() -> Double {
  51. return Auto.conversion(self)
  52. }
  53. }
  54. extension BinaryInteger {
  55. public func auto() -> Double {
  56. let temp = Double("\(self)") ?? 0
  57. return temp.auto()
  58. }
  59. public func auto<T>() -> T where T : BinaryInteger {
  60. let temp = Double("\(self)") ?? 0
  61. return temp.auto()
  62. }
  63. public func auto<T>() -> T where T : BinaryFloatingPoint {
  64. let temp = Double("\(self)") ?? 0
  65. return temp.auto()
  66. }
  67. }
  68. extension BinaryFloatingPoint {
  69. public func auto() -> Double {
  70. let temp = Double("\(self)") ?? 0
  71. return temp.auto()
  72. }
  73. public func auto<T>() -> T where T : BinaryInteger {
  74. let temp = Double("\(self)") ?? 0
  75. return T(temp.auto())
  76. }
  77. public func auto<T>() -> T where T : BinaryFloatingPoint {
  78. let temp = Double("\(self)") ?? 0
  79. return T(temp.auto())
  80. }
  81. }
  82. extension CGPoint: AutoCalculationable {
  83. public func auto() -> CGPoint {
  84. return CGPoint(x: x.auto(), y: y.auto())
  85. }
  86. }
  87. extension CGSize: AutoCalculationable {
  88. public func auto() -> CGSize {
  89. return CGSize(width: width.auto(), height: height.auto())
  90. }
  91. }
  92. extension CGRect: AutoCalculationable {
  93. public func auto() -> CGRect {
  94. return CGRect(origin: origin.auto(), size: size.auto())
  95. }
  96. }
  97. extension CGVector: AutoCalculationable {
  98. public func auto() -> CGVector {
  99. return CGVector(dx: dx.auto(), dy: dy.auto())
  100. }
  101. }
  102. extension UIOffset: AutoCalculationable {
  103. public func auto() -> UIOffset {
  104. return UIOffset(horizontal: horizontal.auto(), vertical: vertical.auto())
  105. }
  106. }
  107. extension UIEdgeInsets: AutoCalculationable {
  108. public func auto() -> UIEdgeInsets {
  109. return UIEdgeInsets(
  110. top: top.auto(),
  111. left: left.auto(),
  112. bottom: bottom.auto(),
  113. right: right.auto()
  114. )
  115. }
  116. }
  117. extension NSLayoutConstraint {
  118. @IBInspectable private var autoConstant: Bool {
  119. get { return false }
  120. set {
  121. guard newValue else { return }
  122. constant = constant.auto()
  123. }
  124. }
  125. }
  126. extension UIView {
  127. @IBInspectable private var autoCornerRadius: CGFloat {
  128. get { return layer.cornerRadius }
  129. set {
  130. let value: CGFloat = newValue.auto()
  131. layer.masksToBounds = true
  132. layer.cornerRadius = abs(CGFloat(Int(value * 100)) / 100)
  133. }
  134. }
  135. }
  136. extension UILabel {
  137. @IBInspectable private var autoFont: Bool {
  138. get { return false }
  139. set {
  140. guard newValue else { return }
  141. guard let text = attributedText?.mutableCopy() as? NSMutableAttributedString else {
  142. return
  143. }
  144. font = font.withSize(font.pointSize.auto())
  145. attributedText = text.reset(font: { $0.auto() })
  146. }
  147. }
  148. @IBInspectable private var autoLine: Bool {
  149. get { return false }
  150. set {
  151. guard newValue else { return }
  152. guard let text = attributedText else { return }
  153. attributedText = text.reset(line: { $0.auto() })
  154. }
  155. }
  156. @IBInspectable private var autoShadowOffset: Bool {
  157. get { return false }
  158. set {
  159. guard newValue else { return }
  160. shadowOffset = shadowOffset.auto()
  161. }
  162. }
  163. }
  164. extension UITextView {
  165. @IBInspectable private var autoFont: Bool {
  166. get { return false }
  167. set {
  168. guard newValue else { return }
  169. guard let font = font else { return }
  170. self.font = font.withSize(font.pointSize.auto())
  171. }
  172. }
  173. }
  174. extension UITextField {
  175. @IBInspectable private var autoFont: Bool {
  176. get { return false }
  177. set {
  178. guard newValue else { return }
  179. guard let font = font else { return }
  180. self.font = font.withSize(font.pointSize.auto())
  181. }
  182. }
  183. }
  184. extension UIImageView {
  185. @IBInspectable private var autoImage: Bool {
  186. get { return false }
  187. set {
  188. guard newValue else { return }
  189. if let width = image?.size.width {
  190. image = image?.scaled(to: width.auto())
  191. }
  192. if let width = highlightedImage?.size.width {
  193. highlightedImage = highlightedImage?.scaled(to: width.auto())
  194. }
  195. }
  196. }
  197. }
  198. extension UIButton {
  199. @IBInspectable private var autoTitle: Bool {
  200. get { return false }
  201. set {
  202. guard newValue else { return }
  203. let states: [UIControl.State] = [
  204. .normal,
  205. .highlighted,
  206. .selected,
  207. .disabled
  208. ]
  209. if
  210. let _ = title(for: state),
  211. let label = titleLabel,
  212. let font = label.font {
  213. label.font = font.withSize(font.pointSize.auto())
  214. }
  215. let titles = states.enumerated().compactMap {
  216. (i, state) -> (Int, NSAttributedString)? in
  217. guard let t = attributedTitle(for: state) else { return nil }
  218. return (i, t)
  219. }
  220. titles.filtered(duplication: { $0.1 }).forEach {
  221. setAttributedTitle(
  222. $0.1.reset(font: { $0.auto() }),
  223. for: states[$0.0]
  224. )
  225. }
  226. }
  227. }
  228. @IBInspectable private var autoImage: Bool {
  229. get { return false }
  230. set {
  231. guard newValue else { return }
  232. let states: [UIControl.State] = [
  233. .normal,
  234. .highlighted,
  235. .selected,
  236. .disabled
  237. ]
  238. let images = states.enumerated().compactMap {
  239. (i, state) -> (Int, UIImage)? in
  240. guard let v = image(for: state) else { return nil }
  241. return (i, v)
  242. }
  243. images.filtered(duplication: { $0.1 }).forEach {
  244. setImage(
  245. $0.1.scaled(to: $0.1.size.width.auto()),
  246. for: states[$0.0]
  247. )
  248. }
  249. let backgrounds = states.enumerated().compactMap {
  250. (i, state) -> (Int, UIImage)? in
  251. guard let v = backgroundImage(for: state) else { return nil }
  252. return (i, v)
  253. }
  254. backgrounds.filtered(duplication: { $0.1 }).forEach {
  255. setBackgroundImage(
  256. $0.1.scaled(to: $0.1.size.width.auto()),
  257. for: states[$0.0]
  258. )
  259. }
  260. }
  261. }
  262. @IBInspectable private var autoTitleInsets: Bool {
  263. get { return false }
  264. set {
  265. guard newValue else { return }
  266. titleEdgeInsets = titleEdgeInsets.auto()
  267. }
  268. }
  269. @IBInspectable private var autoImageInsets: Bool {
  270. get { return false }
  271. set {
  272. guard newValue else { return }
  273. imageEdgeInsets = imageEdgeInsets.auto()
  274. }
  275. }
  276. @IBInspectable private var autoContentInsets: Bool {
  277. get { return false }
  278. set {
  279. guard newValue else { return }
  280. contentEdgeInsets = contentEdgeInsets.auto()
  281. }
  282. }
  283. }
  284. @available(iOS 9.0, *)
  285. extension UIStackView {
  286. @IBInspectable private var autoSpacing: Bool {
  287. get { return false }
  288. set {
  289. guard newValue else { return }
  290. spacing = spacing.auto()
  291. }
  292. }
  293. }
  294. fileprivate extension NSAttributedString {
  295. func reset(font size: (CGFloat) -> CGFloat) -> NSAttributedString {
  296. let string = NSMutableAttributedString(attributedString: self)
  297. enumerateAttributes(
  298. in: NSRange(location: 0, length: length),
  299. options: .longestEffectiveRangeNotRequired
  300. ) { (attributes, range, stop) in
  301. var temp = attributes
  302. if let font = attributes[.font] as? UIFont {
  303. temp[.font] = font.withSize(size(font.pointSize))
  304. }
  305. string.setAttributes(temp, range: range)
  306. }
  307. return string
  308. }
  309. func reset(line spacing: (CGFloat) -> CGFloat) -> NSAttributedString {
  310. let string = NSMutableAttributedString(attributedString: self)
  311. enumerateAttributes(
  312. in: NSRange(location: 0, length: length),
  313. options: .longestEffectiveRangeNotRequired
  314. ) { (attributes, range, stop) in
  315. var temp = attributes
  316. if let paragraph = attributes[.paragraphStyle] as? NSMutableParagraphStyle {
  317. paragraph.lineSpacing = spacing(paragraph.lineSpacing)
  318. temp[.paragraphStyle] = paragraph
  319. }
  320. string.setAttributes(temp, range: range)
  321. }
  322. return string
  323. }
  324. }
  325. fileprivate extension UIImage {
  326. func scaled(to width: CGFloat, opaque: Bool = false) -> UIImage? {
  327. guard self.size.width > 0 else {
  328. return nil
  329. }
  330. let scale = width / self.size.width
  331. let size = CGSize(width: width, height: self.size.height * scale)
  332. UIGraphicsBeginImageContextWithOptions(size, false, 0)
  333. draw(in: CGRect(origin: .zero, size: size))
  334. let new = UIGraphicsGetImageFromCurrentImageContext()
  335. UIGraphicsEndImageContext()
  336. return new
  337. }
  338. }
  339. fileprivate extension Array {
  340. func filtered<E: Equatable>(duplication closure: (Element) throws -> E) rethrows -> [Element] {
  341. return try reduce(into: [Element]()) { (result, e) in
  342. let contains = try result.contains { try closure($0) == closure(e) }
  343. result += contains ? [] : [e]
  344. }
  345. }
  346. }
  347. public extension Double {
  348. func rounded(_ decimalPlaces: Int) -> Double {
  349. let divisor = pow(10.0, Double(max(0, decimalPlaces)))
  350. return (self * divisor).rounded() / divisor
  351. }
  352. }
  353. public extension BinaryFloatingPoint {
  354. func rounded(_ decimalPlaces: Int) -> Double {
  355. let temp = Double("\(self)") ?? 0
  356. return temp.rounded(decimalPlaces)
  357. }
  358. func rounded<T>(_ decimalPlaces: Int) -> T where T: BinaryFloatingPoint {
  359. let temp = Double("\(self)") ?? 0
  360. return T(temp.rounded(decimalPlaces))
  361. }
  362. }
  363. public extension CGPoint {
  364. func rounded(_ decimalPlaces: Int) -> CGPoint {
  365. return CGPoint(x: x.rounded(decimalPlaces), y: y.rounded(decimalPlaces))
  366. }
  367. }
  368. public extension CGSize {
  369. func rounded(_ decimalPlaces: Int) -> CGSize {
  370. return CGSize(width: width.rounded(decimalPlaces), height: height.rounded(decimalPlaces))
  371. }
  372. }
  373. public extension CGRect {
  374. func rounded(_ decimalPlaces: Int) -> CGRect {
  375. return CGRect(origin: origin.rounded(decimalPlaces), size: size.rounded(decimalPlaces))
  376. }
  377. }
  378. public extension UIEdgeInsets {
  379. func rounded(_ decimalPlaces: Int) -> UIEdgeInsets {
  380. return UIEdgeInsets(
  381. top: top.rounded(decimalPlaces),
  382. left: left.rounded(decimalPlaces),
  383. bottom: bottom.rounded(decimalPlaces),
  384. right: right.rounded(decimalPlaces)
  385. )
  386. }
  387. }
  388. #endif
  389. #endif