UIButton subclass with animated 'shimmer' effectAnimated magazineSubclass SKSpriteNodeAnimated Score Amounts for GameSliding effect with bounceThree-layer parallax effectCustom animated image controlAnimated Ticker with JavaScript and Sass CSSCSS3 Animated LoaderRain effect with SFMLTableview Subclass implementing delegate datasource
Why do Radio Buttons not fill the entire outer circle?
Stack Interview Code methods made from class Node and Smart Pointers
Non-trope happy ending?
How to draw a matrix with arrows in limited space
Is this toilet slogan correct usage of the English language?
Does grappling negate Mirror Image?
The IT department bottlenecks progress, how should I handle this?
Quoting Keynes in a lecture
Is it allowed to activate the ability of multiple planeswalkers in a single turn?
C++ copy constructor called at return
Why is it that I can sometimes guess the next note?
Doesn't the system of the Supreme Court oppose justice?
Can you use Vicious Mockery to win an argument or gain favours?
awk assign to multiple variables at once
Review your own paper in Mathematics
Is there a nicer/politer/more positive alternative for "negates"?
How could a planet have erratic days?
Why should universal income be universal?
Mimic lecturing on blackboard, facing audience
Permission on Database
Will number of steps recorded on FitBit/any fitness tracker add up distance in PokemonGo?
Has any country ever had 2 former presidents in jail simultaneously?
Did the UK lift the requirement for registering SIM cards?
Biological Blimps: Propulsion
UIButton subclass with animated 'shimmer' effect
Animated magazineSubclass SKSpriteNodeAnimated Score Amounts for GameSliding effect with bounceThree-layer parallax effectCustom animated image controlAnimated Ticker with JavaScript and Sass CSSCSS3 Animated LoaderRain effect with SFMLTableview Subclass implementing delegate datasource
$begingroup$
In earlier versions of iOS the lock screen had a 'slide to unlock' element which I'm referencing as a 'shimmer' effect.
The effect I'm looking for is simpler:
- Button starts with single color (e.g. blue)
- a band of color (e.g. red) sweeps across the text label from left to right
- Repeat
Here's an example of my ShimmerButton
class in action, and the code itself:
class ShimmerButton: UIButton
private let wrapperLayer = CALayer()
private let gradientLayer = CAGradientLayer()
var gradientColors: [UIColor] = []
didSet
gradientLayer.colors = gradientColors.map( $0.cgColor )
override func layoutSubviews()
super.layoutSubviews()
// only needs to be set once, but no harm (?) in setting multiple times
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
wrapperLayer.addSublayer(gradientLayer)
layer.insertSublayer(wrapperLayer, at: 0)
wrapperLayer.mask = titleLabel?.layer
// update sublayers based on new frame
wrapperLayer.frame = frame
gradientLayer.frame.size = CGSize(width: frame.width * 4, height: frame.height)
// remove any existing animation, and re-create for new size
let animationKeyPath = "position.x"
gradientLayer.removeAnimation(forKey: animationKeyPath)
let animation: CABasicAnimation = CABasicAnimation(keyPath: animationKeyPath)
animation.fromValue = bounds.width - gradientLayer.bounds.width / 2
animation.toValue = gradientLayer.bounds.width / 2
animation.duration = 3
animation.repeatCount = HUGE
animation.fillMode = kCAFillModeForwards
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
gradientLayer.add(animation, forKey: animationKeyPath)
Example usage:
let shimmer = ShimmerButton(frame: .zero)
shimmer.backgroundColor = .white
shimmer.setTitle("Find new skills...", for: .normal)
shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
shimmer.sizeToFit()
shimmer.gradientColors = [.blue, .blue, .red, .blue, .blue]
Some questions:
- Code that only needs to be once off is happening in
layoutSubviews
so will be called multiple times. I did this so I didn't have to overrideinit?(coder:)
andinit(frame:)
. Is this acceptable, or just lazy on my part? - I'm animating the position of a
CAGradientLayer
to get the visual effect, but found that I needed to use another layer as a wrapper otherwise the text itself would move. Am I overlooking a solution that involves fewer layers?
swift animation swift3
$endgroup$
add a comment |
$begingroup$
In earlier versions of iOS the lock screen had a 'slide to unlock' element which I'm referencing as a 'shimmer' effect.
The effect I'm looking for is simpler:
- Button starts with single color (e.g. blue)
- a band of color (e.g. red) sweeps across the text label from left to right
- Repeat
Here's an example of my ShimmerButton
class in action, and the code itself:
class ShimmerButton: UIButton
private let wrapperLayer = CALayer()
private let gradientLayer = CAGradientLayer()
var gradientColors: [UIColor] = []
didSet
gradientLayer.colors = gradientColors.map( $0.cgColor )
override func layoutSubviews()
super.layoutSubviews()
// only needs to be set once, but no harm (?) in setting multiple times
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
wrapperLayer.addSublayer(gradientLayer)
layer.insertSublayer(wrapperLayer, at: 0)
wrapperLayer.mask = titleLabel?.layer
// update sublayers based on new frame
wrapperLayer.frame = frame
gradientLayer.frame.size = CGSize(width: frame.width * 4, height: frame.height)
// remove any existing animation, and re-create for new size
let animationKeyPath = "position.x"
gradientLayer.removeAnimation(forKey: animationKeyPath)
let animation: CABasicAnimation = CABasicAnimation(keyPath: animationKeyPath)
animation.fromValue = bounds.width - gradientLayer.bounds.width / 2
animation.toValue = gradientLayer.bounds.width / 2
animation.duration = 3
animation.repeatCount = HUGE
animation.fillMode = kCAFillModeForwards
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
gradientLayer.add(animation, forKey: animationKeyPath)
Example usage:
let shimmer = ShimmerButton(frame: .zero)
shimmer.backgroundColor = .white
shimmer.setTitle("Find new skills...", for: .normal)
shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
shimmer.sizeToFit()
shimmer.gradientColors = [.blue, .blue, .red, .blue, .blue]
Some questions:
- Code that only needs to be once off is happening in
layoutSubviews
so will be called multiple times. I did this so I didn't have to overrideinit?(coder:)
andinit(frame:)
. Is this acceptable, or just lazy on my part? - I'm animating the position of a
CAGradientLayer
to get the visual effect, but found that I needed to use another layer as a wrapper otherwise the text itself would move. Am I overlooking a solution that involves fewer layers?
swift animation swift3
$endgroup$
add a comment |
$begingroup$
In earlier versions of iOS the lock screen had a 'slide to unlock' element which I'm referencing as a 'shimmer' effect.
The effect I'm looking for is simpler:
- Button starts with single color (e.g. blue)
- a band of color (e.g. red) sweeps across the text label from left to right
- Repeat
Here's an example of my ShimmerButton
class in action, and the code itself:
class ShimmerButton: UIButton
private let wrapperLayer = CALayer()
private let gradientLayer = CAGradientLayer()
var gradientColors: [UIColor] = []
didSet
gradientLayer.colors = gradientColors.map( $0.cgColor )
override func layoutSubviews()
super.layoutSubviews()
// only needs to be set once, but no harm (?) in setting multiple times
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
wrapperLayer.addSublayer(gradientLayer)
layer.insertSublayer(wrapperLayer, at: 0)
wrapperLayer.mask = titleLabel?.layer
// update sublayers based on new frame
wrapperLayer.frame = frame
gradientLayer.frame.size = CGSize(width: frame.width * 4, height: frame.height)
// remove any existing animation, and re-create for new size
let animationKeyPath = "position.x"
gradientLayer.removeAnimation(forKey: animationKeyPath)
let animation: CABasicAnimation = CABasicAnimation(keyPath: animationKeyPath)
animation.fromValue = bounds.width - gradientLayer.bounds.width / 2
animation.toValue = gradientLayer.bounds.width / 2
animation.duration = 3
animation.repeatCount = HUGE
animation.fillMode = kCAFillModeForwards
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
gradientLayer.add(animation, forKey: animationKeyPath)
Example usage:
let shimmer = ShimmerButton(frame: .zero)
shimmer.backgroundColor = .white
shimmer.setTitle("Find new skills...", for: .normal)
shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
shimmer.sizeToFit()
shimmer.gradientColors = [.blue, .blue, .red, .blue, .blue]
Some questions:
- Code that only needs to be once off is happening in
layoutSubviews
so will be called multiple times. I did this so I didn't have to overrideinit?(coder:)
andinit(frame:)
. Is this acceptable, or just lazy on my part? - I'm animating the position of a
CAGradientLayer
to get the visual effect, but found that I needed to use another layer as a wrapper otherwise the text itself would move. Am I overlooking a solution that involves fewer layers?
swift animation swift3
$endgroup$
In earlier versions of iOS the lock screen had a 'slide to unlock' element which I'm referencing as a 'shimmer' effect.
The effect I'm looking for is simpler:
- Button starts with single color (e.g. blue)
- a band of color (e.g. red) sweeps across the text label from left to right
- Repeat
Here's an example of my ShimmerButton
class in action, and the code itself:
class ShimmerButton: UIButton
private let wrapperLayer = CALayer()
private let gradientLayer = CAGradientLayer()
var gradientColors: [UIColor] = []
didSet
gradientLayer.colors = gradientColors.map( $0.cgColor )
override func layoutSubviews()
super.layoutSubviews()
// only needs to be set once, but no harm (?) in setting multiple times
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
wrapperLayer.addSublayer(gradientLayer)
layer.insertSublayer(wrapperLayer, at: 0)
wrapperLayer.mask = titleLabel?.layer
// update sublayers based on new frame
wrapperLayer.frame = frame
gradientLayer.frame.size = CGSize(width: frame.width * 4, height: frame.height)
// remove any existing animation, and re-create for new size
let animationKeyPath = "position.x"
gradientLayer.removeAnimation(forKey: animationKeyPath)
let animation: CABasicAnimation = CABasicAnimation(keyPath: animationKeyPath)
animation.fromValue = bounds.width - gradientLayer.bounds.width / 2
animation.toValue = gradientLayer.bounds.width / 2
animation.duration = 3
animation.repeatCount = HUGE
animation.fillMode = kCAFillModeForwards
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
gradientLayer.add(animation, forKey: animationKeyPath)
Example usage:
let shimmer = ShimmerButton(frame: .zero)
shimmer.backgroundColor = .white
shimmer.setTitle("Find new skills...", for: .normal)
shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
shimmer.sizeToFit()
shimmer.gradientColors = [.blue, .blue, .red, .blue, .blue]
Some questions:
- Code that only needs to be once off is happening in
layoutSubviews
so will be called multiple times. I did this so I didn't have to overrideinit?(coder:)
andinit(frame:)
. Is this acceptable, or just lazy on my part? - I'm animating the position of a
CAGradientLayer
to get the visual effect, but found that I needed to use another layer as a wrapper otherwise the text itself would move. Am I overlooking a solution that involves fewer layers?
swift animation swift3
swift animation swift3
edited Mar 26 '17 at 16:30
Jamal♦
30.4k11121227
30.4k11121227
asked Mar 20 '17 at 20:07
MathewSMathewS
568311
568311
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
$begingroup$
Okay, so sometimes posting a question is the best way to figure out an answer yourself 🙃.
I was looking for an alternative to having the gradient colors evenly spaced (my hack was to repeat the colors e.g. "blue blue red blue blue") and found the locations
property on CAGradientLayer
which is also animatable.
Animating this property feels like a much better approach because with the position not changing I can remove the wrapper layer.
Then, with without the wrapper layer I realized that I could override layerClass
so the buttons backing layer is a gradient layer, which will also resize as needed when the view frame changes so that I don't even need to override layoutSubviews
.
The only thing that feels a little strange is the forced requirement for for gradient colors to have three colors (otherwise I'd need to figure some formula to derive values for locations
).
Edit: I've updated answer so instead of directly setting the colors, I've exposed gradientTint
and gradientHighlight
properties that are used to set the gradients colors
array.
I've created a protocol that captures the properties used to define the shimmer effect, and also provide a default implementation of the animation.
It wasn't a requirement from my original question, but moving this code out of a specific subclass makes this snippet of code reusable (and maintainable) across other subclasses (e.g. UIView, UILabel).
protocol ShimmerEffect
var animationDuration: TimeInterval set get
var animationDelay: TimeInterval set get
var gradientTint: UIColor set get
var gradientHighlight: UIColor set get
//// Expects value between 0.0—1.0 that represents
//// the ratio of the gradient highlight to the full
//// width of the gradient.
var gradientHighlightRatio: Double set get
//// The layer that the gradient will be applied to
var gradientLayer: CAGradientLayer get
Default implementation:
extension ShimmerEffect
/// Configures, and adds the animation to the gradientLayer
func addShimmerAnimation()
// `gradientHighlightRatio` represents how wide the highlight
// should be compared to the entire width of the gradient and
// is used to calculate the positions of the 3 gradient colors.
// If the highlight is 20% width of the gradient, then the
// 'start locations' would be [-0.2, -0.1, 0.0] and the
// 'end locations' would be [1.0, 1.1, 1.2]
let startLocations = [NSNumber(value: -gradientHighlightRatio), NSNumber(value: -gradientHighlightRatio/2), 0.0]
let endLocations = [1, NSNumber(value: 1+(gradientHighlightRatio/2)), NSNumber(value: 1+gradientHighlightRatio)]
let gradientColors = [gradientTint.cgColor, gradientHighlight.cgColor, gradientTint.cgColor]
// If the gradient highlight ratio is wide, then it can
// 'bleed' over into the visible space of the view, which
// looks particularly bad if there is a pause between the
// animation repeating.
// Shifting the start and end points of the gradient by the
// size of the highlight prevents this.
gradientLayer.startPoint = CGPoint(x: -gradientHighlightRatio, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1+gradientHighlightRatio, y: 0.5)
gradientLayer.locations = startLocations
gradientLayer.colors = gradientColors
let animationKeyPath = "locations"
let shimmerAnimation = CABasicAnimation(keyPath: animationKeyPath)
shimmerAnimation.fromValue = startLocations
shimmerAnimation.toValue = endLocations
shimmerAnimation.duration = animationDuration
shimmerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
let animationGroup = CAAnimationGroup()
animationGroup.duration = animationDuration + animationDelay
animationGroup.repeatCount = .infinity
animationGroup.animations = [shimmerAnimation]
// removes animation with same key (if exists) then adds
// the new animation
gradientLayer.removeAnimation(forKey: animationKeyPath)
gradientLayer.add(animationGroup, forKey: animationKeyPath)
In the UIButton subclass I've added property observers to each of the properties that calls addShimmerAnimation()
with any property change.
I also considered just supplying default values and requiring addShimmerAnimation()
to be called manually once properties were configured. Another route was not having any public properties exposed and instead passing everything in through an initializer, but that would remove the possibility of these classes being used in a storyboard (which is an option I like to leave open) and having properties exposed through tagging the properties with IBInspectable
.
class ShimmerButton: UIButton, ShimmerEffect
override static var layerClass: AnyClass
return CAGradientLayer.self
var gradientLayer: CAGradientLayer
return layer as! CAGradientLayer
var animationDuration: TimeInterval = 3
didSet addShimmerAnimation()
var animationDelay: TimeInterval = 1.5
didSet addShimmerAnimation()
var gradientHighlightRatio: Double = 0.3
didSet addShimmerAnimation()
var gradientTint: UIColor = .black
didSet addShimmerAnimation()
var gradientHighlight: UIColor = .white
didSet addShimmerAnimation()
override init(frame: CGRect)
super.init(frame: frame)
gradientLayer.mask = titleLabel?.layer
addShimmerAnimation()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
gradientLayer.mask = titleLabel?.layer
addShimmerAnimation()
Example usage:
let shimmer = ShimmerButton()
shimmer.setTitle("Find new skills", for: .normal)
shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
shimmer.gradientTint = darkBlue
shimmer.gradientHighlight = lightBlue
shimmer.sizeToFit()
What I like about this approach is that the complexity is moved out of the subclass making it super easy to duplicate over other views.
What frustrates me is that UIView
, UILabel
and UIButton
only have minor differences. I wish there was a way for the computed properties to be extracted into a common place.
Example of ShimmerButton
and ShimmerView
(a UIView
subclass) being used together:
$endgroup$
add a comment |
$begingroup$
Shimmer animation can be added like below in iOS
class ViewController: UIViewController
@IBOutlet var label: UILabel!
let gradientLayer = CAGradientLayer()
override func viewDidLoad()
super.viewDidLoad()
self.gradientLayer.frame = self.label.bounds
self.gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
self.gradientLayer.colors = [UIColor.red.cgColor, UIColor.white.cgColor, UIColor.darkGray.cgColor]
let startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
let endLocations : [NSNumber] = [1.0,1.5, 2.0]
self.gradientLayer.locations = startLocations
let animation = CABasicAnimation(keyPath: "locations")
animation.fromValue = startLocations
animation.toValue = endLocations
animation.duration = 0.8
animation.repeatCount = .infinity
self.gradientLayer.add(animation, forKey: animation.keyPath)
self.label.layer.addSublayer(self.gradientLayer)
DispatchQueue.main.asyncAfter(deadline: .now() + 5)
self.gradientLayer.removeAllAnimations()
New contributor
$endgroup$
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f158336%2fuibutton-subclass-with-animated-shimmer-effect%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Okay, so sometimes posting a question is the best way to figure out an answer yourself 🙃.
I was looking for an alternative to having the gradient colors evenly spaced (my hack was to repeat the colors e.g. "blue blue red blue blue") and found the locations
property on CAGradientLayer
which is also animatable.
Animating this property feels like a much better approach because with the position not changing I can remove the wrapper layer.
Then, with without the wrapper layer I realized that I could override layerClass
so the buttons backing layer is a gradient layer, which will also resize as needed when the view frame changes so that I don't even need to override layoutSubviews
.
The only thing that feels a little strange is the forced requirement for for gradient colors to have three colors (otherwise I'd need to figure some formula to derive values for locations
).
Edit: I've updated answer so instead of directly setting the colors, I've exposed gradientTint
and gradientHighlight
properties that are used to set the gradients colors
array.
I've created a protocol that captures the properties used to define the shimmer effect, and also provide a default implementation of the animation.
It wasn't a requirement from my original question, but moving this code out of a specific subclass makes this snippet of code reusable (and maintainable) across other subclasses (e.g. UIView, UILabel).
protocol ShimmerEffect
var animationDuration: TimeInterval set get
var animationDelay: TimeInterval set get
var gradientTint: UIColor set get
var gradientHighlight: UIColor set get
//// Expects value between 0.0—1.0 that represents
//// the ratio of the gradient highlight to the full
//// width of the gradient.
var gradientHighlightRatio: Double set get
//// The layer that the gradient will be applied to
var gradientLayer: CAGradientLayer get
Default implementation:
extension ShimmerEffect
/// Configures, and adds the animation to the gradientLayer
func addShimmerAnimation()
// `gradientHighlightRatio` represents how wide the highlight
// should be compared to the entire width of the gradient and
// is used to calculate the positions of the 3 gradient colors.
// If the highlight is 20% width of the gradient, then the
// 'start locations' would be [-0.2, -0.1, 0.0] and the
// 'end locations' would be [1.0, 1.1, 1.2]
let startLocations = [NSNumber(value: -gradientHighlightRatio), NSNumber(value: -gradientHighlightRatio/2), 0.0]
let endLocations = [1, NSNumber(value: 1+(gradientHighlightRatio/2)), NSNumber(value: 1+gradientHighlightRatio)]
let gradientColors = [gradientTint.cgColor, gradientHighlight.cgColor, gradientTint.cgColor]
// If the gradient highlight ratio is wide, then it can
// 'bleed' over into the visible space of the view, which
// looks particularly bad if there is a pause between the
// animation repeating.
// Shifting the start and end points of the gradient by the
// size of the highlight prevents this.
gradientLayer.startPoint = CGPoint(x: -gradientHighlightRatio, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1+gradientHighlightRatio, y: 0.5)
gradientLayer.locations = startLocations
gradientLayer.colors = gradientColors
let animationKeyPath = "locations"
let shimmerAnimation = CABasicAnimation(keyPath: animationKeyPath)
shimmerAnimation.fromValue = startLocations
shimmerAnimation.toValue = endLocations
shimmerAnimation.duration = animationDuration
shimmerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
let animationGroup = CAAnimationGroup()
animationGroup.duration = animationDuration + animationDelay
animationGroup.repeatCount = .infinity
animationGroup.animations = [shimmerAnimation]
// removes animation with same key (if exists) then adds
// the new animation
gradientLayer.removeAnimation(forKey: animationKeyPath)
gradientLayer.add(animationGroup, forKey: animationKeyPath)
In the UIButton subclass I've added property observers to each of the properties that calls addShimmerAnimation()
with any property change.
I also considered just supplying default values and requiring addShimmerAnimation()
to be called manually once properties were configured. Another route was not having any public properties exposed and instead passing everything in through an initializer, but that would remove the possibility of these classes being used in a storyboard (which is an option I like to leave open) and having properties exposed through tagging the properties with IBInspectable
.
class ShimmerButton: UIButton, ShimmerEffect
override static var layerClass: AnyClass
return CAGradientLayer.self
var gradientLayer: CAGradientLayer
return layer as! CAGradientLayer
var animationDuration: TimeInterval = 3
didSet addShimmerAnimation()
var animationDelay: TimeInterval = 1.5
didSet addShimmerAnimation()
var gradientHighlightRatio: Double = 0.3
didSet addShimmerAnimation()
var gradientTint: UIColor = .black
didSet addShimmerAnimation()
var gradientHighlight: UIColor = .white
didSet addShimmerAnimation()
override init(frame: CGRect)
super.init(frame: frame)
gradientLayer.mask = titleLabel?.layer
addShimmerAnimation()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
gradientLayer.mask = titleLabel?.layer
addShimmerAnimation()
Example usage:
let shimmer = ShimmerButton()
shimmer.setTitle("Find new skills", for: .normal)
shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
shimmer.gradientTint = darkBlue
shimmer.gradientHighlight = lightBlue
shimmer.sizeToFit()
What I like about this approach is that the complexity is moved out of the subclass making it super easy to duplicate over other views.
What frustrates me is that UIView
, UILabel
and UIButton
only have minor differences. I wish there was a way for the computed properties to be extracted into a common place.
Example of ShimmerButton
and ShimmerView
(a UIView
subclass) being used together:
$endgroup$
add a comment |
$begingroup$
Okay, so sometimes posting a question is the best way to figure out an answer yourself 🙃.
I was looking for an alternative to having the gradient colors evenly spaced (my hack was to repeat the colors e.g. "blue blue red blue blue") and found the locations
property on CAGradientLayer
which is also animatable.
Animating this property feels like a much better approach because with the position not changing I can remove the wrapper layer.
Then, with without the wrapper layer I realized that I could override layerClass
so the buttons backing layer is a gradient layer, which will also resize as needed when the view frame changes so that I don't even need to override layoutSubviews
.
The only thing that feels a little strange is the forced requirement for for gradient colors to have three colors (otherwise I'd need to figure some formula to derive values for locations
).
Edit: I've updated answer so instead of directly setting the colors, I've exposed gradientTint
and gradientHighlight
properties that are used to set the gradients colors
array.
I've created a protocol that captures the properties used to define the shimmer effect, and also provide a default implementation of the animation.
It wasn't a requirement from my original question, but moving this code out of a specific subclass makes this snippet of code reusable (and maintainable) across other subclasses (e.g. UIView, UILabel).
protocol ShimmerEffect
var animationDuration: TimeInterval set get
var animationDelay: TimeInterval set get
var gradientTint: UIColor set get
var gradientHighlight: UIColor set get
//// Expects value between 0.0—1.0 that represents
//// the ratio of the gradient highlight to the full
//// width of the gradient.
var gradientHighlightRatio: Double set get
//// The layer that the gradient will be applied to
var gradientLayer: CAGradientLayer get
Default implementation:
extension ShimmerEffect
/// Configures, and adds the animation to the gradientLayer
func addShimmerAnimation()
// `gradientHighlightRatio` represents how wide the highlight
// should be compared to the entire width of the gradient and
// is used to calculate the positions of the 3 gradient colors.
// If the highlight is 20% width of the gradient, then the
// 'start locations' would be [-0.2, -0.1, 0.0] and the
// 'end locations' would be [1.0, 1.1, 1.2]
let startLocations = [NSNumber(value: -gradientHighlightRatio), NSNumber(value: -gradientHighlightRatio/2), 0.0]
let endLocations = [1, NSNumber(value: 1+(gradientHighlightRatio/2)), NSNumber(value: 1+gradientHighlightRatio)]
let gradientColors = [gradientTint.cgColor, gradientHighlight.cgColor, gradientTint.cgColor]
// If the gradient highlight ratio is wide, then it can
// 'bleed' over into the visible space of the view, which
// looks particularly bad if there is a pause between the
// animation repeating.
// Shifting the start and end points of the gradient by the
// size of the highlight prevents this.
gradientLayer.startPoint = CGPoint(x: -gradientHighlightRatio, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1+gradientHighlightRatio, y: 0.5)
gradientLayer.locations = startLocations
gradientLayer.colors = gradientColors
let animationKeyPath = "locations"
let shimmerAnimation = CABasicAnimation(keyPath: animationKeyPath)
shimmerAnimation.fromValue = startLocations
shimmerAnimation.toValue = endLocations
shimmerAnimation.duration = animationDuration
shimmerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
let animationGroup = CAAnimationGroup()
animationGroup.duration = animationDuration + animationDelay
animationGroup.repeatCount = .infinity
animationGroup.animations = [shimmerAnimation]
// removes animation with same key (if exists) then adds
// the new animation
gradientLayer.removeAnimation(forKey: animationKeyPath)
gradientLayer.add(animationGroup, forKey: animationKeyPath)
In the UIButton subclass I've added property observers to each of the properties that calls addShimmerAnimation()
with any property change.
I also considered just supplying default values and requiring addShimmerAnimation()
to be called manually once properties were configured. Another route was not having any public properties exposed and instead passing everything in through an initializer, but that would remove the possibility of these classes being used in a storyboard (which is an option I like to leave open) and having properties exposed through tagging the properties with IBInspectable
.
class ShimmerButton: UIButton, ShimmerEffect
override static var layerClass: AnyClass
return CAGradientLayer.self
var gradientLayer: CAGradientLayer
return layer as! CAGradientLayer
var animationDuration: TimeInterval = 3
didSet addShimmerAnimation()
var animationDelay: TimeInterval = 1.5
didSet addShimmerAnimation()
var gradientHighlightRatio: Double = 0.3
didSet addShimmerAnimation()
var gradientTint: UIColor = .black
didSet addShimmerAnimation()
var gradientHighlight: UIColor = .white
didSet addShimmerAnimation()
override init(frame: CGRect)
super.init(frame: frame)
gradientLayer.mask = titleLabel?.layer
addShimmerAnimation()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
gradientLayer.mask = titleLabel?.layer
addShimmerAnimation()
Example usage:
let shimmer = ShimmerButton()
shimmer.setTitle("Find new skills", for: .normal)
shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
shimmer.gradientTint = darkBlue
shimmer.gradientHighlight = lightBlue
shimmer.sizeToFit()
What I like about this approach is that the complexity is moved out of the subclass making it super easy to duplicate over other views.
What frustrates me is that UIView
, UILabel
and UIButton
only have minor differences. I wish there was a way for the computed properties to be extracted into a common place.
Example of ShimmerButton
and ShimmerView
(a UIView
subclass) being used together:
$endgroup$
add a comment |
$begingroup$
Okay, so sometimes posting a question is the best way to figure out an answer yourself 🙃.
I was looking for an alternative to having the gradient colors evenly spaced (my hack was to repeat the colors e.g. "blue blue red blue blue") and found the locations
property on CAGradientLayer
which is also animatable.
Animating this property feels like a much better approach because with the position not changing I can remove the wrapper layer.
Then, with without the wrapper layer I realized that I could override layerClass
so the buttons backing layer is a gradient layer, which will also resize as needed when the view frame changes so that I don't even need to override layoutSubviews
.
The only thing that feels a little strange is the forced requirement for for gradient colors to have three colors (otherwise I'd need to figure some formula to derive values for locations
).
Edit: I've updated answer so instead of directly setting the colors, I've exposed gradientTint
and gradientHighlight
properties that are used to set the gradients colors
array.
I've created a protocol that captures the properties used to define the shimmer effect, and also provide a default implementation of the animation.
It wasn't a requirement from my original question, but moving this code out of a specific subclass makes this snippet of code reusable (and maintainable) across other subclasses (e.g. UIView, UILabel).
protocol ShimmerEffect
var animationDuration: TimeInterval set get
var animationDelay: TimeInterval set get
var gradientTint: UIColor set get
var gradientHighlight: UIColor set get
//// Expects value between 0.0—1.0 that represents
//// the ratio of the gradient highlight to the full
//// width of the gradient.
var gradientHighlightRatio: Double set get
//// The layer that the gradient will be applied to
var gradientLayer: CAGradientLayer get
Default implementation:
extension ShimmerEffect
/// Configures, and adds the animation to the gradientLayer
func addShimmerAnimation()
// `gradientHighlightRatio` represents how wide the highlight
// should be compared to the entire width of the gradient and
// is used to calculate the positions of the 3 gradient colors.
// If the highlight is 20% width of the gradient, then the
// 'start locations' would be [-0.2, -0.1, 0.0] and the
// 'end locations' would be [1.0, 1.1, 1.2]
let startLocations = [NSNumber(value: -gradientHighlightRatio), NSNumber(value: -gradientHighlightRatio/2), 0.0]
let endLocations = [1, NSNumber(value: 1+(gradientHighlightRatio/2)), NSNumber(value: 1+gradientHighlightRatio)]
let gradientColors = [gradientTint.cgColor, gradientHighlight.cgColor, gradientTint.cgColor]
// If the gradient highlight ratio is wide, then it can
// 'bleed' over into the visible space of the view, which
// looks particularly bad if there is a pause between the
// animation repeating.
// Shifting the start and end points of the gradient by the
// size of the highlight prevents this.
gradientLayer.startPoint = CGPoint(x: -gradientHighlightRatio, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1+gradientHighlightRatio, y: 0.5)
gradientLayer.locations = startLocations
gradientLayer.colors = gradientColors
let animationKeyPath = "locations"
let shimmerAnimation = CABasicAnimation(keyPath: animationKeyPath)
shimmerAnimation.fromValue = startLocations
shimmerAnimation.toValue = endLocations
shimmerAnimation.duration = animationDuration
shimmerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
let animationGroup = CAAnimationGroup()
animationGroup.duration = animationDuration + animationDelay
animationGroup.repeatCount = .infinity
animationGroup.animations = [shimmerAnimation]
// removes animation with same key (if exists) then adds
// the new animation
gradientLayer.removeAnimation(forKey: animationKeyPath)
gradientLayer.add(animationGroup, forKey: animationKeyPath)
In the UIButton subclass I've added property observers to each of the properties that calls addShimmerAnimation()
with any property change.
I also considered just supplying default values and requiring addShimmerAnimation()
to be called manually once properties were configured. Another route was not having any public properties exposed and instead passing everything in through an initializer, but that would remove the possibility of these classes being used in a storyboard (which is an option I like to leave open) and having properties exposed through tagging the properties with IBInspectable
.
class ShimmerButton: UIButton, ShimmerEffect
override static var layerClass: AnyClass
return CAGradientLayer.self
var gradientLayer: CAGradientLayer
return layer as! CAGradientLayer
var animationDuration: TimeInterval = 3
didSet addShimmerAnimation()
var animationDelay: TimeInterval = 1.5
didSet addShimmerAnimation()
var gradientHighlightRatio: Double = 0.3
didSet addShimmerAnimation()
var gradientTint: UIColor = .black
didSet addShimmerAnimation()
var gradientHighlight: UIColor = .white
didSet addShimmerAnimation()
override init(frame: CGRect)
super.init(frame: frame)
gradientLayer.mask = titleLabel?.layer
addShimmerAnimation()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
gradientLayer.mask = titleLabel?.layer
addShimmerAnimation()
Example usage:
let shimmer = ShimmerButton()
shimmer.setTitle("Find new skills", for: .normal)
shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
shimmer.gradientTint = darkBlue
shimmer.gradientHighlight = lightBlue
shimmer.sizeToFit()
What I like about this approach is that the complexity is moved out of the subclass making it super easy to duplicate over other views.
What frustrates me is that UIView
, UILabel
and UIButton
only have minor differences. I wish there was a way for the computed properties to be extracted into a common place.
Example of ShimmerButton
and ShimmerView
(a UIView
subclass) being used together:
$endgroup$
Okay, so sometimes posting a question is the best way to figure out an answer yourself 🙃.
I was looking for an alternative to having the gradient colors evenly spaced (my hack was to repeat the colors e.g. "blue blue red blue blue") and found the locations
property on CAGradientLayer
which is also animatable.
Animating this property feels like a much better approach because with the position not changing I can remove the wrapper layer.
Then, with without the wrapper layer I realized that I could override layerClass
so the buttons backing layer is a gradient layer, which will also resize as needed when the view frame changes so that I don't even need to override layoutSubviews
.
The only thing that feels a little strange is the forced requirement for for gradient colors to have three colors (otherwise I'd need to figure some formula to derive values for locations
).
Edit: I've updated answer so instead of directly setting the colors, I've exposed gradientTint
and gradientHighlight
properties that are used to set the gradients colors
array.
I've created a protocol that captures the properties used to define the shimmer effect, and also provide a default implementation of the animation.
It wasn't a requirement from my original question, but moving this code out of a specific subclass makes this snippet of code reusable (and maintainable) across other subclasses (e.g. UIView, UILabel).
protocol ShimmerEffect
var animationDuration: TimeInterval set get
var animationDelay: TimeInterval set get
var gradientTint: UIColor set get
var gradientHighlight: UIColor set get
//// Expects value between 0.0—1.0 that represents
//// the ratio of the gradient highlight to the full
//// width of the gradient.
var gradientHighlightRatio: Double set get
//// The layer that the gradient will be applied to
var gradientLayer: CAGradientLayer get
Default implementation:
extension ShimmerEffect
/// Configures, and adds the animation to the gradientLayer
func addShimmerAnimation()
// `gradientHighlightRatio` represents how wide the highlight
// should be compared to the entire width of the gradient and
// is used to calculate the positions of the 3 gradient colors.
// If the highlight is 20% width of the gradient, then the
// 'start locations' would be [-0.2, -0.1, 0.0] and the
// 'end locations' would be [1.0, 1.1, 1.2]
let startLocations = [NSNumber(value: -gradientHighlightRatio), NSNumber(value: -gradientHighlightRatio/2), 0.0]
let endLocations = [1, NSNumber(value: 1+(gradientHighlightRatio/2)), NSNumber(value: 1+gradientHighlightRatio)]
let gradientColors = [gradientTint.cgColor, gradientHighlight.cgColor, gradientTint.cgColor]
// If the gradient highlight ratio is wide, then it can
// 'bleed' over into the visible space of the view, which
// looks particularly bad if there is a pause between the
// animation repeating.
// Shifting the start and end points of the gradient by the
// size of the highlight prevents this.
gradientLayer.startPoint = CGPoint(x: -gradientHighlightRatio, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1+gradientHighlightRatio, y: 0.5)
gradientLayer.locations = startLocations
gradientLayer.colors = gradientColors
let animationKeyPath = "locations"
let shimmerAnimation = CABasicAnimation(keyPath: animationKeyPath)
shimmerAnimation.fromValue = startLocations
shimmerAnimation.toValue = endLocations
shimmerAnimation.duration = animationDuration
shimmerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
let animationGroup = CAAnimationGroup()
animationGroup.duration = animationDuration + animationDelay
animationGroup.repeatCount = .infinity
animationGroup.animations = [shimmerAnimation]
// removes animation with same key (if exists) then adds
// the new animation
gradientLayer.removeAnimation(forKey: animationKeyPath)
gradientLayer.add(animationGroup, forKey: animationKeyPath)
In the UIButton subclass I've added property observers to each of the properties that calls addShimmerAnimation()
with any property change.
I also considered just supplying default values and requiring addShimmerAnimation()
to be called manually once properties were configured. Another route was not having any public properties exposed and instead passing everything in through an initializer, but that would remove the possibility of these classes being used in a storyboard (which is an option I like to leave open) and having properties exposed through tagging the properties with IBInspectable
.
class ShimmerButton: UIButton, ShimmerEffect
override static var layerClass: AnyClass
return CAGradientLayer.self
var gradientLayer: CAGradientLayer
return layer as! CAGradientLayer
var animationDuration: TimeInterval = 3
didSet addShimmerAnimation()
var animationDelay: TimeInterval = 1.5
didSet addShimmerAnimation()
var gradientHighlightRatio: Double = 0.3
didSet addShimmerAnimation()
var gradientTint: UIColor = .black
didSet addShimmerAnimation()
var gradientHighlight: UIColor = .white
didSet addShimmerAnimation()
override init(frame: CGRect)
super.init(frame: frame)
gradientLayer.mask = titleLabel?.layer
addShimmerAnimation()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
gradientLayer.mask = titleLabel?.layer
addShimmerAnimation()
Example usage:
let shimmer = ShimmerButton()
shimmer.setTitle("Find new skills", for: .normal)
shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
shimmer.gradientTint = darkBlue
shimmer.gradientHighlight = lightBlue
shimmer.sizeToFit()
What I like about this approach is that the complexity is moved out of the subclass making it super easy to duplicate over other views.
What frustrates me is that UIView
, UILabel
and UIButton
only have minor differences. I wish there was a way for the computed properties to be extracted into a common place.
Example of ShimmerButton
and ShimmerView
(a UIView
subclass) being used together:
edited Mar 21 '17 at 17:56
answered Mar 20 '17 at 20:51
MathewSMathewS
568311
568311
add a comment |
add a comment |
$begingroup$
Shimmer animation can be added like below in iOS
class ViewController: UIViewController
@IBOutlet var label: UILabel!
let gradientLayer = CAGradientLayer()
override func viewDidLoad()
super.viewDidLoad()
self.gradientLayer.frame = self.label.bounds
self.gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
self.gradientLayer.colors = [UIColor.red.cgColor, UIColor.white.cgColor, UIColor.darkGray.cgColor]
let startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
let endLocations : [NSNumber] = [1.0,1.5, 2.0]
self.gradientLayer.locations = startLocations
let animation = CABasicAnimation(keyPath: "locations")
animation.fromValue = startLocations
animation.toValue = endLocations
animation.duration = 0.8
animation.repeatCount = .infinity
self.gradientLayer.add(animation, forKey: animation.keyPath)
self.label.layer.addSublayer(self.gradientLayer)
DispatchQueue.main.asyncAfter(deadline: .now() + 5)
self.gradientLayer.removeAllAnimations()
New contributor
$endgroup$
add a comment |
$begingroup$
Shimmer animation can be added like below in iOS
class ViewController: UIViewController
@IBOutlet var label: UILabel!
let gradientLayer = CAGradientLayer()
override func viewDidLoad()
super.viewDidLoad()
self.gradientLayer.frame = self.label.bounds
self.gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
self.gradientLayer.colors = [UIColor.red.cgColor, UIColor.white.cgColor, UIColor.darkGray.cgColor]
let startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
let endLocations : [NSNumber] = [1.0,1.5, 2.0]
self.gradientLayer.locations = startLocations
let animation = CABasicAnimation(keyPath: "locations")
animation.fromValue = startLocations
animation.toValue = endLocations
animation.duration = 0.8
animation.repeatCount = .infinity
self.gradientLayer.add(animation, forKey: animation.keyPath)
self.label.layer.addSublayer(self.gradientLayer)
DispatchQueue.main.asyncAfter(deadline: .now() + 5)
self.gradientLayer.removeAllAnimations()
New contributor
$endgroup$
add a comment |
$begingroup$
Shimmer animation can be added like below in iOS
class ViewController: UIViewController
@IBOutlet var label: UILabel!
let gradientLayer = CAGradientLayer()
override func viewDidLoad()
super.viewDidLoad()
self.gradientLayer.frame = self.label.bounds
self.gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
self.gradientLayer.colors = [UIColor.red.cgColor, UIColor.white.cgColor, UIColor.darkGray.cgColor]
let startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
let endLocations : [NSNumber] = [1.0,1.5, 2.0]
self.gradientLayer.locations = startLocations
let animation = CABasicAnimation(keyPath: "locations")
animation.fromValue = startLocations
animation.toValue = endLocations
animation.duration = 0.8
animation.repeatCount = .infinity
self.gradientLayer.add(animation, forKey: animation.keyPath)
self.label.layer.addSublayer(self.gradientLayer)
DispatchQueue.main.asyncAfter(deadline: .now() + 5)
self.gradientLayer.removeAllAnimations()
New contributor
$endgroup$
Shimmer animation can be added like below in iOS
class ViewController: UIViewController
@IBOutlet var label: UILabel!
let gradientLayer = CAGradientLayer()
override func viewDidLoad()
super.viewDidLoad()
self.gradientLayer.frame = self.label.bounds
self.gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
self.gradientLayer.colors = [UIColor.red.cgColor, UIColor.white.cgColor, UIColor.darkGray.cgColor]
let startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
let endLocations : [NSNumber] = [1.0,1.5, 2.0]
self.gradientLayer.locations = startLocations
let animation = CABasicAnimation(keyPath: "locations")
animation.fromValue = startLocations
animation.toValue = endLocations
animation.duration = 0.8
animation.repeatCount = .infinity
self.gradientLayer.add(animation, forKey: animation.keyPath)
self.label.layer.addSublayer(self.gradientLayer)
DispatchQueue.main.asyncAfter(deadline: .now() + 5)
self.gradientLayer.removeAllAnimations()
New contributor
New contributor
answered 1 min ago
Alok SInhaAlok SInha
1011
1011
New contributor
New contributor
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f158336%2fuibutton-subclass-with-animated-shimmer-effect%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown