Change an UIImage dynamically for portrait and landscape - ipad

I'm trying to display an image in a UIImageView in portrait and landscape only for Ipad, when I added the UIImageView to the StoryBoard I set the width and height to 768x365 and the image it's showed perfect, but if the Ipad it's rotated to landscape I want to show the same image in 1024x365.
I try the following but without succeed.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
let orientation = UIApplication.sharedApplication().statusBarOrientation
switch orientation {
case .Portrait, .PortraitUpsideDown:
// 4
imageView.frame = CGRect(x: 0.0, y: 0.0, width: 1024.0, height: 365)
self.imageView.setNeedsDisplay()
case .LandscapeLeft, .LandscapeRight:
imageView.frame = CGRect(x: 0.0, y: 0.0, width: 768.0, height: 365)
self.imageView.setNeedsDisplay()
}
PD : The image resolution is 1680x598

Related

Why value in the frame than is equal to a specific size doesn't do the same job when I change the orientation of the device?

I have a piece of code that allows me to change the size of an UIImageView during the change of the orientation of the device.
I hardcoded this values (height = 896 and width = 414), and it works well, but when I want to do a dynamic size by putting the dynamic values (height = self.view.frame.size.height and width = self.view.frame.size.width), it doesn't work...
However, self.view.frame.size.height = 896 and self.view.frame.size.width = 414 this is strange... I missed something I think.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
print("Landscape")
self.newImageView?.frame.size.height = 414
self.newImageView?.frame.size.width = 896
navBar?.frame = CGRect(x: 0, y: 0, width: 896, height: 100)
} else {
print("Portrait")
self.newImageView?.frame.size.height = 896
self.newImageView?.frame.size.width = 414
navBar?.frame = CGRect(x: 0, y: 40, width: 414, height: 100)
}
}
EDIT (Solution):
For info, I used that way for my code, but the solution in the ticket is shorter and more modern
//Share bar
let shareBar: UIBarButtonItem = UIBarButtonItem.init(barButtonSystemItem:.action, target: self, action: #selector(userDidTapShare))
self.navigationItem.rightBarButtonItem = shareBar
if UIDevice.current.orientation.isLandscape {
let width = max(self.view.frame.size.width, self.view.frame.size.height)
let height = min(self.view.frame.size.width, self.view.frame.size.height)
self.newImageView?.frame.size.height = height
self.newImageView?.frame.size.width = width
navBar = UINavigationBar(frame: CGRect(x: 0, y: UIApplication.shared.statusBarFrame.height, width: width, height: 100))
} else {
let width = min(self.view.frame.size.width, self.view.frame.size.height)
let height = max(self.view.frame.size.width, self.view.frame.size.height)
self.newImageView?.frame.size.height = height
self.newImageView?.frame.size.width = width
navBar = UINavigationBar(frame: CGRect(x: 0, y: UIApplication.shared.statusBarFrame.height, width: width, height: 100))
}
view.addSubview(navBar!)
Any ideas?
If you want to resize the navbar
self.navBar.autoresizingMask = (UIView.AutoresizingMask(rawValue: UIView.AutoresizingMask.RawValue(UInt8(UIView.AutoresizingMask.flexibleRightMargin.rawValue) | UInt8(UIView.AutoresizingMask.flexibleLeftMargin.rawValue) | UInt8(UIView.AutoresizingMask.flexibleBottomMargin.rawValue) | UInt8(UIView.AutoresizingMask.flexibleTopMargin.rawValue))))
put this after your view.addSubview(navBar!)
Put a breakpoint in that method and you will see that this method is called before the view changes the orientation, this is why you don't the proper height and width, use the values inside the size parameter instead, this should work.
But agree with the comments, use auto-layout instead, it will make your life easier

UIView autorotation issue

I am getting issues in autorotation of a UIView which is nothing but a red line of width 4 and height half of superview's height in landscape mode, and in portrait mode the height is 4 points and width is half of superview width. You can see the issue in the gif. As I rotate from landscape to portrait, the effect of autorotation is not smooth and with distortions.
Here is the code. What am I doing wrong?
private var myView:UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
myView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width/2, height: 4))
myView.backgroundColor = UIColor.red
view.addSubview(myView)
myView.center = CGPoint(x: view.bounds.width/2, y: view.bounds.height/2)
}
private func layoutMyView() {
let orientation = UIApplication.shared.statusBarOrientation
if orientation == .landscapeLeft || orientation == .landscapeRight {
myView.frame = CGRect(x: 0, y: 0, width: view.bounds.width/2, height: 4)
} else {
myView.frame = CGRect(x: 0, y: 0, width: 4, height: view.bounds.height/2)
}
myView.center = CGPoint(x: view.bounds.width/2, y: view.bounds.height/2)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let orientation = UIApplication.shared.statusBarOrientation
NSLog("View will transition to \(size), \(orientation.rawValue)")
if size.width < size.height {
NSLog("Portrait mode")
} else {
NSLog("Landscape mode")
}
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { [unowned self] (_) in
let orient = UIApplication.shared.statusBarOrientation
self.layoutMyView()
}, completion: { [unowned self] (UIViewControllerTransitionCoordinatorContext) -> Void in
let orient = UIApplication.shared.statusBarOrientation
self.layoutMyView()
NSLog("rotation completed to \(orient.rawValue), \(self.myView.frame)")
})
}
There are a few potential options:
In animate(alongsideTransition:completion:), do a keyframe animation to set mid animation point so you don’t end up with that curious boxy feel mid animation. E.g. this shrink it down to a 4x4 box
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let origin = CGPoint(x: (view.bounds.midX + view.bounds.midY) / 2 - 2,
y: (view.bounds.midX + view.bounds.midY) / 2 - 2)
coordinator.animate(alongsideTransition: { _ in
UIView.animateKeyframes(withDuration: coordinator.transitionDuration, delay: 0, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
self.myView.frame = CGRect(origin: origin, size: CGSize(width: 4, height: 4))
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
self.layoutMyView()
}
})
})
}
There are many possible variations on this theme. This still animates the view, but gives it a more “deliberate” sort of vibe and doesn’t lose the “line” feel of it.
Rather than rendering a “line” with UIView, use CAShapeLayer and update its path.
You could run a CADisplayLink while transition is in progress, and then update the frame to whatever you want as the animation progresses.
Instead of animating the frame, you could animate the transform of this view, possibly rotating it in the opposite direction that the view is rotating.

