How to Change UIImageView ContentMode With Animation using swift? - ios

I will try with this code, but not work
let contentMode: UIView.ContentMode = imageView.contentMode == .scaleAspectFill ?
.scaleAspectFit : .scaleAspectFill
UIView.animate(withDuration: 0.5) {
self.imageView.contentMode = contentMode
}

You can't do it. This is not an animatable property. It is unclear what kind of animation would even make sense.
An alternative would be to substitute a second image view with a different content mode, and cross-dissolve from one image view to the other (transformation).

Why don't you just animate UIImageView itself? The code below is an example for growing animation.
let height = imageView.frame.height
let width = imageView.frame.width
let x = imageView.frame.origin.x
let y = imageView.frame.origin.y
imageView.frame = CGRect(x: x, y: y, width: 0, height: 0)
UIView.animate(withDuration: 0.5) {
self.imageView.frame = CGRect(x: x, y: y, width: width, height: height)
}

I'm done this with help of transition
let contentMode: UIView.ContentMode = imageView.contentMode == .scaleAspectFill ? .scaleAspectFit : .scaleAspectFill
UIView.transition(with: imageView, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.imageView.contentMode = contentMode
})

Related

How can I transition a UIImage slowly from top to bottom for applying filter to the image?

I want to make it appear as if my image is slowly getting filtered from top to bottom. I am adding two image views. My processed image is in the background and non-processed in the front. I am making the height of non-processed image 0. Here is my code.
imageView.frame = CGRect(x: 0, y: 100, width: self.view.bounds.size.width, height: 400)
imageView.image = processedImage
let nonProcessedImageView = UIImageView()
nonProcessedImageView.frame = CGRect(x: 0, y: 100, width: self.view.bounds.size.width, height: 400)
nonProcessedImageView.image = nonProcessedImage
view.addSubview(nonProcessedImageView)
UIView.transition(with: nonProcessedImageView,
duration: 5.0,
animations: {
nonProcessedImageView.frame = CGRect(x: 0, y: 500, width: self.view.bounds.size.width, height: 0)
},
completion: {_ in
})
The non processed image does not even appear on top of the processed.
The issue seems to be that changing the y coordinate of the frame in the animation block leads to issues when using the UIView.Animate function.
See this answer
To quote the most essential part:
You should change the center property (to move the view) and bounds
property (to change its size) instead. Those properties behave as
expected.
Since you want to just reduce the height, you don't need to do anything to the y coordinate
let nonProcessedImageView = UIImageView()
var newImageFrame = imageView.frame
nonProcessedImageView.frame = newImageFrame
nonProcessedImageView.clipsToBounds = true
nonProcessedImageView.contentMode = .scaleAspectFit
nonProcessedImageView.image = imageView.image
view.addSubview(nonProcessedImageView)
// I set this to 1 instead of 0 as 0 does not show the image
// for some reason
newImageFrame.size.height = 1
// Update your processed image
imageView.image = processedImage
UIView.animate(withDuration: 5.0) {
nonProcessedImageView.frame = newImageFrame
}
completion: { (isComplete) in
if isComplete {
nonProcessedImageView.removeFromSuperview()
}
}
This gives some results but as you can see, the animation is not so good because as you reduce the height, the image view's contentMode kicks in and gives the seen effect.
For best results, add the new image view to a UIView and reduce the height of the UIView instead of the UIImageView
var newImageFrame = imageView.frame
let containerView = UIView(frame: newImageFrame)
containerView.clipsToBounds = true
let nonProcessedImageView = UIImageView()
nonProcessedImageView.frame = containerView.bounds
nonProcessedImageView.clipsToBounds = true
nonProcessedImageView.contentMode = .scaleAspectFit // same as original
nonProcessedImageView.image = imageView.image
containerView.addSubview(nonProcessedImageView)
view.addSubview(containerView)
newImageFrame.size.height = 1
imageView.image = processedImage
UIView.animate(withDuration: 3.0) {
containerView.frame = newImageFrame
}
completion: { (isComplete) in
if isComplete {
containerView.removeFromSuperview()
}
}
This should give you what you want:
This works for top to bottom transition.
imageView.frame = CGRect(x: 0, y: 100, width: self.view.bounds.width, height: 400)
imageView.image = nonProcessedImage
view.addSubview(imageView)
let frontView = UIView(frame: CGRect(x: 0, y: 100, width: self.view.frame.width, height: 0))
frontView.clipsToBounds = true
view.addSubview(frontView)
let nonProcessedImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 400))
nonProcessedImageView.image = processedImage
nonProcessedImageView.clipsToBounds = true
frontView.addSubview(nonProcessedImageView)
UIView.transition(with: frontView, duration: 5, options: [.allowAnimatedContent], animations: {
frontView.frame = CGRect(x: 0, y: 100, width: self.view.frame.width, height: 400)
}, completion: nil)

