CATransform3D breaks layer order? - ios

Basically I want to build the scrollView like the one in iOS7 safari tab switcher. I use CATransform3D to get the feeling of oblique. However when I employ transform the layers just don't display in proper order.(They are in correct order before transform, seems like a total reversal in order.Back layer jumps to the front). How can I fix this thorny problem?
By the way in my case superView.bringSubViewToFront doesn't work.
My code:
//Create imageView
...
scroller.addSubview(imageView)
let layer = imageView.layer
//Set shadow
layer.shadowOpacity = 0.2
layer.shadowRadius = 2
layer.shadowOffset = CGSizeMake(6, -10)
//Transform, if without following code the layer order is correct
var transform = CATransform3DIdentity
transform.m34 = 0.0009
let radiants = 0.11*M_PI
transform = CATransform3DRotate(transform, CGFloat(radiants), 1.0, 0.0, 0.0)
scroller.bringSubviewToFront(imageView)//It doesn't work.

I just found that if you set the z axis to 1.0 in CATransform3DTranslate the layer become in front of other layers, so just make it 0.

Related

How to change SHAPE of UIImage in iOS

Hey guys how to change the shape of UIImage like this:
This is the original one:
And this is the way I wanna it be:
If you get any idea about this, please leave a message. Thx:)
You can achieve this by layer transformation.
self.imgView.layer.transform = CATransform3DScale(self.imgView.layer.transform, 1, 1, 1.5)
Here you need to play with z position of layer transformation.
Please refer CATransform3D for more detail...
You can achive this with transform. But not scale - you need to setup perspective, and rotate you view a bit around x axis:
// Take Identity matrix
var perspectiveMatrix = CATransform3DIdentity
// Add perspective.
perspectiveMatrix.m34 = -1.0 / 1000.0
// Rotate by 30 degree
let rotationMatrix = CATransform3DRotate(self.perspectiveMatrix,CGFloat.pi / 6.0, 1, 0, 0)
//Apply transform
view.layer.transform = rotationMatrix
You can apply transform in UIView.animation block to make smooth animation.

Animate View with multiple arguments

I am trying to animate the opacity of a CALayer but the values are more than one value to begin with and one to end with.
For example: I want it to animate throw these values: 0.0, 0.7, 0.3, 1.0, 0.5, 0.0
I also want the animation to repeat with auto reverse.
This is what I have for now:
let redLineAnimation = CABasicAnimation(keyPath: "opacity")
redLineAnimation.duration = 0.4
redLineAnimation.autoreverses = true
redLineAnimation.fromValue = 0.0
redLineAnimation.toValue = 1.0
redLineAnimation.repeatCount = Float.infinity
movingRedLineLayer.addAnimation(redLineAnimation, forKey: nil)
I am new to iOS development. What can I do? Thanks!
Take a look at CAKeyframeAnimation. That will give you what you want.
And if what you are animating is a view's layer, you could use the easier-to-use UIView keyframe based animation methods. (Core Animation is pretty tricky, and the docs are spotty)

ios animating already transformed view