Adjust the position of layer with transparent hole when view change from portrait to landscape

I am using this code to create a layer with a transparent hole
let radius = min(self.view.frame.size.width,self.view.frame.size.height)
print(radius)
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height), cornerRadius: 0)
let circlePath = UIBezierPath(roundedRect: CGRect(x: 0, y: self.view.frame.size.height/2 - radius/2 , width: radius, height: radius), cornerRadius: radius/2)
path.append(circlePath)
path.usesEvenOddFillRule = true
fillLayer.path = path.cgPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = UIColor.black.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)
it works fine if the initial view is portrait or landscape as show in the image
However if the view changes from portrait to landscape or landscape to portrait
The layer is not properly oriented as shown
It looks like you'll need to update the frame of your path whenever the user rotates their device. You're looking for something like the answer here.
If you need to animate along with the transition, try something like this:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { context in
// update frames here
}, completion: nil)
}

How to get a landscape cropped image without rotation in Swift 3/4

I hate to say that I'm the new guy to iOS development but the shoe fits...
My app needs to either load an image/capture one from camera (which it does) but then needs to crop in LANDSCAPE.
I now have an extension that takes the image and shrinks it down to a landscape image (looks good if the camera is taken from landscape position OR scrunches it to a landscape if taken in portrait). It places it in my "viewfinder" as a tiny landscape image (seen below) and I can swipe outward to make it the size of the "view finder" but that's not what I need.
(the "viewfinder" I had to draw because I didn't put borders on it but you can see the thumbnail in the top left)
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
setImageToCrop(image: info[UIImagePickerControllerOriginalImage] as!
UIImage)
picker.dismiss(animated: true, completion: nil)
}
// -------------
func setImageToCrop(image:UIImage){
imageView.image = image
imageViewWidth.constant = image.size.width
imageViewHeight.constant = image.size.height
let scaleHeight = scrollView.frame.size.width/image.size.width
let scaleWidth = scrollView.frame.size.height/image.size.height
scrollView.minimumZoomScale = max(scaleWidth, scaleHeight)
scrollView.zoomScale = max(scaleWidth, scaleHeight)
self.cropButton.isHidden = false
}
// ------------------
#IBAction func cropButtonPressed(_ sender: Any) {
let scale:CGFloat = 1/scrollView.zoomScale
let x:CGFloat = scrollView.contentOffset.x * scale
let y:CGFloat = scrollView.contentOffset.y * scale
let width:CGFloat = scrollView.frame.size.width * scale
let height:CGFloat = scrollView.frame.size.height * scale
let croppedCGImage = imageView.image?.cgImage?.cropping(to: CGRect(x: x, y: y, width: width, height: height))
let croppedImage = UIImage(cgImage: croppedCGImage!)
// "now you can use croppedImage as you like...."
setImageToCrop(image: croppedImage)
}
// ---------------
And then the extension that shrinks the image...
public extension UIImage {
/// Extension to fix orientation of an UIImage without EXIF
func fixOrientation() -> UIImage {
guard let cgImage = cgImage else { return self }
if imageOrientation == .up { return self }
var transform = CGAffineTransform.identity
switch imageOrientation {
case .down, .downMirrored:
transform = transform.translatedBy(x: size.width, y: size.height)
transform = transform.rotated(by: CGFloat(M_PI))
case .left, .leftMirrored:
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.rotated(by: CGFloat(M_PI_2))
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.height)
transform = transform.rotated(by: CGFloat(-M_PI_2))
case .up, .upMirrored:
break
}
switch imageOrientation {
case .upMirrored, .downMirrored:
transform.translatedBy(x: size.width, y: 0)
transform.scaledBy(x: -1, y: 1)
case .leftMirrored, .rightMirrored:
transform.translatedBy(x: size.height, y: 0)
transform.scaledBy(x: -1, y: 1)
case .up, .down, .left, .right:
break
}
if let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: cgImage.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) {
ctx.concatenate(transform)
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
default:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
}
if let finalImage = ctx.makeImage() {
return (UIImage(cgImage: finalImage))
}
}
// something failed -- return original
return self
}
}
I have been around and around with different answers in StackOverFlow but haven't found the solution I'm looking for. I first found an extension that was the "fix all holy grail" but it did nothing. (The app still crops the image but stil rotates when it's saved.)
I'm 100% sure it's something stupid/small that I'm missing but I'm now lost in my code and if it means anything, I've been chasing this issue for WEEKS, if not MONTHS, now. I didn't want to ask unless I absolutely had to. Is there a better way to get an image and crop it it landscape without having it rotate? or is there a fix to this code?

Losing image orientation while converting an image to CGImage

I'm facing an image orientation issue when cropping a square portion of an image out of a rectangular original image. When image is in landscape, it's fine. But when it is in portrait, it seems that the image orientation is not preserved, which result in an image with wrong orientation AND bad crop:
func cropImage(cropRectangleCoordinates: CGRect) {
let croppedImage = originalImage
let finalCroppedImage : CGImageRef = CGImageCreateWithImageInRect(croppedImage.CGImage, cropRectangleCoordinates)
finalImage = UIImage(CGImage: finalCroppedImage)!
}
I think the problem is with croppedImage.CGImage. Here the image gets converted to CGImage, but it seems not to preserve the orientation.
It's easy to preserve orientation by using UIImage only, but to make the crop, image needs to be temporarily CGImage and this is the problem. Even if I reorient the image when converting back to UIImage, it might be in the correct orientation but the damage is already done when cropping CGImage.
This is a swift question, so please answer in swift, as the solution can be different in Objective-C.
SWIFT 3:
convert rotated cgImage to UIImage by this method
UIImage(cgImage:croppedCGImage, scale:originalImage.scale, orientation:originalImage.imageOrientation)
Source #David Berry answer
Here's a UIImage extension I wrote after looking after looking at several older pieces of code written by others. It's written in Swift 3 and uses the iOS orientation property plus CGAffineTransform to re-draw the image in proper orientation.
SWIFT 3:
public extension UIImage {
/// Extension to fix orientation of an UIImage without EXIF
func fixOrientation() -> UIImage {
guard let cgImage = cgImage else { return self }
if imageOrientation == .up { return self }
var transform = CGAffineTransform.identity
switch imageOrientation {
case .down, .downMirrored:
transform = transform.translatedBy(x: size.width, y: size.height)
transform = transform.rotated(by: CGFloat(M_PI))
case .left, .leftMirrored:
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.rotated(by: CGFloat(M_PI_2))
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.height)
transform = transform.rotated(by: CGFloat(-M_PI_2))
case .up, .upMirrored:
break
}
switch imageOrientation {
case .upMirrored, .downMirrored:
transform.translatedBy(x: size.width, y: 0)
transform.scaledBy(x: -1, y: 1)
case .leftMirrored, .rightMirrored:
transform.translatedBy(x: size.height, y: 0)
transform.scaledBy(x: -1, y: 1)
case .up, .down, .left, .right:
break
}
if let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: cgImage.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) {
ctx.concatenate(transform)
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
default:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
}
if let finalImage = ctx.makeImage() {
return (UIImage(cgImage: finalImage))
}
}
// something failed -- return original
return self
}
}
I found a solution.... time will tell if it's robust enough, but it seems to work in all situations. That was a vicious bug to fix.
So the problem is that UIImage, in some case only, lose it's orientation when converted to CGImage. It affects portraits image, that are automatically put in landscape mode. So image that are landscape by default are not affected.
But where the bug is vicious is that it doesn't affect ALL portrait images !! Also imageorientation value won't help for some image.
Those problematic images are images that user has in it's library that he got from email, messages, or saved from the web, so not taken with a camera. These images possibly don't have orientation information, and thus in some case, an image in portrait.... REMAINS in portrait when converted to CGImage. I really got stuck on that until I realized that some of my image in my device library were saved from messages or emails.
So the only reliable way I found to guess which image will be reoriented, is to create both version of a given image: UIImage, and CGImage, and compare their height value. If they are equal, then the CGImage version will not be rotated and you could work with it as expected.
But if they height are different, you can be sure that the CGImage conversion from CGImageCreateWithImageInRect will landscapize the image.
In this case only, I swap the x/y coordinate of origin, that I pass as rectangle coordinate to crop and it treats those special images correctly.
That was a long post, but the main idea is to compare CGImage height to UIImage width, and if they are different, expect origin point to be inverted.
This is THE answer, credit to #awolf (Cropping an UIImage). Handles scale and orientation perfectly. Just call this method on the image you want to crop, and pass in the cropping CGRect without worrying about scale or orientation. Feel free to check whether cgImage is nil instead of force unwrapping it like I did here.
extension UIImage {
func croppedInRect(rect: CGRect) -> UIImage {
func rad(_ degree: Double) -> CGFloat {
return CGFloat(degree / 180.0 * .pi)
}
var rectTransform: CGAffineTransform
switch imageOrientation {
case .left:
rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
case .right:
rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
case .down:
rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
default:
rectTransform = .identity
}
rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)
let imageRef = self.cgImage!.cropping(to: rect.applying(rectTransform))
let result = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
return result
}
}
Another note: if you are working with imageView embedded in a scrollView, there is one additional step, you have to take the zoom factor into account. Assuming your imageView spans the entire content view of the scrollView, and you use the bounds of the scrollView as the cropping frame, the cropped image can be obtained as
let ratio = imageView.image!.size.height / scrollView.contentSize.height
let origin = CGPoint(x: scrollView.contentOffset.x * ratio, y: scrollView.contentOffset.y * ratio)
let size = CGSize(width: scrollView.bounds.size.width * ratio, let height: scrollView.bounds.size.height * ratio)
let cropFrame = CGRect(origin: origin, size: size)
let croppedImage = imageView.image!.croppedInRect(rect: cropFrame)
Change your UIImage creation call to:
finalImage = UIImage(CGImage:finalCroppedImage, scale:originalImage.scale, orientation:originalImage.orientation)
to maintain the original orientation (and scale) of the image.
SWIFT 5. I added the following as an extension to UIImage. Idea is to force the image inside your UIImage to match that of the UIImage orientation (which only plays a role in how it's displayed). Redrawing the actual image data inside the UIImage "container" will make the corresponding CGImage to have the same orientation
func forceSameOrientation() -> UIImage {
UIGraphicsBeginImageContext(self.size)
self.draw(in: CGRect(origin: CGPoint.zero, size: self.size))
guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
UIGraphicsEndImageContext()
return self
}
UIGraphicsEndImageContext()
return image
}
#JGuo has the only answer that has actually worked. I've updated only a little bit to return an optional UIImage? and for swift-er syntax. I prefer to never implicitly unwrap.
extension UIImage {
func crop(to rect: CGRect) -> UIImage? {
func rad(_ degree: Double) -> CGFloat {
return CGFloat(degree / 180.0 * .pi)
}
var rectTransform: CGAffineTransform
switch imageOrientation {
case .left:
rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
case .right:
rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
case .down:
rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
default:
rectTransform = .identity
}
rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)
guard let imageRef = self.cgImage?.cropping(to: rect.applying(rectTransform)) else { return nil }
let result = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
return result
}
}
Here's its implementation as a computed property in my ViewController.
var croppedImage: UIImage? {
guard let image = self.image else { return nil }
let ratio = image.size.height / self.contentSize.height
let origin = CGPoint(x: self.contentOffset.x * ratio, y: self.contentOffset.y * ratio)
let size = CGSize(width: self.bounds.size.width * ratio, height: self.bounds.size.height * ratio)
let cropFrame = CGRect(origin: origin, size: size)
let croppedImage = image.crop(to: cropFrame)
return croppedImage
}

Resources