my question is quite straight forward. I am aiming to add a basic glow effect to a button in swift. I want the text to glow, not the entire button box.
I have attached an image as an example to illustrate what I am aiming to achieve.
I have looked elsewhere but typically only find animations which is not what I want. Sorry the image is of poor quality.
I am currently using this code but my settings button appears with a very weak glow, how can I make it stronger:
import UIKit
extension UIView {
enum GlowEffect: Float {
case small = 0.4, normal = 2, big = 30
}
func doGlowAnimation(withColor color: UIColor, withEffect effect: GlowEffect = .normal) {
layer.masksToBounds = false
layer.shadowColor = color.cgColor
layer.shadowRadius = 0
layer.shadowOpacity = 1
layer.shadowOffset = .zero
let glowAnimation = CABasicAnimation(keyPath: "shadowRadius")
glowAnimation.fromValue = 20 // effect.rawValue
glowAnimation.toValue = 20
glowAnimation.fillMode = .removed
glowAnimation.repeatCount = .infinity
layer.add(glowAnimation, forKey: "shadowGlowingAnimation")
}
}
Changing the intensity doesn't give that strong color effect near the individual letters
To create an outer glow effect on the title of a UIButton you'll want to make sure you adjust the shadow properties of the UIButton's titleLabel. Meaning you could run your animation by saying:
button.titleLabel?.doGlowAnimation(withColor: UIColor.yellow)
The animation adjusts shadowRadius though currently goes from 20 to 20 so there's no actual animation.
extension UIView {
enum GlowEffect: Float {
case small = 0.4, normal = 2, big = 30
}
func doGlowAnimation(withColor color: UIColor, withEffect effect: GlowEffect = .normal) {
layer.masksToBounds = false
layer.shadowColor = color.cgColor
layer.shadowRadius = 0
layer.shadowOpacity = 0.8
layer.shadowOffset = .zero
let glowAnimation = CABasicAnimation(keyPath: "shadowRadius")
glowAnimation.fromValue = 0
glowAnimation.toValue = effect.rawValue
glowAnimation.fillMode = .removed
glowAnimation.repeatCount = .infinity
glowAnimation.duration = 2
glowAnimation.autoreverses = true
layer.add(glowAnimation, forKey: "shadowGlowingAnimation")
}
}
Provides a pulsating outer glow, growing over the course of two seconds then reversing.
Related
I'm trying to light up a sequence of buttons in order with a small delay in between but I just can't figure out how to glow each button separately with a small delay in between without freezing all code.
at this point I got this, which waits a second and for some reason lights up both buttons at the same time after.
The array given to the method contains values from 1-3 referencing one of the 3 buttons in order
private func showSequence(sequence: Array<Int>){
for i in sequence {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.buttonArray[i-1].doGlowAnimation(withColor: .white, withEffect: .big)
}
}
}
And the glow effect I found online, code:
extension UIView {
enum GlowEffect: Float {
case small = 0.4, normal = 2, big = 15
}
func doGlowAnimation(withColor color: UIColor, withEffect effect: GlowEffect = .normal) {
layer.masksToBounds = false
layer.shadowColor = color.cgColor
layer.shadowRadius = 0
layer.shadowOpacity = 1
layer.shadowOffset = .zero
let glowAnimation = CABasicAnimation(keyPath: "shadowRadius")
glowAnimation.fromValue = 0
glowAnimation.toValue = effect.rawValue
glowAnimation.beginTime = CACurrentMediaTime()+0.3
glowAnimation.duration = CFTimeInterval(0.3)
glowAnimation.fillMode = .removed
glowAnimation.autoreverses = true
glowAnimation.isRemovedOnCompletion = true
layer.add(glowAnimation, forKey: "shadowGlowingAnimation")
}
}
I am try create effect like few view stack over other for collection view, I am not build effect like this in the past and can't understand how I can create this effect programmatically without images?
Can somebody explain me how I can repeat this effect or post link to code of tutorial?
Example of this effect below:
To get that effect you'd need to add layers under the collection view cells' layers that are smaller and shifted down a little from the cell's layers. You'd use the same background color as the cell on each layer that had a lower alpha than the cell's layer.
I created a sample project that demonstrates how to get the effect:
https://github.com/DuncanMC/CustomCollectionViewCell.git
The cells look like this:
The heavy lifting is in a custom subclass of UICollectionViewCell that I called MyCollectionViewCell
Here is that class:
class MyCollectionViewCell: UICollectionViewCell {
public var contentCornerRadius: CGFloat = 0 {
didSet {
contentView.layer.cornerRadius = contentCornerRadius
sizeLayerFrames()
}
}
public var fraction: CGFloat = 0.075 { // What percent to shrink & shift the faded layers down (0.075 = 7.5%)
didSet {
sizeLayerFrames()
}
}
private var layerMask = CAShapeLayer()
private var layer1 = CALayer()
private var layer2 = CALayer()
// Use this function to set the cell's background color.
// (You can't set the view's background color, since we Don't clip the view to it's bounds.)
// Be sure to set the background color explicitly, since by default it sets a random color that will persist
// as the cells are recylced, causing your cell colors to move around as the user scrolls
public func setBackgroundColor(_ color: UIColor) {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
color.getRed(&red, green: &green, blue: &blue, alpha: nil)
contentView.layer.backgroundColor = color.cgColor
//Make the first extra layer have the same color as the cell's layer, but with alpha 0.25
layer1.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 0.25).cgColor
//Make the second extra layer have the same color as the cell's layer, but with alpha 0.125
layer2.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 0.125).cgColor
}
#IBOutlet weak var customLabel: UILabel!
//Do The initial setup once the cell is loaded.
//Note that t
override func awakeFromNib() {
contentView.layer.masksToBounds = false
// Color each cell's layer some random hue (change to set whatever color you desire.)
//For testing, use a color based on a random hue and fairly high random brightness.
let hue = CGFloat.random(in: 0...360)
let brightness = CGFloat.random(in: 0.8...1.0)
layer.masksToBounds = false
setBackgroundColor(UIColor(hue: hue, saturation: 1, brightness: brightness, alpha: 1))
// Make the inside of the shape layer white (opaque), The color doesn't matter - just that the alpha value is 1
// and the outside clear (transparent)
layerMask.fillColor = UIColor.white.cgColor
layerMask.backgroundColor = UIColor.clear.cgColor
//With the even/odd rule, the inner shape will not be filled (we'll only fill the part NOT in the inner shape)
layerMask.fillRule = .evenOdd
contentCornerRadius = 30
sizeLayerFrames()
contentView.layer.addSublayer(layer1)
layer1.addSublayer(layer2)
layer1.mask = layerMask
}
private func sizeLayerFrames() {
layer1.cornerRadius = contentCornerRadius
layer2.cornerRadius = contentCornerRadius
let viewBounds = bounds //Use the layer's bounds as the starting point for the extra layers.
var frame1 = viewBounds
frame1.origin.y += viewBounds.size.height * fraction
frame1.origin.x += viewBounds.size.width * fraction
frame1.size.width *= CGFloat(1 - 2 * fraction)
layer1.frame = frame1
var frame2 = viewBounds
frame2.origin.y += viewBounds.size.height * 0.75 * fraction
frame2.origin.x += viewBounds.size.width * fraction
frame2.size.width *= CGFloat(1 - 4 * fraction)
layer2.frame = frame2
//Create a mask layer to clip the extra layers.
var maskFrame = viewBounds
//We are going to install the mask on layer1, so offeset the frame to cover the whole view contents
maskFrame.origin.y -= viewBounds.size.height * fraction
maskFrame.origin.x -= viewBounds.size.width * fraction
maskFrame.size.height += viewBounds.size.height * fraction * 1.75
layerMask.frame = maskFrame
maskFrame = viewBounds
let innerPath = UIBezierPath(roundedRect: maskFrame, cornerRadius: 30)
maskFrame.size.height += viewBounds.size.height * fraction * 1.75
let combinedPath = UIBezierPath(rect: maskFrame)
combinedPath.append(innerPath)
layerMask.path = combinedPath.cgPath
}
override var bounds: CGRect {
didSet {
sizeLayerFrames()
}
}
}
Let's say I have an animator that moves a view from (0, 0) to (-120, 0):
let frameAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.8)
animator.addAnimations {
switch state:
case .normal: view.frame.origin.x = 0
case .swiped: view.frame.origin.x = -120
}
}
I use it together with UIPanGestureRecognizer, so that I can resize the view continuously along with the finger movements.
The issue comes when I want to add some sort of bouncing effect at the start or at the end of the animation. NOT just the damping ratio, but the bounce effect. The easiest way to imagine this is Swipe-To-Delete feature of UITableViewCell, where you can drag "Delete" button beyond its actual width, and then it bounces back.
Effectively what I want to achieve, is the way to set fractionComplete property outside of [0, 1] segment, so when the fraction is 1.2, the offset becomes 144 instead of its 120 maximum.
And right now the maximum value for fractionComplete is exactly 1.
Below are some examples to have this issue visualized:
What I currently have:
What I want to achieve:
EDIT (19 January):
Sorry for my delayed reply. Here are some clarifications:
I don't use UIView.animate(...), and use UIViewPropertyAnimator instead for a very specific reason: it handles for me all the timings, curves and velocities.
For example, you dragged the view halfway through. This means that duration of the remaining part should be two times less than total duration. Or if you dragged though the 99% of the distance, it should complete the remaining part almost instantly.
As an addition, UIViewPropertyAnimator has such features as pause (when user starts dragging once again), or reverse (when user started dragging to the left, but after that he changed his mind and moved the finger to the right), that I also benefit from.
All this is not available for simple UIView animations, or requires TONS of effort at best. It is only capable of simple transitions, and this is not the case.
That's why I have to use some sort of animator.
And as I mentioned in the comments thread in the answer that was removed by its publisher, the most complex part for me here is to simulate the friction effect: the further you drag, the less the view actually moves. Just as when you're trying to drag any UIScrollView outside of it's content.
Thanks for your effort guys, but I don't think any of these 2 answers is relevant. I will try to implement this behaviour using UIDynamicAnimator whenever I have time. Probably in the nearest week or two. I will publish my approach in case I have any decent results.
EDIT (20 January):
I just uploaded a demo project to the GitHub, which includes all the transitions that I have in my project. So now you can actually have an idea why do I need to use animators and how I use them: https://github.com/demon9733/bouncingview-prototype
The only file you are actually interested in is MainViewController+Modes.swift. Everything related to transitions and animations is contained there.
What I need to do is to enable user to drag the handle area beyond "Hide" button width with a damping effect. "Hide" button will appear on swiping the handle area to the left.
P.S. I didn't really test this demo, so it can have bugs that I don't have in my main project. So you can safely ignore them.
you need to allow pan gesture to get to needed x position and at the end of pan an animation is needed to be triggered
one way to do this would be:
var initial = CGRect.zero
override func viewDidLayoutSubviews() {
initial = animatedView.frame
}
#IBAction func pan(_ sender: UIPanGestureRecognizer) {
let closed = initial
let open = initial.offsetBy(dx: -120, dy: 0)
// 1 manage panning along x direction
sender.view?.center = CGPoint(x: (sender.view?.center.x)! + sender.translation(in: sender.view).x, y: (sender.view?.center.y)! )
sender.setTranslation(CGPoint.zero, in: self.view)
// 2 animate to needed position once pan ends
if sender.state == .ended {
if (sender.view?.frame.origin.x)! > initialOrigin.origin.x {
UIView.animate(withDuration: 1 , animations: {
sender.view?.frame = closed
})
} else {
UIView.animate(withDuration: 1 , animations: {
sender.view?.frame = open
})
}
}
}
Edit 20 Jan
For simulating dampening effect and make use of UIViewPropertyAnimator specifically,
var initialOrigin = CGRect.zero
override func viewDidLayoutSubviews() {
initialOrigin = animatedView.frame
}
#IBAction func pan(_ sender: UIPanGestureRecognizer) {
let closed = initialOrigin
let open = initialOrigin.offsetBy(dx: -120, dy: 0)
// 1. to simulate dampening
var multiplier: CGFloat = 1.0
if animatedView?.frame.origin.x ?? CGFloat(0) > closed.origin.x || animatedView?.frame.origin.x ?? CGFloat(0) < open.origin.x {
multiplier = 0.2
} else {
multiplier = 1
}
// 2. animate panning
sender.view?.center = CGPoint(x: (sender.view?.center.x)! + sender.translation(in: sender.view).x * multiplier, y: (sender.view?.center.y)! )
sender.setTranslation(CGPoint.zero, in: self.view)
// 3. animate to needed position once pan ends
if sender.state == .ended {
if (sender.view?.frame.origin.x)! > initialOrigin.origin.x {
let animate = UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: {
self.animatedView.frame.origin.x = closed.origin.x
})
animate.startAnimation()
} else {
let animate = UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: {
self.animatedView.frame.origin.x = open.origin.x
})
animate.startAnimation()
}
}
}
Here is possible approach (simplified & a bit scratchy - only bounce, w/o button at right, because it would much more code and actually only a matter of frames management)
Due to long delay of UIPanGestureRecognizer at ending, I prefer to use UILongPressGestureRecognizer, as it gives faster feedback.
Here is demo result
The Storyboard of used below ViewController has only gray-background-rect-container view, everything else is done in code provided below.
class ViewController: UIViewController {
#IBOutlet weak var container: UIView!
let imageView = UIImageView()
var initial: CGFloat = .zero
var dropped = false
private func excedesLimit() -> Bool {
// < set here desired bounce limits
return imageView.frame.minX < -180 || imageView.frame.minX > 80
}
#IBAction func pressHandler(_ sender: UILongPressGestureRecognizer) {
let location = sender.location(in: imageView.superview).x
if sender.state == .began {
dropped = false
initial = location - imageView.center.x
}
else if !dropped {
if (sender.state == .changed) {
imageView.center = CGPoint(x: location - initial, y: imageView.center.y)
dropped = excedesLimit()
}
if sender.state == .ended || dropped {
initial = .zero
// variant with animator
let animator = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut) {
let stickTo: CGFloat = self.imageView.frame.minX < -100 ? -100 : 0 // place for button at right
self.imageView.frame = CGRect(origin: CGPoint(x: stickTo, y: self.imageView.frame.origin.y), size: self.imageView.frame.size)
}
animator.isInterruptible = true
animator.startAnimation()
// uncomment below - variant with UIViewAnimation
// UIView.beginAnimations("bounce", context: nil)
// UIView.setAnimationDuration(0.2)
// UIView.setAnimationTransition(.none, for: imageView, cache: true)
// UIView.setAnimationBeginsFromCurrentState(true)
//
// let stickTo: CGFloat = imageView.frame.minX < -100 ? -100 : 0 // place for button at right
// imageView.frame = CGRect(origin: CGPoint(x: stickTo, y: imageView.frame.origin.y), size: imageView.frame.size)
// UIView.setAnimationDelegate(self)
// UIView.setAnimationDidStop(#selector(makeBounce))
// UIView.commitAnimations()
}
}
}
// #objc func makeBounce() {
// let bounceAnimation = CABasicAnimation(keyPath: "position.x")
// bounceAnimation.duration = 0.1
// bounceAnimation.repeatCount = 0
// bounceAnimation.autoreverses = true
// bounceAnimation.fillMode = kCAFillModeBackwards
// bounceAnimation.isRemovedOnCompletion = true
// bounceAnimation.isAdditive = false
// bounceAnimation.timingFunction = CAMediaTimingFunction(name: "easeOut")
// imageView.layer.add(bounceAnimation, forKey:"bounceAnimation");
// }
override func viewDidLoad() {
super.viewDidLoad()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(named: "cat")
imageView.contentMode = .scaleAspectFill
imageView.layer.borderColor = UIColor.red.cgColor
imageView.layer.borderWidth = 1.0
imageView.clipsToBounds = true
imageView.isUserInteractionEnabled = true
container.addSubview(imageView)
imageView.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true
imageView.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 1).isActive = true
imageView.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 1).isActive = true
let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(pressHandler(_:)))
pressGesture.minimumPressDuration = 0
pressGesture.allowableMovement = .infinity
imageView.addGestureRecognizer(pressGesture)
}
}
I’m trying to add a label to an SCNNode. So far I’ve managed to set a SKLabelNode as the material for the SCNNode. It sort of works, I can the SCNNode becomes the background colour of the SKLabelNode but I can’t see the text. Sometime I can see a red haze (the text colour is red) but no readable text.
I also tried setting the material as a UIView and adding a UiLabel as a sub view. Again it sets as I can the whole SCNNode becomes the background colour of the UiLabel but I can’t see any text.
var block = SCNNode()
var spriteScene = SKScene()
var Lbl = SKLabelNode()
lbl.text = “Hello World”
lbl.color = blue (playground colour literal)
lbl. = 2 //I tried various numbers
lbl.fontColor = SKColor.red
spriteScene.addChild(lbl)
I got it after some hit and trial. I had to try different values before I got these size, scale and rotation to display the label as I want.
note: My node here is SCNPlane with 0.3 width and 0.2 height, so size of SKScene and rectangle and position of label are hard coded accordingly.
func addLabel(text: String){
let sk = SKScene(size: CGSize(width: 3000, height: 2000))
sk.backgroundColor = UIColor.clear
let rectangle = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 3000, height: 2000), cornerRadius: 10)
rectangle.fillColor = UIColor.black
rectangle.strokeColor = UIColor.white
rectangle.lineWidth = 5
rectangle.alpha = 0.5
let lbl = SKLabelNode(text: text)
lbl.fontSize = 160
lbl.numberOfLines = 0
lbl.fontColor = UIColor.white
lbl.fontName = "Helvetica-Bold"
lbl.position = CGPoint(x:1500,y:1000)
lbl.preferredMaxLayoutWidth = 2900
lbl.horizontalAlignmentMode = .center
lbl.verticalAlignmentMode = .center
lbl.zRotation = .pi
sk.addChild(rectangle)
sk.addChild(lbl)
let material = SCNMaterial()
material.isDoubleSided = true
material.diffuse.contents = sk
node.geometry?.materials = [material]
node.geometry?.firstMaterial?.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(1), Float(1), 1)
node.geometry?.firstMaterial?.diffuse.wrapS = .repeat
node.geometry?.firstMaterial?.diffuse.wrapS = .repeat
}
I have looked around about this before but have yet to find an answer. I've been at this for a while and need to know how to animate a LINE as opposed to a rectangle.
From what I can see the animation is very different going from a stroke to a box. Just need some pointers here-
I have a border that is drawn on view load (not with an animation, it just draws it) here:
override public func drawRect(rect: CGRect){
super.drawRect(rect)
let borderColor = self.hasError! ? kDefaultActiveColor : kDefaultErrorColor
let textRect = self.textRectForBounds(rect)
let context = UIGraphicsGetCurrentContext()
let borderlines : [CGPoint] = [CGPointMake(0, CGRectGetHeight(textRect) - 1),
CGPointMake(CGRectGetWidth(textRect), CGRectGetHeight(textRect) - 1)]
if self.enabled {
CGContextBeginPath(context);
CGContextAddLines(context, borderlines, 2);
CGContextSetLineWidth(context, 1.3);
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
CGContextStrokePath(context);
I got this from another answer and am trying to take it apart. I need the line to animate/grow out from its centre when the view loads, as in grow to the same width and size as it is drawn here but animated.
I do not know how to accomplish this, however another answer achieved the effect I need by setting the border to a size of zero here:
self.activeBorder = UIView(frame: CGRectZero)
self.activeBorder.backgroundColor = kDefaultActiveColor
//self.activeBorder.backgroundColor = kDefaultActiveBorderColor
self.activeBorder.layer.opacity = 0
self.addSubview(self.activeBorder)
and animating using:
self.borderlines.transform = CATransform3DMakeScale(CGFloat(0.01), CGFloat(1.0), 1)
self.activeBorder.layer.opacity = 1
CATransaction.begin()
self.activeBorder.layer.transform = CATransform3DMakeScale(CGFloat(0.03), CGFloat(1.0), 1)
let anim2 = CABasicAnimation(keyPath: "transform")
let fromTransform = CATransform3DMakeScale(CGFloat(0.01), CGFloat(1.0), 1)
let toTransform = CATransform3DMakeScale(CGFloat(1.0), CGFloat(1.0), 1)
anim2.fromValue = NSValue(CATransform3D: fromTransform)
anim2.toValue = NSValue(CATransform3D: toTransform)
anim2.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
anim2.fillMode = kCAFillModeForwards
anim2.removedOnCompletion = false
self.activeBorder.layer.addAnimation(anim2, forKey: "_activeBorder")
CATransaction.commit()
This seems to be using entirely different code than my original drawRect and I don't know how to combine the two. Where am I going wrong here? How can I draw out from the centre my first border as I do the second, active border?
Use CAShapeLater, UIBezierPath and CABasicAnimation to animatedly draw line.
Here is the sample code:
class SampleView: UIView {
override public func drawRect(rect: CGRect) {
let leftPath = UIBezierPath()
leftPath.moveToPoint(CGPointMake(CGRectGetMidX(frame), CGRectGetHeight(frame)))
leftPath.addLineToPoint(CGPointMake(0, CGRectGetHeight(frame)))
let rightPath = UIBezierPath()
rightPath.moveToPoint(CGPointMake(CGRectGetMidX(frame), CGRectGetHeight(frame)))
rightPath.addLineToPoint(CGPointMake(CGRectGetWidth(frame), CGRectGetHeight(frame)))
let leftShapeLayer = CAShapeLayer()
leftShapeLayer.path = leftPath.CGPath
leftShapeLayer.strokeColor = UIColor.redColor().CGColor;
leftShapeLayer.lineWidth = 1.3
leftShapeLayer.strokeEnd = 0
layer.addSublayer(leftShapeLayer)
let rightShpaeLayer = CAShapeLayer()
rightShpaeLayer.path = rightPath.CGPath
rightShpaeLayer.strokeColor = UIColor.redColor().CGColor;
rightShpaeLayer.lineWidth = 1.3
rightShpaeLayer.strokeEnd = 0
layer.addSublayer(rightShpaeLayer)
let drawLineAnimation = CABasicAnimation(keyPath: "strokeEnd")
drawLineAnimation.toValue = NSNumber(float: 1)
drawLineAnimation.duration = 1
drawLineAnimation.fillMode = kCAFillModeForwards
drawLineAnimation.removedOnCompletion = false
leftShapeLayer.addAnimation(drawLineAnimation, forKey: nil)
rightShpaeLayer.addAnimation(drawLineAnimation, forKey: nil)
}
}
The code in viewDidLoad of ViewController
// ...
let sampleView = SampleView()
sampleView.frame = CGRectMake(10, 60, 240, 50)
sampleView.backgroundColor = UIColor.lightGrayColor()
view.addSubview(sampleView)
Here is the capture:
Note: If you run the code in iOS9 Simulator (I have not tested the earlier version), the animation may get blocked. Choose to run it in a real device or in iOS10 Simulator.