Skip to content

Commit

Permalink
provide more snapshot types
Browse files Browse the repository at this point in the history
  • Loading branch information
lkzhao committed Feb 4, 2017
1 parent 6d6470c commit eda0864
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 71 deletions.
71 changes: 43 additions & 28 deletions Sources/HeroContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,36 +108,49 @@ extension HeroContext {
let oldAlpha = view.alpha
view.layer.cornerRadius = 0
view.alpha = 1

let snapshot: UIView
if #available(iOS 9.0, *), let stackView = view as? UIStackView {
snapshot = stackView.slowSnapshotView()
} else if let imageView = view as? UIImageView, view.subviews.isEmpty {
let contentView = UIImageView(image: imageView.image)
contentView.frame = imageView.bounds
contentView.contentMode = imageView.contentMode
contentView.tintColor = imageView.tintColor
contentView.backgroundColor = imageView.backgroundColor
let snapShotView = UIView()
snapShotView.addSubview(contentView)
snapshot = snapShotView
} else if let barView = view as? UINavigationBar, barView.isTranslucent {
let newBarView = UINavigationBar(frame: barView.frame)

newBarView.barStyle = barView.barStyle
newBarView.tintColor = barView.tintColor
newBarView.barTintColor = barView.barTintColor
newBarView.clipsToBounds = false

// take a snapshot without the background
barView.layer.sublayers![0].opacity = 0
let realSnapshot = barView.snapshotView(afterScreenUpdates: true)!
barView.layer.sublayers![0].opacity = 1

newBarView.addSubview(realSnapshot)
snapshot = newBarView
} else {
let snapshotType:HeroSnapshotType = self[view]?.snapshotType ?? .optimized

switch snapshotType {
case .normal:
snapshot = view.snapshotView(afterScreenUpdates: true)!
case .layerRender:
snapshot = view.slowSnapshotView()
case .noSnapshot:
snapshot = view
case .optimized:
if #available(iOS 9.0, *), let stackView = view as? UIStackView {
snapshot = stackView.slowSnapshotView()
} else if let imageView = view as? UIImageView, view.subviews.isEmpty {
let contentView = UIImageView(image: imageView.image)
contentView.frame = imageView.bounds
contentView.contentMode = imageView.contentMode
contentView.tintColor = imageView.tintColor
contentView.backgroundColor = imageView.backgroundColor
let snapShotView = UIView()
snapShotView.addSubview(contentView)
snapshot = snapShotView
} else if let barView = view as? UINavigationBar, barView.isTranslucent {
let newBarView = UINavigationBar(frame: barView.frame)

newBarView.barStyle = barView.barStyle
newBarView.tintColor = barView.tintColor
newBarView.barTintColor = barView.barTintColor
newBarView.clipsToBounds = false

// take a snapshot without the background
barView.layer.sublayers![0].opacity = 0
let realSnapshot = barView.snapshotView(afterScreenUpdates: true)!
barView.layer.sublayers![0].opacity = 1

newBarView.addSubview(realSnapshot)
snapshot = newBarView
} else {
snapshot = view.snapshotView(afterScreenUpdates: true)!
}
}

view.layer.cornerRadius = oldCornerRadius
view.alpha = oldAlpha

