UIButton+Haneke.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. //
  2. // UIButton+Haneke.swift
  3. // Haneke
  4. //
  5. // Created by Joan Romano on 10/1/14.
  6. // Copyright (c) 2014 Haneke. All rights reserved.
  7. //
  8. import UIKit
  9. public extension UIButton {
  10. public var hnk_imageFormat : Format<UIImage> {
  11. let bounds = self.bounds
  12. assert(bounds.size.width > 0 && bounds.size.height > 0, "[\(Mirror(reflecting: self).description) \(#function)]: UIButton size is zero. Set its frame, call sizeToFit or force layout first. You can also set a custom format with a defined size if you don't want to force layout.")
  13. let contentRect = self.contentRect(forBounds: bounds)
  14. let imageInsets = self.imageEdgeInsets
  15. let scaleMode = self.contentHorizontalAlignment != UIControl.ContentHorizontalAlignment.fill || self.contentVerticalAlignment != UIControl.ContentVerticalAlignment.fill ? ImageResizer.ScaleMode.AspectFit : ImageResizer.ScaleMode.Fill
  16. let imageSize = CGSize(width: contentRect.width - imageInsets.left - imageInsets.right, height: contentRect.height - imageInsets.top - imageInsets.bottom)
  17. return HanekeGlobals.UIKit.formatWithSize(imageSize, scaleMode: scaleMode, allowUpscaling: scaleMode == ImageResizer.ScaleMode.AspectFit ? false : true)
  18. }
  19. public func hnk_setImageFromURL(_ URL: Foundation.URL, state: UIControl.State = .normal, placeholder: UIImage? = nil, format: Format<UIImage>? = nil, failure fail: ((Error?) -> ())? = nil, success succeed: ((UIImage) -> ())? = nil) {
  20. let fetcher = NetworkFetcher<UIImage>(URL: URL)
  21. self.hnk_setImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, failure: fail, success: succeed)
  22. }
  23. public func hnk_setImage(_ image: UIImage, key: String, state: UIControl.State = .normal, placeholder: UIImage? = nil, format: Format<UIImage>? = nil, success succeed: ((UIImage) -> ())? = nil) {
  24. let fetcher = SimpleFetcher<UIImage>(key: key, value: image)
  25. self.hnk_setImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, success: succeed)
  26. }
  27. public func hnk_setImageFromFile(_ path: String, state: UIControl.State = .normal, placeholder: UIImage? = nil, format: Format<UIImage>? = nil, failure fail: ((Error?) -> ())? = nil, success succeed: ((UIImage) -> ())? = nil) {
  28. let fetcher = DiskFetcher<UIImage>(path: path)
  29. self.hnk_setImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, failure: fail, success: succeed)
  30. }
  31. public func hnk_setImageFromFetcher(_ fetcher: Fetcher<UIImage>, state: UIControl.State = .normal, placeholder: UIImage? = nil, format: Format<UIImage>? = nil, failure fail: ((Error?) -> ())? = nil, success succeed: ((UIImage) -> ())? = nil){
  32. self.hnk_cancelSetImage()
  33. self.hnk_imageFetcher = fetcher
  34. let didSetImage = self.hnk_fetchImageForFetcher(fetcher, state: state, format : format, failure: fail, success: succeed)
  35. if didSetImage { return }
  36. if let placeholder = placeholder {
  37. self.setImage(placeholder, for: state)
  38. }
  39. }
  40. public func hnk_cancelSetImage() {
  41. if let fetcher = self.hnk_imageFetcher {
  42. fetcher.cancelFetch()
  43. self.hnk_imageFetcher = nil
  44. }
  45. }
  46. // MARK: Internal Image
  47. // See: http://stackoverflow.com/questions/25907421/associating-swift-things-with-nsobject-instances
  48. var hnk_imageFetcher : Fetcher<UIImage>! {
  49. get {
  50. let wrapper = objc_getAssociatedObject(self, &HanekeGlobals.UIKit.SetImageFetcherKey) as? ObjectWrapper
  51. let fetcher = wrapper?.hnk_value as? Fetcher<UIImage>
  52. return fetcher
  53. }
  54. set (fetcher) {
  55. var wrapper : ObjectWrapper?
  56. if let fetcher = fetcher {
  57. wrapper = ObjectWrapper(value: fetcher)
  58. }
  59. objc_setAssociatedObject(self, &HanekeGlobals.UIKit.SetImageFetcherKey, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  60. }
  61. }
  62. func hnk_fetchImageForFetcher(_ fetcher : Fetcher<UIImage>, state : UIControl.State = .normal, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())?, success succeed : ((UIImage) -> ())?) -> Bool {
  63. let format = format ?? self.hnk_imageFormat
  64. let cache = Shared.imageCache
  65. if cache.formats[format.name] == nil {
  66. cache.addFormat(format)
  67. }
  68. var animated = false
  69. let fetch = cache.fetch(fetcher: fetcher, formatName: format.name, failure: {[weak self] error in
  70. if let strongSelf = self {
  71. if strongSelf.hnk_shouldCancelImageForKey(fetcher.key) { return }
  72. strongSelf.hnk_imageFetcher = nil
  73. fail?(error)
  74. }
  75. }) { [weak self] image in
  76. if let strongSelf = self {
  77. if strongSelf.hnk_shouldCancelImageForKey(fetcher.key) { return }
  78. strongSelf.hnk_setImage(image, state: state, animated: animated, success: succeed)
  79. }
  80. }
  81. animated = true
  82. return fetch.hasSucceeded
  83. }
  84. func hnk_setImage(_ image : UIImage, state : UIControl.State, animated : Bool, success succeed : ((UIImage) -> ())?) {
  85. self.hnk_imageFetcher = nil
  86. if let succeed = succeed {
  87. succeed(image)
  88. } else if animated {
  89. UIView.transition(with: self, duration: HanekeGlobals.UIKit.SetImageAnimationDuration, options: .transitionCrossDissolve, animations: {
  90. self.setImage(image, for: state)
  91. }, completion: nil)
  92. } else {
  93. self.setImage(image, for: state)
  94. }
  95. }
  96. func hnk_shouldCancelImageForKey(_ key:String) -> Bool {
  97. if self.hnk_imageFetcher?.key == key { return false }
  98. Log.debug(message: "Cancelled set image for \((key as NSString).lastPathComponent)")
  99. return true
  100. }
  101. // MARK: Background image
  102. public var hnk_backgroundImageFormat : Format<UIImage> {
  103. let bounds = self.bounds
  104. assert(bounds.size.width > 0 && bounds.size.height > 0, "[\(Mirror(reflecting: self).description) \(#function)]: UIButton size is zero. Set its frame, call sizeToFit or force layout first. You can also set a custom format with a defined size if you don't want to force layout.")
  105. let imageSize = self.backgroundRect(forBounds: bounds).size
  106. return HanekeGlobals.UIKit.formatWithSize(imageSize, scaleMode: .Fill)
  107. }
  108. public func hnk_setBackgroundImageFromURL(_ URL : Foundation.URL, state : UIControl.State = .normal, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())? = nil, success succeed : ((UIImage) -> ())? = nil) {
  109. let fetcher = NetworkFetcher<UIImage>(URL: URL)
  110. self.hnk_setBackgroundImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, failure: fail, success: succeed)
  111. }
  112. public func hnk_setBackgroundImage(_ image : UIImage, key: String, state : UIControl.State = .normal, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, success succeed : ((UIImage) -> ())? = nil) {
  113. let fetcher = SimpleFetcher<UIImage>(key: key, value: image)
  114. self.hnk_setBackgroundImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, success: succeed)
  115. }
  116. public func hnk_setBackgroundImageFromFile(_ path: String, state : UIControl.State = .normal, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())? = nil, success succeed : ((UIImage) -> ())? = nil) {
  117. let fetcher = DiskFetcher<UIImage>(path: path)
  118. self.hnk_setBackgroundImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, failure: fail, success: succeed)
  119. }
  120. public func hnk_setBackgroundImageFromFetcher(_ fetcher : Fetcher<UIImage>, state : UIControl.State = .normal, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())? = nil, success succeed : ((UIImage) -> ())? = nil) {
  121. self.hnk_cancelSetBackgroundImage()
  122. self.hnk_backgroundImageFetcher = fetcher
  123. let didSetImage = self.hnk_fetchBackgroundImageForFetcher(fetcher, state: state, format : format, failure: fail, success: succeed)
  124. if didSetImage { return }
  125. if let placeholder = placeholder {
  126. self.setBackgroundImage(placeholder, for: state)
  127. }
  128. }
  129. public func hnk_cancelSetBackgroundImage() {
  130. if let fetcher = self.hnk_backgroundImageFetcher {
  131. fetcher.cancelFetch()
  132. self.hnk_backgroundImageFetcher = nil
  133. }
  134. }
  135. // MARK: Internal Background image
  136. // See: http://stackoverflow.com/questions/25907421/associating-swift-things-with-nsobject-instances
  137. var hnk_backgroundImageFetcher : Fetcher<UIImage>! {
  138. get {
  139. let wrapper = objc_getAssociatedObject(self, &HanekeGlobals.UIKit.SetBackgroundImageFetcherKey) as? ObjectWrapper
  140. let fetcher = wrapper?.hnk_value as? Fetcher<UIImage>
  141. return fetcher
  142. }
  143. set (fetcher) {
  144. var wrapper : ObjectWrapper?
  145. if let fetcher = fetcher {
  146. wrapper = ObjectWrapper(value: fetcher)
  147. }
  148. objc_setAssociatedObject(self, &HanekeGlobals.UIKit.SetBackgroundImageFetcherKey, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  149. }
  150. }
  151. func hnk_fetchBackgroundImageForFetcher(_ fetcher: Fetcher<UIImage>, state: UIControl.State = .normal, format: Format<UIImage>? = nil, failure fail: ((Error?) -> ())?, success succeed : ((UIImage) -> ())?) -> Bool {
  152. let format = format ?? self.hnk_backgroundImageFormat
  153. let cache = Shared.imageCache
  154. if cache.formats[format.name] == nil {
  155. cache.addFormat(format)
  156. }
  157. var animated = false
  158. let fetch = cache.fetch(fetcher: fetcher, formatName: format.name, failure: {[weak self] error in
  159. if let strongSelf = self {
  160. if strongSelf.hnk_shouldCancelBackgroundImageForKey(fetcher.key) { return }
  161. strongSelf.hnk_backgroundImageFetcher = nil
  162. fail?(error)
  163. }
  164. }) { [weak self] image in
  165. if let strongSelf = self {
  166. if strongSelf.hnk_shouldCancelBackgroundImageForKey(fetcher.key) { return }
  167. strongSelf.hnk_setBackgroundImage(image, state: state, animated: animated, success: succeed)
  168. }
  169. }
  170. animated = true
  171. return fetch.hasSucceeded
  172. }
  173. func hnk_setBackgroundImage(_ image: UIImage, state: UIControl.State, animated: Bool, success succeed: ((UIImage) -> ())?) {
  174. self.hnk_backgroundImageFetcher = nil
  175. if let succeed = succeed {
  176. succeed(image)
  177. } else if animated {
  178. UIView.transition(with: self, duration: HanekeGlobals.UIKit.SetImageAnimationDuration, options: .transitionCrossDissolve, animations: {
  179. self.setBackgroundImage(image, for: state)
  180. }, completion: nil)
  181. } else {
  182. self.setBackgroundImage(image, for: state)
  183. }
  184. }
  185. func hnk_shouldCancelBackgroundImageForKey(_ key: String) -> Bool {
  186. if self.hnk_backgroundImageFetcher?.key == key { return false }
  187. Log.debug(message: "Cancelled set background image for \((key as NSString).lastPathComponent)")
  188. return true
  189. }
  190. }