Image size error while rendering UIView to UIImage

I would like to convert two images into one image with custom design.
The expected image is a snapshot of my UICollectionViewCell, not a UIImage actually.
I copied the layout codes from my custom UICVCell.swift file and tried to render the view into UIImage, but the result image is what you can see below.
I searched through a lot of questions in SOF, but most of it was about 'How you can render a UIView into a UIImage.'
I've tried drawing too, but had the same messed up result.
Can anybody tell me what's the problem?
I would really appreciate your help, it's my first question in SOF.
I might cry in a minute or two, literally...
class ViewController: UIViewController {
private var imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.clipsToBounds = true
return iv
}()
func createBubbleImage(images: [UIImage?]) -> UIImage? {
switch images.count {
case 1:
return images[0]
case 2:
let newView = UIView(frame: .init(x: 0, y: 0, width: 300, height: 300))
let size = newView.frame.width
let iv0 = UIImageView(image: images[0])
iv0.contentMode = .scaleAspectFit
iv0.clipsToBounds = true
let iv1 = UIImageView(image: images[1])
iv1.contentMode = .scaleAspectFit
iv1.clipsToBounds = true
newView.addSubview(iv0)
iv0.anchor(top: newView.topAnchor, left: newView.leftAnchor, paddingTop: size * 0.04, paddingLeft: size * 0.04, width: size * 0.56, height: size * 0.56)
newView.addSubview(iv1)
iv1.anchor(bottom: newView.bottomAnchor, right: newView.rightAnchor, paddingBottom: size * 0.04, paddingRight: size * 0.04, width: size * 0.56, height: size * 0.56)
iv0.layer.cornerRadius = size * 0.28
iv1.layer.cornerRadius = size * 0.28
let renderer = UIGraphicsImageRenderer(bounds: newView.bounds)
return renderer.image { ctx in
newView.layer.render(in: ctx.cgContext)
}
default:
return UIImage(named: "logo")
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(imageView)
imageView.center(inView: view)
imageView.setDimensions(width: 300, height: 300)
imageView.image = createBubbleImage(images: [UIImage(named: "0"), UIImage(named: "1")])
}
}
Expected Image
Result Image
Do the following:
Remove the code lines that have the anchor method.
Initiate the iv0 and iv1 with UIImageView(frame: ...)
Add iv0.image = images[0] and iv1.image = images[1]
I also increased the radius a little bit because in my test device the images were not completely circular.
The code should look like this:
func createBubbleImage(images: [UIImage?]) -> UIImage? {
switch images.count {
case 1:
return images[0]
case 2:
let newView = UIView(frame: .init(x: 0, y: 0, width: 300, height: 300))
let size = newView.frame.width
let iv0 = UIImageView(frame: .init(x: 0, y: 0, width: 200, height: 200))
iv0.image = images[0]
iv0.contentMode = .scaleAspectFit
iv0.clipsToBounds = true
let iv1 = UIImageView(frame: .init(x: 100, y: 100, width: 200, height: 200))
iv1.image = images[1]
iv1.contentMode = .scaleAspectFit
iv1.clipsToBounds = true
newView.addSubview(iv0)
newView.addSubview(iv1)
iv0.layer.cornerRadius = size * 0.35
iv1.layer.cornerRadius = size * 0.35
let renderer = UIGraphicsImageRenderer(bounds: newView.bounds)
return renderer.image { ctx in
newView.layer.render(in: ctx.cgContext)
}
default:
return UIImage(named: "logo")
}
}
With it, the result is the following:
I had to take a screenshot from your "expected image", that's why the images look like zoomed in. You can play with the UIImageView(frame: ...) part to adjust the two images as you want (I didn't played enough to avoid that cut in the edges, but it is possible with the right measures).
Remark: the x and y values in the UIImageView frame are the horizontal and vertical distances from the top-left corner of newView to the top-left corner of your UIImageView, respectively.

Can I resize just the image of an imageView?

