Here is two code for objective c and swift, both are the same.
Using objective c it animate scale correctly, but in swift it just jump to final value, i think this is a bug, can someone test and verify it?
Objective C
UIView *testView = [[UIView alloc] initWithFrame:CGRectMake(200, 200, 100, 100)];
testView.backgroundColor = [UIColor greenColor];
[self addSubview:testView];
CAKeyframeAnimation * transformAnim = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
transformAnim.values = #[[NSValue valueWithCATransform3D:CATransform3DMakeRotation(3 * M_PI/180, 0, 0, -1)],
[NSValue valueWithCATransform3D:CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(3 * M_PI/180, 0, 0, 1))],
[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.5, 1.5, 1)],
[NSValue valueWithCATransform3D:CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(-8 * M_PI/180, 0, 0, 1))]];
transformAnim.keyTimes = #[#0, #0.349, #0.618, #1];
transformAnim.duration = 1;
[testView.layer addAnimation:transformAnim forKey:#"test"];
Swift
let testView = UIView(frame: CGRectMake(200, 200, 100, 100))
testView.backgroundColor = UIColor.greenColor()
self.addSubview(testView)
var transformAnim = CAKeyframeAnimation(keyPath:"transform")
transformAnim.values = [NSValue(CATransform3D: CATransform3DMakeRotation(3 * CGFloat(M_PI/180), 0, 0, -1)),
NSValue(CATransform3D: CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(3 * CGFloat(M_PI/180), 0, 0, 1))),
NSValue(CATransform3D: CATransform3DMakeScale(1.5, 1.5, 1)),
NSValue(CATransform3D: CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(-8 * CGFloat(M_PI/180), 0, 0, 1)))]
transformAnim.keyTimes = [0, 0.349, 0.618, 1]
transformAnim.duration = 1
testView.layer.addAnimation(transformAnim, forKey: "transform")
This is objc anim, it works as intended
This is swift anim, scale transform just jump
I tested your code in both Objective-C and Swift and the behavior was exactly the same in both languages (it looks like your first gif animation in both cases).
Here's how to confirm my results. Your code didn't make sense to me (add a view and animate it all in one move?) so I broke it up into two button methods, and I moved it into the view controller. So, here it is in Objective-C:
#interface ViewController ()
#property (nonatomic) UIView* testView;
#end
#implementation ViewController
- (IBAction) doButton1 {
UIView *testView = [[UIView alloc] initWithFrame:CGRectMake(200, 200, 100, 100)];
testView.backgroundColor = [UIColor greenColor];
[self.view addSubview:testView];
self.testView = testView;
}
- (IBAction) doButton2 {
CAKeyframeAnimation * transformAnim = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
transformAnim.values = #[[NSValue valueWithCATransform3D:CATransform3DMakeRotation(3 * M_PI/180, 0, 0, -1)],
[NSValue valueWithCATransform3D:CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(3 * M_PI/180, 0, 0, 1))],
[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.5, 1.5, 1)],
[NSValue valueWithCATransform3D:CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(-8 * M_PI/180, 0, 0, 1))]];
transformAnim.keyTimes = #[#0, #0.349, #0.618, #1];
transformAnim.duration = 1;
[self.testView.layer addAnimation:transformAnim forKey:#"test"];
}
#end
And here it is in Swift:
class ViewController: UIViewController {
var testView : UIView!
#IBAction func doButton1() {
let testView = UIView(frame: CGRectMake(200, 200, 100, 100))
testView.backgroundColor = UIColor.greenColor()
self.view.addSubview(testView)
self.testView = testView
}
#IBAction func doButton2() {
var transformAnim = CAKeyframeAnimation(keyPath:"transform")
transformAnim.values = [NSValue(CATransform3D: CATransform3DMakeRotation(3 * CGFloat(M_PI/180), 0, 0, -1)),
NSValue(CATransform3D: CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(3 * CGFloat(M_PI/180), 0, 0, 1))),
NSValue(CATransform3D: CATransform3DMakeScale(1.5, 1.5, 1)),
NSValue(CATransform3D: CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(-8 * CGFloat(M_PI/180), 0, 0, 1)))]
transformAnim.keyTimes = [0, 0.349, 0.618, 1]
transformAnim.duration = 1
self.testView.layer.addAnimation(transformAnim, forKey: "transform")
}
}
Now, in either language, tap the first button (to add the view) and then the second button (to animate it).
Make sure to put
self.targetView.layoutIfNeeded()
Before calling the animation.
testView.layer.transform = CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(-8 * CGFloat(M_PI/180), 0, 0, 1))
testView.layer.addAnimation(transformAnim, forKey: "transform")
It works fine
I'm a bit late to this answer, but I just noticed this bug too. In Swift, it seems as if you can't add a subview immediately before animating it. It seems as if you have to insert a delay, ex:
let testView = UIView(frame: CGRectMake(200, 200, 100, 100))
testView.backgroundColor = UIColor.greenColor()
self.addSubview(testView)
var timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "animate", userInfo: nil, repeats: false)
}
func animate() {
var transformAnim = CAKeyframeAnimation(keyPath:"transform")
transformAnim.values = [NSValue(CATransform3D: CATransform3DMakeRotation(3 * CGFloat(M_PI/180), 0, 0, -1)),
NSValue(CATransform3D: CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(3 * CGFloat(M_PI/180), 0, 0, 1))),
NSValue(CATransform3D: CATransform3DMakeScale(1.5, 1.5, 1)),
NSValue(CATransform3D: CATransform3DConcat(CATransform3DMakeScale(1.5, 1.5, 1), CATransform3DMakeRotation(-8 * CGFloat(M_PI/180), 0, 0, 1)))]
transformAnim.keyTimes = [0, 0.349, 0.618, 1]
transformAnim.duration = 1
testView.layer.addAnimation(transformAnim, forKey: "transform")
}
Related
I'd like to add a lighting effect like these two buttons, a shadow on the bottom but also a lighted edge on top as if there were a light above it.
I've been experimenting with :
button.layer.shadowColor = UIColor.black.cgColor
button.layer.shadowColor.layer.shadowOffset = CGSize(width: 2, height: 2)
button.layer.shadowColor.layer.shadowRadius = 2
button.layer.shadowColor.layer.shadowOpacity = 8
button.layer.shadowColor.layer.masksToBounds = false
and get a nice shadow on the bottom, but how would I light up the top edge?
I was able to get a button pretty similar to your first drawing, just as an experiment:
The button background is constructed entirely using elementary drawing commands involving UIBezierPath and CGContext in a UIGraphicsImageRenderer. So if that's an acceptable sort of approach, you could just do a tweak on the sort of thing I'm doing.
let r = UIGraphicsImageRenderer(size: CGSize(width:100, height:100))
let im = r.image {
ctx in let con = ctx.cgContext
do {
let p = UIBezierPath(roundedRect: CGRect.init(x: 0, y: 0, width: 100, height: 50), cornerRadius: 10)
UIColor(white: 0.9, alpha: 1.0).setFill()
p.fill()
}
do {
let p = UIBezierPath(roundedRect: CGRect.init(x: 0, y: 50, width: 100, height: 50), cornerRadius: 10)
UIColor(white: 0.1, alpha: 1.0).setFill()
p.fill()
}
do {
let p = UIBezierPath(roundedRect: CGRect.init(x: 0, y: 2, width: 100, height: 95), cornerRadius: 10)
p.addClip()
}
let locs : [CGFloat] = [ 0.0, 0.2, 0.8, 1.0 ]
let colors : [CGFloat] = [
0.54, 0.54, 0.54, 1.0,
0.5, 0.5, 0.5, 1.0,
0.5, 0.5, 0.5, 1.0,
0.44, 0.44, 0.44, 1.0,
]
let sp = CGColorSpaceCreateDeviceRGB()
let grad = CGGradient(
colorSpace:sp, colorComponents: colors, locations: locs, count: 4)!
con.drawLinearGradient(grad,
start: CGPoint(x:0,y:0), end: CGPoint(x:0,y:100), options:[])
}
let b = UIButton(type: .custom)
b.setTitle("9", for: .normal)
b.setBackgroundImage(im, for: .normal)
b.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
b.titleLabel?.font = UIFont.systemFont(ofSize: 40, weight: .bold)
self.view.addSubview(b)
b.layer.borderWidth = 1
b.layer.borderColor = UIColor.black.cgColor
b.layer.cornerRadius = 10
I have an opening book animation: https://streamable.com/n1c0n
And I want to on 90 degrees my image has changed.
I use this code:
var book1ImageViewI : UIImageView
let book2ImageViewI : UIImageView
book1ImageViewI = UIImageView(frame: CGRect( x: self.view.frame.size.width / 2 - 140, y: (self.view.frame.size.height / 2) - ( (self.view.frame.width / 2) / 2 ), width: ( (self.view.frame.width / 2) / 8) * 7, height: self.view.frame.width / 2))
book2ImageViewI = UIImageView(frame: CGRect(x: self.view.frame.size.width / 2 - 140, y: (self.view.frame.size.height / 2) - ( (self.view.frame.width / 2) / 2 ), width: ( (self.view.frame.width / 2) / 8) * 7, height: self.view.frame.width / 2))
book1ImageViewI.image = UIImage(named:"attachment_83090027.jpg")
book2ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c152238ee5078384d--antique-books-rabbit-hole.jpg")
book1ImageViewI.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
book2ImageViewI.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
var transform = CATransform3DIdentity
transform.m34 = 1.0 / -2000.0
book1ImageViewI.layer.transform = transform
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
})
{
(bCompleted) in
if (bCompleted) {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
}
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}, completion: {
(bFinished) in
//Whatever
})
}
self.view.addSubview(book2ImageViewI)
self.view.addSubview(book1ImageViewI)
Everything works fine. But on 90 degrees animation slightly delayed.
I want to get animation like this without delay: https://streamable.com/q2bf6
How to do it?
P.S. In second animation use this code:
UIView.animate(withDuration: 3, delay: 0.0,
options: [], animations: {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}
book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi, 0, 1, 0)
self.view.layoutIfNeeded()
}, completion: {_ in
})
But this code does not fit. Because images changes not on 90 degrees. Sometimes earlier. Sometimes later.
Apparently, the anchor-point change was quite crucial wherever rotation transform is involved, and it is not straightforward. Every time you change anchorpoint, it alters the UIView position, and we must compensate for this change somehow.
Here is the final version using imageview which works without interruption for me. Apparently, it works with addKeyFrame API as well but for simplicity's sake I kept UIView.animate().
The trick I used is to have smaller duration for first animation and larger one for the second. It almost look like a page flip. You can also play with UIView.animate overrides that provide for UIViewAnimationOptions - such as curveEaseIn and curveEaseOut to make it look like page flip of a book.
It seems to work for me, the only thing is that you can't say it is rotating from the front or back. If your imageview is slant (like the slant book page you show in video), it should be visible and you can simply correct by changing the - to + sign within CATransform3DRotate arguments, or vice versa in animation block.
NOTE:
If it still plays hide and seek, try on real device instead of simulator just in case. There is apparent difference due to GPU execution when it comes to animations.
func pageFlipAnimation()
{
self.myImageView.image = UIImage.init(named: "firstImage")
var transform = CATransform3DIdentity
let transform1 = CATransform3DRotate(transform, -CGFloat(Double.pi)*0.5, 0, 1, 0)
let transform2 = CATransform3DRotate(transform, -CGFloat(Double.pi), 0, 1, 0)
transform.m34 = 1.0 / -5000.0
self.setAnchorPoint(anchorPoint: CGPoint(x: 0, y: 0.5), forView: self.myImageView)
UIView.animate(withDuration: 0.5, animations:
{
self.myImageView.layer.transform = transform1
})
{
(bFinished) in
self.myImageView.image = UIImage.init(named: "secondImage")
UIView.animate(withDuration: 0.75, animations:
{
self.myImageView.layer.transform = transform2
})
}
}
//This should ideally be a UIView Extension
func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView)
{
var newPoint = CGPoint(x:view.bounds.size.width * anchorPoint.x, y:view.bounds.size.height * anchorPoint.y)
var oldPoint = CGPoint(x:view.bounds.size.width * view.layer.anchorPoint.x, y:view.bounds.size.height * view.layer.anchorPoint.y)
newPoint = newPoint.applying(view.transform)
oldPoint = oldPoint.applying(view.transform)
var position = view.layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
view.layer.position = position
view.layer.anchorPoint = anchorPoint
}
This part of your code conflicts:
(bCompleted) in
if (bCompleted) {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
}
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}, completion: {
(bFinished) in
//Whatever
})
You set book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0) at the same time as book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
Try this instead:
(bCompleted) in
if (bCompleted) {
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}, completion: {
(bFinished) in
//Whatever
})
}
Here is my code about add a layer with animation added.
let myLayer: CALayer = .init()
let myAnimation: CABasicAnimation = .init(keyPath: "bounds.size.height")
// for myLayer to center of superview
let centerPoint: CGPoint = .init(x: mySuperview.bounds.width / 2, y: mySuperview.bounds.height / 2)
myLayer.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
myLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
myLayer.position = centerPoint
myLayer.backgroundColor = UIColor.red.cgColor
myAnimation.fromValue = 20
myAnimation.toValue = 100
myAnimation.duration = 1
myAnimation.beginTime = CACurrentMediaTime() + 1.0
myAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
myLayer.add(myAnimation, forKey: "bounds.size.height")
mySuperview.layer.addSublayer(myLayer)
and myLayer was appeared at correct position. but added animation isn't working.
not only about bounds.size.height. frame.size.height, transform, opacity... I tried all of them but It isn't changing.
I am confused because I have a recollection of working well easily by above code.
Did I skip anything?
Let's say I have a rectangle that is horizontally long. It's blue. I want it to be red. However, I want the color change to start on one side and end on the other.
I can use a key frame animation to make the whole view gradually change from red to blue. Is there a way to gradually change it from left-right/right-left??
UIView.animateKeyframesWithDuration(2.0 /*Total*/, delay: 0.0, options: UIViewKeyframeAnimationOptions.CalculationModeLinear, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 1/1, animations:{
self.view.backgroundColor = UIColor.redColor()
self.view.layoutIfNeeded()
})
},
completion: { finished in
if (!finished) { return }
})
Take a look at CAGradientLayer. You can animate its locations, colors or endPoint and startPoint
Here is a quick snippet you can paste into playground and see how it can work. In this case I'm animating the gradients color locations.
import UIKit
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 400))
let startLocations = [0, 0]
let endLocations = [1, 2]
let layer = CAGradientLayer()
layer.colors = [UIColor.redColor().CGColor, UIColor.blueColor().CGColor]
layer.frame = view.frame
layer.locations = startLocations
layer.startPoint = CGPoint(x: 0.0, y: 1.0)
layer.endPoint = CGPoint(x: 1.0, y: 1.0)
view.layer.addSublayer(layer)
let anim = CABasicAnimation(keyPath: "locations")
anim.fromValue = startLocations
anim.toValue = endLocations
anim.duration = 2.0
layer.addAnimation(anim, forKey: "loc")
layer.locations = endLocations
XCPlaygroundPage.currentPage.liveView = view
Swift 3 version:
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 400))
let startLocations = [0, 0]
let endLocations = [1, 2]
let layer = CAGradientLayer()
layer.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
layer.frame = view.frame
layer.locations = startLocations as [NSNumber]?
layer.startPoint = CGPoint(x: 0.0, y: 1.0)
layer.endPoint = CGPoint(x: 1.0, y: 1.0)
view.layer.addSublayer(layer)
let anim = CABasicAnimation(keyPath: "locations")
anim.fromValue = startLocations
anim.toValue = endLocations
anim.duration = 2.0
layer.add(anim, forKey: "loc")
layer.locations = endLocations as [NSNumber]?
PlaygroundPage.current.liveView = view
I would expect this code to move my newLabel from the upper left corner (ULO) to the lower left corner (LLO) but it doesn't! Apparently I need someone to hold my hand through the process of setting (0, 0) to LLO for a UIView. I've been through the documentation and read post after post on this forum but can't seem to make it happen. Disclaimer: I am trying to accomplish this in a playground in xCode 6 beta 2 so it possible it's a bug or not supported.
var newLabel = UILabel(frame: CGRectMake(0, 0, 150, 50))
newLabel.text = "This is a test"
newLabel.layer.borderWidth = 2
newLabel.textAlignment = NSTextAlignment.Center
newLabel.backgroundColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 0.8)
var myView = UIView(frame: CGRectMake(0, 0, 200, 100))
myView.layer.borderWidth = 2
myView.addSubview(newLabel)
myView.transform = CGAffineTransformMakeScale(1, -1)
myView.backgroundColor = UIColor(red: 0.8, green: 0, blue: 0, alpha: 0.8)
It seems like you haven't added Myview in main view try this code:--
var newLabel = UILabel(frame: CGRectMake(0, 0, 150, 50))
newLabel.text = "This is a test"
newLabel.layer.borderWidth = 2
newLabel.textAlignment = NSTextAlignment.Center
newLabel.backgroundColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 0.8)
var myView = UIView(frame: CGRectMake(0, 0, 200, 100))
myView.layer.borderWidth = 2
myView.addSubview(newLabel)
myView.transform = CGAffineTransformMakeScale(1, -1)
myView.backgroundColor = UIColor(red: 0.8, green: 0, blue: 0, alpha: 0.8)
self.view.addSubview(myView);
To flip the UIView it has to be a subView of another view... My issue was I was trying to flip the mainView or baseView which apparently cant be done. This make since, since it needs a graphic space to be able to draw to. Below is the code achieving what I was looking for. Also of not I also had to "flip" my label as well to return it to it upright state (again this is logical).
var newLabel = UILabel(frame: CGRectMake(0, 0, 150, 50))
newLabel.transform = CGAffineTransformMakeScale(1, -1)
newLabel.text = "This is a test"
newLabel.layer.borderWidth = 2
newLabel.textAlignment = NSTextAlignment.Center
newLabel.backgroundColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 0.8)
var subView = UIView(frame: CGRectMake(50, 50, 200, 100))
subView.transform = CGAffineTransformMakeScale(1, -1)
subView.layer.borderWidth = 2
subView.addSubview(newLabel)
subView.backgroundColor = UIColor(red: 0.8, green: 0, blue: 0, alpha: 0.8)
var myView = UIView()
myView = UIView(frame: CGRectMake(0, 0, 300, 200))
//myView.transform = CGAffineTransformMakeScale(1, -1)
myView.layer.borderWidth = 2
myView.addSubview(subView)
myView.backgroundColor = UIColor(red: 0, green: 0.8, blue: 0, alpha: 0.8)
Thanks #Mohit for your input in helping me to find the solution.