I have a collection view with a collection of images (which are views). The layout causes transformations to occur on every visible item. The item at the center is both zoomed and translated while the others are just translated.
The point is, the layout is setting the layer's transform property for each item.
Later, when the user touches an item, I want to animate the item using a keyframe animation.
The behavior I am experiencing is that the item seems to revert back to its untransformed state, the animation occurs on its untransformed state and then the item's layout transformation returns.
Why?
I've tried using the view's transform property, the backing layer's affine transform property, and the backing layer's 3D transform property all with this same behavior. The keyframe animation uses 3D transforms.
I clearly have a gap in my understanding of transforms and animations, though I have seen this question posed for platforms other than iOS without answers.
Layout transformation code (in a flow layout subclass):
// Calculate the distance from the center of the visible rect to the center of the attributes.
// Then normalize it so we can compare them all. This way, all items further away than the
// active get the same transform.
let distanceFromVisibleRectToItem = CGRectGetMidX(visibleRect) - attributes.center.x
let normalizedDistance = distanceFromVisibleRectToItem / ACTIVE_DISTANCE
let isLeft = distanceFromVisibleRectToItem > 0
var transform = CATransform3DIdentity
var maskAlpha: CGFloat = 0.0
if (abs(distanceFromVisibleRectToItem) < ACTIVE_DISTANCE) {
// We're close enough to apply the transform in relation to how far away from the center we are.
transform = CATransform3DTranslate(transform,
(isLeft ? -FLOW_OFFSET : FLOW_OFFSET) * abs(distanceFromVisibleRectToItem/TRANSLATE_DISTANCE),
0, (1 - abs(normalizedDistance)) * 40000 + (isLeft ? 200 : 0))
// Set the zoom factor.
let zoom = 1 + ZOOM_FACTOR * (1 - abs(normalizedDistance))
transform = CATransform3DScale(transform, zoom, zoom, 1)
attributes.zIndex = 1
let ratioToCenter = (ACTIVE_DISTANCE - abs(distanceFromVisibleRectToItem)) / ACTIVE_DISTANCE
// Interpolate between 0.0f and INACTIVE_GREY_VALUE
maskAlpha = INACTIVE_GREY_VALUE + ratioToCenter * (-INACTIVE_GREY_VALUE)
} else {
// We're too far away - just apply a standard perspective transform.
transform = CATransform3DTranslate(transform, isLeft ? -FLOW_OFFSET : FLOW_OFFSET, 0, 0)
attributes.zIndex = 0
maskAlpha = INACTIVE_GREY_VALUE
}
attributes.transform3D = transform
Animation code (note this is a Swift extension to the UIView class):
func bounceView(completion: (() -> Void)? = nil) {
var animation = bounceAnimation(frame.height)
animation.delegate = AnimationDelegate(completion: completion)
layer.addAnimation(animation, forKey: "bounce")
}
func bounceAnimation(itemHeight: CGFloat) -> CAKeyframeAnimation {
let factors: [CGFloat] = [0, 32, 60, 83, 100, 114, 124, 128, 128, 124, 114, 100, 83, 60, 32,
0, 24, 42, 54, 62, 64, 62, 54, 42, 24, 0, 18, 28, 32, 28, 18, 0]
var transforms: [AnyObject] = [NSValue(CATransform3D: self.layer.transform)]
for factor in factors {
let positionOffset = factor/256.0 * itemHeight
let transform = CATransform3DMakeTranslation(0, -positionOffset, 0)
transforms.append(NSValue(CATransform3D: transform))
}
let animation = CAKeyframeAnimation(keyPath: "transform")
animation.repeatCount = 1
animation.duration = CFTimeInterval(factors.count)/30.0
animation.fillMode = kCAFillModeForwards
animation.values = transforms
animation.removedOnCompletion = true // final stage is equal to starting stage
animation.autoreverses = false
return animation;
}
Note: I should state more simply that I want this process to begin and end in the collection view layout transformed state. I want the animation to occur on the layout transformed state as well (i.e. no reverting to the untransformed state at all).
It seems that in your animations you are not syncing your model with your presentation layer.
When you animate with Core Animation (CABasicAnimation, for instance) it only changes the presentation layer (the thing you see on the screen) but in fact the state your layers have is different from the visible one.
(You can read more about that in this objc.io issue)
So basically to fix this you need to update your model in your CAAnimation. You need to set a from and toValue, and then, after you set those, you update your transform so it can match the final state of your presentation layer.
EDIT
Now that we have some code I can be concrete
To sync the model layer to the presentation layer you need to set your model to the final state before adding the animation:
var animation = bounceAnimation(frame.height)
animation.delegate = AnimationDelegate(completion: completion)
layer.transform = CATransform3DMakeTranslation(0, 0, 0)
layer.addAnimation(animation, forKey: "bounce")
David Rönnqvist has a good rant about this issue in this gist.
SOLUTION:
The help Tiago Almeida offered was very useful to get me thinking about what the animation was actually doing (how it was actually working). My basic assumption that the animation would begin with the current model layer transform was incorrect. As Tiago points out, the presentation layer is a separate and distinct layer from the model layer.
Once I got that through my thick head, I realized that I had to manually concatenate the current model transform with the transform being built for the animation frame and viola! Things work as expected.
The updated animation code:
var transforms: [AnyObject] = []
for factor in factors {
let positionOffset = factor/256.0 * itemHeight
var transform = CATransform3DMakeTranslation(0, -positionOffset, 0)
transform = CATransform3DConcat(self.layer.transform, transform)
transforms.append(NSValue(CATransform3D: transform))
}

Multiple Drop Shadows on Single View iOS