Expand Down Expand Up @@ -171,7 +184,9 @@ extension HeroContext {
snapshot.frame = containerView.convert(view.bounds, from: view)
snapshot.heroID = view.heroID

hide(view: view)
if snapshotType != .noSnapshot {
hide(view: view)
}

containerView.addSubview(snapshot)
snapshotViews[view] = snapshot
Expand Down
131 changes: 89 additions & 42 deletions Sources/HeroModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,57 @@ extension HeroModifier {
}

// other modifiers
extension HeroModifier {
/**
transition from/to the state of the view with matching heroID
Will also force the view to use global coordinate space.
- Parameters:
- heroID: the source view's heroId.
*/
public static func source(heroID: String) -> HeroModifier {
return HeroModifier { targetState in
targetState.source = heroID
}
}

/**
Works in combination with position modifier to apply a natural curve when moving to the destination.
*/
public static var arc: HeroModifier = .arc()
/**
Works in combination with position modifier to apply a natural curve when moving to the destination.
- Parameters:
- intensity: a value of 1 represent a downward natural curve ╰. a value of -1 represent a upward curve ╮.
default is 1.
*/
public static func arc(intensity: CGFloat = 1) -> HeroModifier {
return HeroModifier { targetState in
targetState.arc = intensity
}
}

/**
Cascade applys increasing delay modifiers to subviews
*/
public static var cascade: HeroModifier = .cascade()

/**
Cascade applys increasing delay modifiers to subviews
- Parameters:
- delta: delay in between each animation
- direction: cascade direction
- delayMatchedViews: whether or not to delay matched subviews until all cascading animation have started
*/
public static func cascade(delta: TimeInterval = 0.02,
direction: CascadeDirection = .topToBottom,
delayMatchedViews: Bool = false) -> HeroModifier {
return HeroModifier { targetState in
targetState.cascade = (delta, direction, delayMatchedViews)
}
}
}

// advance modifiers
extension HeroModifier {
/**
Use global coordinate space.
Expand All @@ -215,92 +266,80 @@ extension HeroModifier {

/**
Sets the zPosition during the animation, not animatable.

During animation, Hero might incorrectly infer the order to draw your views. Use this modifier to adjust
the view draw order.
- Parameters:
- zPosition: zPosition during the animation
- zPosition: zPosition during the animation
*/
public static func zPosition(_ zPosition: CGFloat) -> HeroModifier {
return HeroModifier { targetState in
targetState.zPosition = zPosition
}
}

/**
Same as zPosition modifier but only effective only when the view is matched. Will override zPosition modifier.
Will also force the view to use global coordinate space when the view is matched.
- Parameters:
- zPosition: zPosition during the animation
- zPosition: zPosition during the animation
*/
public static func zPositionIfMatched(_ zPositionIfMatched: CGFloat) -> HeroModifier {
return HeroModifier { targetState in
targetState.zPositionIfMatched = zPositionIfMatched
}
}

/**
ignore all heroModifiers attributes for a view's direct subviews.
*/
public static var ignoreSubviewModifiers: HeroModifier = .ignoreSubviewModifiers()

/**
ignore all heroModifiers attributes for a view's subviews.
- Parameters:
- recursive: if false, will only ignore direct subviews' modifiers. default false.
- recursive: if false, will only ignore direct subviews' modifiers. default false.
*/
public static func ignoreSubviewModifiers(recursive: Bool = false) -> HeroModifier {
return HeroModifier { targetState in
targetState.ignoreSubviewModifiers = recursive
}
}

/**
transition from/to the state of the view with matching heroID
Will also force the view to use global coordinate space.
- Parameters:
- heroID: the source view's heroId.
Will create snapshot optimized for different view type.
For custom views or views with masking, useOptimizedSnapshot might create snapshots
that appear differently than the actual view.
In that case, use .useNormalSnapshot or .useSlowRenderSnapshot to disable the optimization.

This modifier actually does nothing by itself since .useOptimizedSnapshot is the default.
*/
public static func source(heroID: String) -> HeroModifier {
return HeroModifier { targetState in
targetState.source = heroID
}
public static var useOptimizedSnapshot: HeroModifier = HeroModifier { targetState in
targetState.snapshotType = .optimized
}

/**
Works in combination with position modifier to apply a natural curve when moving to the destination.
*/
public static var arc: HeroModifier = .arc()
/**
Works in combination with position modifier to apply a natural curve when moving to the destination.
- Parameters:
- intensity: a value of 1 represent a downward natural curve ╰. a value of -1 represent a upward curve ╮.
default is 1.
Create snapshot using snapshotView(afterScreenUpdates:).
*/
public static func arc(intensity: CGFloat = 1) -> HeroModifier {
return HeroModifier { targetState in
targetState.arc = intensity
}
public static var useNormalSnapshot: HeroModifier = HeroModifier { targetState in
targetState.snapshotType = .normal
}

/**
Cascade applys increasing delay modifiers to subviews
Create snapshot using layer.render(in: currentContext).
This is slower than .useNormalSnapshot but gives more accurate snapshot for some views (eg. UIStackView).
*/
public static var cascade: HeroModifier = .cascade()

public static var useLayerRenderSnapshot: HeroModifier = HeroModifier { targetState in
targetState.snapshotType = .layerRender
}

/**
Cascade applys increasing delay modifiers to subviews
- Parameters:
- delta: delay in between each animation
- direction: cascade direction
- delayMatchedViews: whether or not to delay matched subviews until all cascading animation have started
Force Hero to not create any snapshot when animating this view.
This will mess up the view hierarchy, therefore, view controllers have to rebuild
its view structure after the transition finishes.
*/
public static func cascade(delta: TimeInterval = 0.02,
direction: CascadeDirection = .topToBottom,
delayMatchedViews: Bool = false) -> HeroModifier {
return HeroModifier { targetState in
targetState.cascade = (delta, direction, delayMatchedViews)
}
public static var useNoSnapshot: HeroModifier = HeroModifier { targetState in
targetState.snapshotType = .noSnapshot
}
}

Expand Down Expand Up @@ -381,6 +420,14 @@ extension HeroModifier {
if let zPosition = parameters.getCGFloat(0) {
modifier = .zPositionIfMatched(zPosition)
}
case "useOptimizedSnapshot":
modifier = .useOptimizedSnapshot
case "useNormalSnapshot":
modifier = .useNormalSnapshot
case "useLayerRenderSnapshot":
modifier = .useLayerRenderSnapshot
case "useNoSnapshot":
modifier = .useNoSnapshot
default: break
}
return modifier
Expand Down
21 changes: 21 additions & 0 deletions Sources/HeroTargetState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@

import UIKit


public enum HeroSnapshotType {
/// Will optimize for different type of views
/// For custom views or views with masking, .optimizedDefault might create snapshots
/// that appear differently than the actual view.
/// In that case, use .normal or .slowRender to disable the optimization
case optimized

/// snapshotView(afterScreenUpdates:)
case normal

/// layer.render(in: currentContext)
case layerRender

/// will not create snapshot. animate the view directly.
/// This will mess up the view hierarchy, therefore, view controllers have to rebuild
/// its view structure after the transition finishes
case noSnapshot
}

public struct HeroTargetState {
internal var opacity: CGFloat?
internal var cornerRadius: CGFloat?
Expand All @@ -39,6 +59,7 @@ public struct HeroTargetState {
internal var cascade: (TimeInterval, CascadeDirection, Bool)?
internal var ignoreSubviewModifiers: Bool?
internal var useGlobalCoordinateSpace: Bool?
internal var snapshotType: HeroSnapshotType?

internal var custom: [String:Any]?

Expand Down
12 changes: 11 additions & 1 deletion Sources/UIKit+Hero.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,21 @@ public extension UIViewController {
}
}

@available(*, deprecated: 0.1.4, message: "use hero_dismissViewController instead")
@IBAction public func ht_dismiss(_ sender: UIView) {
hero_dismissViewController(sender)
}

@available(*, deprecated: 0.1.4, message: "use hero_replaceViewController(with:) instead")
public func heroReplaceViewController(with next: UIViewController) {
hero_replaceViewController(with: next)
}

@IBAction public func hero_dismissViewController(_ sender: UIView) {
dismiss(animated: true, completion: nil)
}

public func heroReplaceViewController(with next: UIViewController) {
public func hero_replaceViewController(with next: UIViewController) {
if let navigationController = navigationController {
var vcs = navigationController.childViewControllers
if !vcs.isEmpty {
Expand Down

0 comments on commit eda0864

Please sign in to comment.