Swift: Fading two images while chaging frame - swift4

Can someone tell me how can I animate a cross dissolve transition while chaging the initial frame?
My code:
self.image = initialImage
UIView.transition(with: _self, duration: 10.0, options: [.transitionCrossDissolve, .allowUserInteraction], animations: {
self.image = newImage
})
Also tried:
let transition = CATransition()
transition.duration = 10
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
transition.delegate = self
self?.layer.add(transition, forKey: nil)
Changing the frame size while the animation runs leaves the initial image at the initial frame size, while the newImage adapts to the updated frame.
Thank you.

For an easier approach, create two image view instances and animate the alpha of the first one, while changing the frame for both of them.
let firstImageView = UIImageView(image: UIImage(named: "first"))
firstImageView.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
let secondImageView = UIImageView(image: UIImage(named: "second"))
secondImageView.frame = firstImageView.frame
//the second image view should obscure the first one, that's why it's the first in the array
let imageViews = [secondImageView, firstImageView]
imageViews.forEach { view.addSubview($0) }
let newFrame = CGRect(x: 50, y: 50, width: 100, height: 100)
UIView.animate(withDuration: 10) {
firstImageView.alpha = 0
imageViews.forEach { $0.frame = newFrame }
}

Related

How to redraw a subviews in landscape

Good afternoon Community,
I am trying to add two subviews to a viewcontroller by means of an extension, but when rotating the device it does not respect the layout, any option on how to make it look correct?
I attach my code below and a couple of example photos, the first photo is how I would like it to look, and the rest of the ui is lost.
This is my code:
func presentCustomAlertController(on ViewController:UIViewController,toPresentCustomView customView:UIView,withBackgroundView backGroundView:UIView){
customView.translatesAutoresizingMaskIntoConstraints = false
guard let targetView = ViewController.view else {return}
backGroundView.alpha = 0
backGroundView.backgroundColor = .black
backGroundView.frame = targetView.bounds
targetView.addSubview(backGroundView)
targetView.addSubview(customView)
customView.setNeedsLayout()
customView.setNeedsDisplay()
customView.layoutIfNeeded()
customView.layoutSubviews()
targetView.setNeedsLayout()
targetView.setNeedsDisplay()
targetView.layoutIfNeeded()
targetView.layoutSubviews()
backGroundView.setNeedsDisplay()
backGroundView.setNeedsLayout()
backGroundView.layoutIfNeeded()
customView.frame = CGRect(x: targetView.center.x, y: targetView.center.y, width: targetView.frame.size.width-80, height: 300)
UIView.animate(withDuration: 0.25, animations: {
backGroundView.alpha = 0.6
},completion: { done in
UIView.animate(withDuration: 0.25, animations: {
customView.center = targetView.center
})
})
}
}

Swift 4 AVMutableComposition animations

I am trying to display an image over top of a video for the first few seconds only. I am adding an image to a CALayer and then attempting to hide it using CABasicAnimation. I have tried a few different iterations of the code below and cannot get the image to disappear. I have also tried setting the delegate property and having the parent view extend CAAnimationDelegate with an implementation of animationDidStop. However, this seems to only fire once when the view is rendered and not at the desired time within the video. What is the proper way to perform animations when constructing videos using AVMutableComposition?
let mixComposition = AVMutableComposition.init()
let videoLayer = CALayer()
videoLayer?.frame = CGRect.init(x: 0, y: 0, width: 414, height: 414)
let parentLayer = CALayer()
parentLayer?.frame = CGRect.init(x: 0, y: 0, width: 414, height: 414)
parentLayer?.addSublayer(videoLayer!)
let imageLayer = CALayer()
imageLayer.backgroundColor = UIColor.green.cgColor
imageLayer.frame = CGRect(x: 100, y: 100, width: 50, height: 50)
imageLayer.contents = UIImage(named: "music")?.cgImage
let fadeOut = CABasicAnimation(keyPath: "opacity")
fadeOut.fromValue = 1.0
fadeOut.toValue = 0.0
fadeOut.duration = 2.0
fadeOut.setValue("video", forKey:"fadeOut")
fadeOut.fillMode = CAMediaTimingFillMode.forwards
imageLayer.add(fadeOut, forKey: nil);
parentLayer?.addSublayer(imageLayer)
// Main video composition instruction
mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction?.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: insertTime)
mainInstruction?.layerInstructions = arrayLayerInstructions
mainInstruction?.backgroundColor = UIColor.systemPink.cgColor
// Main video composition
let mainComposition = AVMutableVideoComposition()
mainComposition.instructions = [mainInstruction!]
mainComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
mainComposition.renderSize = outputSize!

tap event does not fire on animated imageview

