You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

196 lines
7.5 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. // The MIT License (MIT)
  2. //
  3. // Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. import UIKit
  23. class SnapshotWrapperView: UIView {
  24. let contentView: UIView
  25. init(contentView: UIView) {
  26. self.contentView = contentView
  27. super.init(frame: contentView.frame)
  28. addSubview(contentView)
  29. }
  30. required init?(coder aDecoder: NSCoder) {
  31. fatalError("init(coder:) has not been implemented")
  32. }
  33. override func layoutSubviews() {
  34. super.layoutSubviews()
  35. contentView.bounds.size = bounds.size
  36. contentView.center = bounds.center
  37. }
  38. }
  39. extension UIView: HeroCompatible { }
  40. public extension HeroExtension where Base: UIView {
  41. /**
  42. **ID** is the identifier for the view. When doing a transition between two view controllers,
  43. Hero will search through all the subviews for both view controllers and matches views with the same **heroID**.
  44. Whenever a pair is discovered,
  45. Hero will automatically transit the views from source state to the destination state.
  46. */
  47. var id: String? {
  48. get { return objc_getAssociatedObject(base, &type(of: base).AssociatedKeys.heroID) as? String }
  49. set { objc_setAssociatedObject(base, &type(of: base).AssociatedKeys.heroID, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  50. }
  51. /**
  52. **isEnabled** allows to specify whether a view and its subviews should be consider for animations.
  53. If true, Hero will search through all the subviews for heroIds and modifiers. Defaults to true
  54. */
  55. var isEnabled: Bool {
  56. get { return objc_getAssociatedObject(base, &type(of: base).AssociatedKeys.heroEnabled) as? Bool ?? true }
  57. set { objc_setAssociatedObject(base, &type(of: base).AssociatedKeys.heroEnabled, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  58. }
  59. /**
  60. **isEnabledForSubviews** allows to specify whether a view's subviews should be consider for animations.
  61. If true, Hero will search through all the subviews for heroIds and modifiers. Defaults to true
  62. */
  63. var isEnabledForSubviews: Bool {
  64. get { return objc_getAssociatedObject(base, &type(of: base).AssociatedKeys.heroEnabledForSubviews) as? Bool ?? true }
  65. set { objc_setAssociatedObject(base, &type(of: base).AssociatedKeys.heroEnabledForSubviews, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  66. }
  67. /**
  68. Use **modifiers** to specify animations alongside the main transition. Checkout `HeroModifier.swift` for available modifiers.
  69. */
  70. var modifiers: [HeroModifier]? {
  71. get { return objc_getAssociatedObject(base, &type(of: base).AssociatedKeys.heroModifiers) as? [HeroModifier] }
  72. set { objc_setAssociatedObject(base, &type(of: base).AssociatedKeys.heroModifiers, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  73. }
  74. /**
  75. **modifierString** provides another way to set **modifiers**. It can be assigned through storyboard.
  76. */
  77. var modifierString: String? {
  78. get { fatalError("Reverse lookup is not supported") }
  79. set { modifiers = newValue?.parse() }
  80. }
  81. /// Used for .overFullScreen presentation
  82. internal var storedAlpha: CGFloat? {
  83. get {
  84. if let doubleValue = (objc_getAssociatedObject(base, &type(of: base).AssociatedKeys.heroStoredAlpha) as? NSNumber)?.doubleValue {
  85. return CGFloat(doubleValue)
  86. }
  87. return nil
  88. }
  89. set {
  90. if let newValue = newValue {
  91. objc_setAssociatedObject(base, &type(of: base).AssociatedKeys.heroStoredAlpha, NSNumber(value: newValue.native), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  92. } else {
  93. objc_setAssociatedObject(base, &type(of: base).AssociatedKeys.heroStoredAlpha, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  94. }
  95. }
  96. }
  97. }
  98. public extension UIView {
  99. fileprivate struct AssociatedKeys {
  100. static var heroID = "heroID"
  101. static var heroModifiers = "heroModifers"
  102. static var heroStoredAlpha = "heroStoredAlpha"
  103. static var heroEnabled = "heroEnabled"
  104. static var heroEnabledForSubviews = "heroEnabledForSubviews"
  105. }
  106. // TODO: can be moved to internal later (will still be accessible via IB)
  107. @available(*, renamed: "hero.id")
  108. @IBInspectable var heroID: String? {
  109. get { return hero.id }
  110. set { hero.id = newValue }
  111. }
  112. // TODO: can be moved to internal later (will still be accessible via IB)
  113. @available(*, renamed: "hero.isEnabled")
  114. @IBInspectable var isHeroEnabled: Bool {
  115. get { return hero.isEnabled }
  116. set { hero.isEnabled = newValue }
  117. }
  118. // TODO: can be moved to internal later (will still be accessible via IB)
  119. @available(*, renamed: "hero.isEnabledForSubviews")
  120. @IBInspectable var isHeroEnabledForSubviews: Bool {
  121. get { return hero.isEnabledForSubviews }
  122. set { hero.isEnabledForSubviews = newValue }
  123. }
  124. @available(*, renamed: "hero.modifiers")
  125. var heroModifiers: [HeroModifier]? {
  126. get { return hero.modifiers }
  127. set { hero.modifiers = newValue }
  128. }
  129. // TODO: can be moved to internal later (will still be accessible via IB)
  130. @available(*, renamed: "hero.modifierString")
  131. @IBInspectable var heroModifierString: String? {
  132. get { fatalError("Reverse lookup is not supported") }
  133. set { hero.modifiers = newValue?.parse() }
  134. }
  135. internal func slowSnapshotView() -> UIView {
  136. UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
  137. guard let currentContext = UIGraphicsGetCurrentContext() else {
  138. UIGraphicsEndImageContext()
  139. return UIView()
  140. }
  141. layer.render(in: currentContext)
  142. let image = UIGraphicsGetImageFromCurrentImageContext()
  143. UIGraphicsEndImageContext()
  144. let imageView = UIImageView(image: image)
  145. imageView.frame = bounds
  146. return SnapshotWrapperView(contentView: imageView)
  147. }
  148. internal func snapshotView() -> UIView? {
  149. let snapshot = snapshotView(afterScreenUpdates: true)
  150. if #available(iOS 11.0, *), let oldSnapshot = snapshot {
  151. // in iOS 11, the snapshot taken by snapshotView(afterScreenUpdates) won't contain a container view
  152. return SnapshotWrapperView(contentView: oldSnapshot)
  153. } else {
  154. return snapshot
  155. }
  156. }
  157. internal var flattenedViewHierarchy: [UIView] {
  158. guard hero.isEnabled else { return [] }
  159. if #available(iOS 9.0, *), isHidden && (superview is UICollectionView || superview is UIStackView || self is UITableViewCell) {
  160. return []
  161. } else if isHidden && (superview is UICollectionView || self is UITableViewCell) {
  162. return []
  163. } else if hero.isEnabledForSubviews {
  164. return [self] + subviews.flatMap { $0.flattenedViewHierarchy }
  165. } else {
  166. return [self]
  167. }
  168. }
  169. @available(*, renamed: "hero.storedAplha")
  170. internal var heroStoredAlpha: CGFloat? {
  171. get { return hero.storedAlpha }
  172. set { hero.storedAlpha = newValue }
  173. }
  174. }