So I have this code:
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.borderWidth = 5
imageView.layer.borderColor = UIColor.green.cgColor
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalToConstant: 200),
imageView.widthAnchor.constraint(equalToConstant: 200),
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0),
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
])
let image = UIImage(named: "3")
//let image = UIImage(named: "3")?.resizeImageWith(newSize: CGSize(width: 5,height: 5))
imageView.image = image
}
That looks like this:
Im trying to resize just the image ( so like from the green border there is a margin) with this extension:
func resizeImageWith(newSize: CGSize) -> UIImage {
let horizontalRatio = newSize.width / size.width
let verticalRatio = newSize.height / size.height
let ratio = min(horizontalRatio, verticalRatio)
let newSize = CGSize(width: size.width * ratio, height: size.height * ratio)
UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: newSize))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
But when I use that like this:
let image = UIImage(named: "3")?.resizeImageWith(newSize: CGSize(width: 60,height: 60))
I get the following result:
And same with this:
let image = UIImage(named: "3")?.resizeImageWith(newSize: CGSize(width: 5,height: 5))
The image just get worst.
Is it possible to just resize the image in order to have a margin from the imageView? Thank you!
Change the image view content mode to Center:
imageView.contentMode = .center
I was able to produce what you wanted, but doing away with constraints. Using a container view that holds the imageView as a subview, you can use the container like a frame and set its border width and color.
With the frame in place, you can then resize the image as per your needs by just setting the frame of the imageView. The last 2 lines of the code are commented out, but if you uncomment them, you will see that the image will shrink to fit the new imageView frame size set to (50, 50).
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let containerView = UIView()
containerView.layer.borderWidth = 5
containerView.layer.borderColor = UIColor.green.cgColor
containerView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
containerView.center = view.center
let imageView = UIImageView()
view.addSubview(containerView)
containerView.addSubview(imageView)
let image = UIImage(named: "3")!
imageView.frame = CGRect(origin: imageView.frame.origin, size: CGSize(width: 200, height: 200))
imageView.image = image
imageView.contentMode = .scaleAspectFit
imageView.center = view.convert(containerView.center, to: containerView)
print("Image Width: \(image.size.width)")
print("Image Height: \(image.size.height)")
print("ImageView Width: \(imageView.frame.width)")
print("ImageView Height: \(imageView.frame.height)")
print("Container Width: \(containerView.frame.width)")
print("Container Height: \(containerView.frame.height)")
print("View Center: \(view.center.x),\(view.center.y)")
print("Container Center: \(containerView.center.x),\(containerView.center.y)")
//imageView.frame.size = CGSize(width: 50, height: 50)
//imageView.center = view.convert(containerView.center, to: containerView)
}
}
I used .scaleAspectFit so that the image will be forced to fit whatever size you set the imageView to, while maintaining its original aspect ratio. You can of course change this to suit your needs as well. If you need to set the size of your image in different ways, you'll need to change the imageView's contentMode property as appropriate. More info here:
https://developer.apple.com/documentation/uikit/uiimageview
The convert function just helps to layout the imageView where you want it, within its parent view.
The print debug statements are helpful for checking your layout, but of course, unnecessary.

How to animate a stitched image?