I want an animating imageView with UIBezierPath (sliding from top to bottom) to be clicked. But every google help I found didn't solve it. But it does not print "clicked".
The animation does work perfectly. Maybe anyone's got a clue?
Help is very appreciated.
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "advent_button"))
let dimension = 25 + drand48() * 30
imageView.frame = CGRect(x: 0, y: 0, width: dimension, height: dimension)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleImageTap))
imageView.addGestureRecognizer(tapGestureRecognizer)
imageView.isUserInteractionEnabled = true
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = customPath().cgPath
animation.duration = 20
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.repeatCount = 0.5
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
imageView.layer.add(animation, forKey: nil)
view.addSubview(imageView)
}
#objc func handleImageTap() {
print ("clicked")
}
func customPath() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 200, y: 0))
let endpoint = CGPoint(x: 20 , y: 700)
let cp1 = CGPoint(x: 90, y: 250)
let cp2 = CGPoint(x: 180, y: 400)
path.addCurve(to: endpoint, controlPoint1: cp1, controlPoint2: cp2)
return path
}
I've tried the following:
UIView.animate(withDuration: 30, delay: delay, options: [.allowUserInteraction], animations: {
self.button.frame = CGRect(
x: randomXEndShift,
y: 700,
width: dimension,
height: dimension)
}, completion: nil)
Shows the same behavoir. Even using .allowUserInteraction does not help at all.
I got your issue. i am sure you are touching layer not UIImageView and you have added uitapgesturerecognizer to UIImageview not layer. Do one thing set
animation.isRemovedOnCompletion = true
and set imageView.frame similar co- ordinates where animation will stop
Full code :
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "Icon123"))
imageView.backgroundColor = .red
// let dimension = 25 + drand48() * 30
imageView.isUserInteractionEnabled = true
imageView.frame = CGRect(x: 10, y: 200, width: 50, height: 50)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleImageTap))
imageView.addGestureRecognizer(tapGestureRecognizer)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = customPath().cgPath
animation.duration = 5
animation.fillMode = .forwards
animation.isRemovedOnCompletion = true
animation.repeatCount = 0.5
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
imageView.layer.add(animation, forKey: nil)
view.addSubview(imageView)
// Do any additional setup after loading the view, typically from a nib.
}
#objc func handleImageTap() {
print ("clicked")
}
func customPath() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 200, y: 0))
let endpoint = CGPoint(x: 20 , y: 700)
let cp1 = CGPoint(x: 90, y: 250)
let cp2 = CGPoint(x: 180, y: 400)
path.addCurve(to: endpoint, controlPoint1: cp1, controlPoint2: cp2)
return path
}
}

Circular crop for photos from photo library when using UIImagePickerController

I am trying to change the square crop tool to circular one for the images selected from Photo Library in Swift 4. I tried many old codes available in here with no luck. Can somebody please help me.
There are mainly 2 issues in this code.
It doesn't hide the already existing square cropping area.
It shows the choose and cancel buttons under the old overlay, so cant tap on it.
Any help would be appreciated.
My code:
private func hideDefaultEditOverlay(view: UIView)
{
for subview in view.subviews
{
if let cropOverlay = NSClassFromString("PLCropOverlayCropView")
{
if subview.isKind(of: cropOverlay) {
subview.isHidden = true
break
}
else {
hideDefaultEditOverlay(view: subview)
}
}
}
}
private func addCircleOverlayToImageViewer(viewController: UIViewController) {
let circleColor = UIColor.clear
let maskColor = UIColor.black.withAlphaComponent(0.8)
let screenHeight = UIScreen.main.bounds.size.height
let screenWidth = UIScreen.main.bounds.size.width
hideDefaultEditOverlay(view: viewController.view)
let circleLayer = CAShapeLayer()
let circlePath = UIBezierPath(ovalIn: CGRect(x: 0, y: screenHeight - screenWidth, width: screenWidth, height: screenWidth))
circlePath.usesEvenOddFillRule = true
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = circleColor.cgColor
let maskPath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight), cornerRadius: 0)
maskPath.append(circlePath)
maskPath.usesEvenOddFillRule = true
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.fillColor = maskColor.cgColor
viewController.view.layer.addSublayer(maskLayer)
// Move and Scale label
let label = UILabel(frame: CGRect(x: 0, y: 20, width: view.frame.width, height: 50))
label.text = "Move and Scale"
label.textAlignment = .center
label.textColor = UIColor.white
viewController.view.addSubview(label)
}

Circle scale animation works a bit weird