I'm looking to add multiple drop shadows with different opacities to a view. The specs for the shadows are as follows:
Y-offset of 4 with blur radius of 1
Y-offset of 10 with blur radius of 10
Y-offset of 2 with blur radius of 4
Blur radius of 1, spread of 1 (no offsets, will probably have to be 4 different shadows)
I can get all this working just fine using CALayers. Here's the code I have working for that (please note that I haven't bothered to set shadowPath yet, and won't until I get the multiple shadows thing working):
layer.cornerRadius = 4
layer.masksToBounds = false
layer.shouldRasterize = true
let layer2 = CALayer(layer: layer), layer3 = CALayer(layer: layer), layer4 = CALayer(layer: layer)
layer.shadowOffset = CGSizeMake(0, 4)
layer.shadowRadius = 1
layer2.shadowOffset = CGSizeMake(0, 10)
layer2.shadowRadius = 10
layer2.shadowColor = UIColor.blackColor().CGColor
layer2.shouldRasterize = true //Evidently not copied during initialization from self.layer
layer3.shadowOffset = CGSizeMake(0, 2)
layer3.shadowRadius = 4
layer3.shouldRasterize = true
layer4.shadowOffset = CGSizeMake(0, 1)
layer4.shadowRadius = 1
layer4.shadowOpacity = 0.1
layer4.shouldRasterize = true
layer.addSublayer(layer2)
layer.addSublayer(layer3)
layer.addSublayer(layer4)
(While this code is in Swift, I trust that it looks familiar enough to most Cocoa/Objective-C developers for it to make sense. Just know that layer is equivalent to self.layer in this context.)
The problem, however, arises when I attempt to use different opacities for each shadow. The shadowOpacity property of layer ends up being applied to all of its sublayers. This is a problem, as I need all of them to have their own shadow opacity. I have tried setting each layer's shadow opacity to its correct value (0.04, 0.12, etc.), but then the opacity of 0.04 of layer is applied to all sublayers. So I tried to set layer.shadowOpacity to 1.0, but this made all the shadows solid black. I also tried to be clever and do layer2.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.12).CGColor, but it was just changed to full black with no transparency.
I suppose it makes some sort of sense that the layers should all have the same shadow opacity. But what's a way to get this working, varying opacities and all (doesn't have to utilize CALayer if it's easier another way)?
Please don't answer with "just use an image": no matter how sane that may be, I'm trying to avoid it. Just humor me.
Thanks.
EDIT: As per request, here's what I'm after: .
The key thing that needs to be added is setting the layers' shadowPath. By default, Core Graphics draws a shadow around the layer's visible content, but in your code neither backgroundColor nor bounds are set for the layers, so the layers are actually empty.
Assuming you have a UIView subclass, you can make it work by adding something like this:
override func layoutSubviews() {
super.layoutSubviews()
layer.sublayers?.forEach { (sublayer) in
sublayer.shadowPath = UIBezierPath(rect: bounds).cgPath
}
}
I tested this approach on a view with multiple shadows and it worked as expected, as soon as the shadowPath is defined for the shadow layers. Different shadow colors and opacities worked as well, but you have to keep in mind that upper layers in the hierarchy will overlap the layers behind them, so if the front layer has a thick shadow, the other shadows can get hidden by it.
What about adding the alpha to the shadow color instead of the layer shadow opacity?
i.e. instead of
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.5
do
layer.shadowColor = UIColor.black.withAlphaComponent(0.5).cgColor
for each layer.

CALayer perspective reset

I've got the following trouble.
In my app i'm implementing flip-clock. I've got 3 CATransformLayers and can swipe them up-down and down-up. I always have two layers on top with indexes 1 at top and 0 at bottom and one layer down with index 2. So, when i flip down-up, i quickly rotate layer 0 down and transform layer 2 up, so layer 0 could already be visible.
i'm using the following code to do this :
auto CATransform3D t3d = CATransform3DIdentity;
t3d.m34 = 1.0/-500.0;
layer2.sublayerTransform = CATransform3DRotate(t3d, -angle , 1.0, 0.0, 0.0) ;
to flip layer at index 2 ( bottom layer ) up.
After this, i immedeately do the following with the layer at index 0 :
auto CATransform3D t3d = CATransform3DIdentity;
t3d.m34 = 0.0; // for make sure m34 is zero - excess code
layer0.sublayerTransform = CATransform3DRotate(t3d, M_PI , 1.0, 0.0, 0.0) ;
After all animation completed - of up-down direction - i rearrange transform sublayers to make this action more and more times :
[container.layer insertSublayer:layer0 above:(CALayer*)[container.layer.sublayers objectAtIndex:2]] ;
Container is flip-clock's holder base view. The problem is, that when i flip one time - zero layer falls down on the back view normally. After second flip - it's all right too. But after third flip - the layer, that was flipped the very first time ( former layer 2) falls in the back scene with perspective, not looking at the fact, that i reset transformation to m34 = 0.0. What can be the reason for this?
As always, any help would be appreciated.
well, the problem was in transformation speed - i increased it to 2000 ( it was 0.5 ) and the back layer isn't visible anymore!

Resources