I'm not sure how to create this animation. Would you somehow split the 1 jpg file evenly in 3 pieces and animate that? Or would you have to make multiple copies of the jpg and do something with them?
Any help would be awesome!
UPDATE
Since you want a crossfade, it's probably easiest to do this by splitting the image into separate cel images:
import UIKit
import PlaygroundSupport
extension UIImage {
func subImage(inUnitRect unitRect: CGRect) -> UIImage? {
guard imageOrientation == .up, let cgImage = self.cgImage else { return nil }
let cgImageWidth = CGFloat(cgImage.width)
let cgImageHeight = CGFloat(cgImage.height)
let scaledRect = CGRect(x: unitRect.origin.x * cgImageWidth, y: unitRect.origin.y * cgImageHeight, width: unitRect.size.width * cgImageWidth, height: unitRect.size.height * cgImageHeight)
guard let croppedCgImage = cgImage.cropping(to: scaledRect) else { return nil }
return UIImage(cgImage: croppedCgImage, scale: scale, orientation: .up)
}
}
let image = #imageLiteral(resourceName: "image.png")
let celCount: CGFloat = 3
let cels = stride(from: 0, to: celCount, by: 1).map({ (i) -> UIImage in
image.subImage(inUnitRect: CGRect(x: i / celCount, y: 0, width: 1/3, height: 1))!
})
Then we can use a keyframe animation to crossfade the layer contents:
let imageView = UIImageView(image: cels[0])
PlaygroundPage.current.liveView = imageView
let animation = CAKeyframeAnimation(keyPath: "contents")
var values = [CGImage]()
var keyTimes = [Double]()
for (i, cel) in cels.enumerated() {
keyTimes.append(Double(i) / Double(cels.count))
values.append(cel.cgImage!)
// The 0.9 means 90% of the time will be spent *outside* of crossfade.
keyTimes.append((Double(i) + 0.9) / Double(cels.count))
values.append(cel.cgImage!)
}
values.append(cels[0].cgImage!)
keyTimes.append(1.0)
animation.keyTimes = keyTimes.map({ NSNumber(value: $0) })
animation.values = values
animation.repeatCount = .infinity
animation.duration = 5
imageView.layer.add(animation, forKey: animation.keyPath)
Result:
ORIGINAL
There are multiple ways you can do this. One is by setting or animating the contentsRect property of the image view's layer.
In your image, there are three cels, and each occupies exactly 1/3 of the image. The contentsRect is in the unit coordinate space, which makes computation easy. The contentsRect for cel i is CGRect(x: i/3, y: 0, width: 1/3, height: 0).
You want discrete jumps between cels, instead of smooth sliding transitions, so you need to use a keyframe animation with a kCAAnimationDiscrete calculationMode.
import UIKit
import PlaygroundSupport
let image = #imageLiteral(resourceName: "image.png")
let celSize = CGSize(width: image.size.width / 3, height: image.size.height)
let imageView = UIImageView()
imageView.frame = CGRect(origin: .zero, size: celSize)
imageView.image = image
PlaygroundPage.current.liveView = imageView
let animation = CAKeyframeAnimation(keyPath: "contentsRect")
animation.duration = 1.5
animation.calculationMode = kCAAnimationDiscrete
animation.repeatCount = .infinity
animation.values = [
CGRect(x: 0, y: 0, width: 1/3.0, height: 1),
CGRect(x: 1/3.0, y: 0, width: 1/3.0, height: 1),
CGRect(x: 2/3.0, y: 0, width: 1/3.0, height: 1)
] as [CGRect]
imageView.layer.add(animation, forKey: animation.keyPath!)
Result:

Back ground images animation scrolling

I have punch of images(5-6 images) that should animatedly scrolling on back ground with different speed. I put them just on UIView not in UIScrollView.
Right now I have this method for it:
I adding images on the screen that user see and copy of images behind the screen. But because of that images and copyImages(behind the screen) must pass the same distance them has different speed(Not that I put).
So it's not working properly for me.
Could anyone help with that?
P.S. Some images has different size so i need change yPos for them. Just for elaboration.
func setUpBackGroundLayersWithArray(){
var xPos: CGFloat = 0
for (index,image) in self.backGroundsImages.reverse().enumerate(){
var yPos:CGFloat = -30
switch index {
case 1: yPos = -10
xPos = 320
case 2: yPos = -10
default: yPos = -30
}
let imageView = UIImageView(image: image)
imageView.frame = CGRectMake(xPos, yPos, image.size.width, image.size.height)
imageView.contentMode = .ScaleAspectFit
self.addSubview(imageView)
let copyimageView = UIImageView(image: image)
copyimageView.frame = CGRectMake(320, yPos, image.size.width, image.size.height)
copyimageView.contentMode = .ScaleAspectFit
self.addSubview(copyimageView)
self.layoutIfNeeded()
let animator = UIViewPropertyAnimator(duration:8 - Double(index + 1), curve: .Linear, animations: {
UIView.animateKeyframesWithDuration(0, delay: 0, options: [.Repeat], animations: {
imageView.frame = CGRectMake(0 - copyimageView.frame.size.width, imageView.frame.origin.y, imageView.frame.size.width, imageView.frame.size.height)
}, completion: nil)
})
let secondAnimator = UIViewPropertyAnimator(duration:10 - Double(index + 1), curve: .Linear, animations: {
UIView.animateKeyframesWithDuration(0, delay: 0, options: [.Repeat], animations: {
copyimageView.frame = CGRectMake(0-copyimageView.frame.size.width, copyimageView.frame.origin.y, copyimageView.frame.size.width, copyimageView.frame.size.height)
}, completion: nil)
})
self.animators.append(animator)
self.animators.append(secondAnimator)
}
}
You can use UICollectionView for simple showing and animating on images. For animation you need use scrollToItemAtIndexPath:atScrollPosition:animated: method after needed time interval

Resources