I am animating my GMSMarker so that it pulse once in couple seconds.
func addWave()
{
// circleView.layer.cornerRadius = size / 2
//Scale
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = 1
scaleAnimation.toValue = zoom
//Opacity
let alphaAnimation = CABasicAnimation(keyPath: "opacity")
alphaAnimation.toValue = 0.0
//Corner radius
// let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius")
// cornerRadiusAnimation.fromValue = size / 2
// cornerRadiusAnimation.toValue = (size * zoom)/2
//Animation Group
let animations: [CAAnimation] = [scaleAnimation, alphaAnimation]
let animationGroup = CAAnimationGroup()
animationGroup.duration = duration
animationGroup.animations = animations
animationGroup.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animationGroup.repeatCount = 1
animationGroup.fillMode = kCAFillModeForwards
animationGroup.isRemovedOnCompletion = false
circleView.layer.add(animationGroup, forKey: "group")
}
The result looks like this:
And if I uncomment Corner radius section it looks like this:
So I need an advice.
Based on my observations, I think it must be a issue of wrong path of your CAShapeLayer hence the masking of the circle.
I just wrote a radar animation, I hope it might help you.
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = 0
scaleAnimation.toValue = 1
let alphaAnimation = CABasicAnimation(keyPath: "opacity")
alphaAnimation.fromValue = 1
alphaAnimation.toValue = 0
let animations = CAAnimationGroup()
animations.duration = 0.8
animations.repeatCount = Float.infinity
animations.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animations.animations = [scaleAnimation, alphaAnimation]
circleView.layer.add(animations, forKey: "animations")
What I did was, I used two CAShapeLayer (one being the orange marker and other is the rader layer at the back of marker). The animations were applied on the radar layer.
radarLayer = CAShapeLayer()
radarLayer.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height);
radarLayer.path = UIBezierPath(rect: radarLayer.frame).cgPath
radarLayer.fillColor = UIColor.orange.cgColor
radarLayer.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2)
radarLayer.cornerRadius = radarLayer.frame.size.width/2
radarLayer.masksToBounds = true
radarLayer.opacity = 0
self.layer.addSublayer(radarLayer)
circleLayer = CAShapeLayer()
circleLayer.frame = CGRect(x: 0, y: 0, width: 16, height: 16);
circleLayer.path = UIBezierPath(rect: circleLayer.frame).cgPath
circleLayer.fillColor = UIColor.orange.cgColor
circleLayer.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2)
circleLayer.cornerRadius = circleLayer.frame.size.width/2
circleLayer.masksToBounds = true
self.layer.addSublayer(circleLayer)
PS. I'm new to Swift, so enlighten me if I'm wrong somewhere :)
Example : http://i.imgur.com/v2jFWgw.jpg
The following in an excerpt from Google documentation on Markers:
The view behaves as if clipsToBounds is set to YES, regardless of its actual value. You can apply transforms that work outside the bounds, but the object you draw must be within the bounds of the object. All transforms/shifts are monitored and applied. In short: subviews must be contained within the view.
The consequence of that, at least for my case, was exactly the behaviour mentioned in the question: a squared circle animation. The image I wanted to add inside the marker, at the end of the animation, was bigger than the container, so once added to the marker, it was clipped to the bounds of it.
The following is what I did:
public var pulseImageView: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
imageView.image = PaletteElements.pulseLocation.value
imageView.contentMode = .center
let pulseAnimation = CABasicAnimation(keyPath: "transform.scale.xy")
pulseAnimation.repeatCount = Float.infinity
pulseAnimation.fromValue = 0
pulseAnimation.toValue = 2.0
pulseAnimation.isRemovedOnCompletion = false
let fadeOutAnimation = CABasicAnimation(keyPath: "opacity")
fadeOutAnimation.duration = 2.5
fadeOutAnimation.fromValue = 1.0
fadeOutAnimation.toValue = 0
fadeOutAnimation.repeatCount = Float.infinity
let animationGroup = CAAnimationGroup()
animationGroup.duration = 2.5
animationGroup.animations = [pulseAnimation, fadeOutAnimation]
animationGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
animationGroup.repeatCount = .greatestFiniteMagnitude
animationGroup.fillMode = CAMediaTimingFillMode.forwards
animationGroup.isRemovedOnCompletion = false
imageView.layer.add(animationGroup, forKey: "pulse")
return imageView
}()
I defined a var inside my helper class so I can retrieve the image whenever I need it. My original image, the pulse image, was 116x116, so I created the imageView as 300x300 with a contentMode = .center, so the small image was in the center and not stretched. I chose 300x300 because my animation scaled up the pulse image until a 2x its initial value (116x2), so I made room for the entire animation to be performed.
Finally I added it to as a Marker to the map:
let pulseMarker = GMSMarker(position: userLocation.coordinate)
pulseMarker.iconView = pulseImageView
pulseMarker.groundAnchor = CGPoint(x: 0.5, y: 0.5)
pulseMarker.map = googleMapView
Hope it can help.