Calculate position in image after ScaleAspectFill in UIImageView - ios

I'm loading images of various sizes inside an UIImageView. Some one the images come with annotations, each annotation has a unique position in the image. The annotation coordinates are relative to the original image size.
How do I transform the coordinates so the annotation show up at the same position in the UIImageView after resizing?
I could use AVMakeRectWithAspectRatioInsideRect to calculate the new position, but this only works with ScaleAspectFit and not ScaleAspectFill which I must use.

You need to:
calculate the aspectRatio for aspectFill
multiply the x,y coords for your annotation by the aspectRatio
adjust that point by the amount of the image that extends outside the view frame
This is one way to do it (a little verbose, for clarity):
func aspectFillPoint(for point: CGPoint, in view: UIImageView) -> CGPoint {
guard let img = view.image else {
return CGPoint.zero
}
// imgSize will be modified
var imgSize = img.size
let viewSize = view.frame.size
let aspectWidth = viewSize.width / imgSize.width
let aspectHeight = viewSize.height / imgSize.height
// calculate aspectFill ratio
let f = max(aspectWidth, aspectHeight)
// scale imgSize
imgSize.width *= f
imgSize.height *= f
// unless aspect ratio of view is the same as image,
// it will either extend above and below or left and right
// of the view frame
let xOffset = (viewSize.width - imgSize.width) / 2.0
let yOffset = (viewSize.height - imgSize.height) / 2.0
// scale the original point, and adjust for offsets
return CGPoint(
x: (point.x * f) + xOffset,
y: (point.y * f) + yOffset
)
}
Assuming you have an image assigned to theImageView, which is set to .scaleAspectFill, you can call it like this:
let annotationPoint = CGPoint(x: 232, y: 148)
var newPoint = aspectFillPoint(for: annotationPoint, in: theImageView)
// Note: newPoint is relative to the View Bounds, so unless the
// imageView is at 0,0 we need to adjust for position
newPoint.x += theImageView.frame.origin.x
newPoint.y += theImageView.frame.origin.y

Related

How to get x and y position of UIImage in UIImageView?

I want to get original x and y position of UIImage when we set it in UIImageView with scaleAspectFill.
As we know in scaleAspectFill, some of the portion is clipped. So as per my requirement I want to get x and y value (it may be - value I don't know.).
Here is the original image from gallery
Now I am setting this above image to my app view.
So as above situation, I want to get it's hidden x, y position of image which are clipped.
Can any one tell how to get it?
Use following extension
extension UIImageView {
var imageRect: CGRect {
guard let imageSize = self.image?.size else { return self.frame }
let scale = UIScreen.main.scale
let imageWidth = (imageSize.width / scale).rounded()
let frameWidth = self.frame.width.rounded()
let imageHeight = (imageSize.height / scale).rounded()
let frameHeight = self.frame.height.rounded()
let ratio = max(frameWidth / imageWidth, frameHeight / imageHeight)
let newSize = CGSize(width: imageWidth * ratio, height: imageHeight * ratio)
let newOrigin = CGPoint(x: self.center.x - (newSize.width / 2), y: self.center.y - (newSize.height / 2))
return CGRect(origin: newOrigin, size: newSize)
}
}
Usage
let rect = imageView.imageRect
print(rect)
UI Test
let testView = UIView(frame: rect)
testView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
imageView.superview?.addSubview(testView)
Use below extension to find out accurate details of Image in ImageView.
extension UIImageView {
var contentRect: CGRect {
guard let image = image else { return bounds }
guard contentMode == .scaleAspectFit else { return bounds }
guard image.size.width > 0 && image.size.height > 0 else { return bounds }
let scale: CGFloat
if image.size.width > image.size.height {
scale = bounds.width / image.size.width
} else {
scale = bounds.height / image.size.height
}
let size = CGSize(width: image.size.width * scale, height: image.size.height * scale)
let x = (bounds.width - size.width) / 2.0
let y = (bounds.height - size.height) / 2.0
return CGRect(x: x, y: y, width: size.width, height: size.height)
}
}
How to test
let rect = imgTest.contentRect
print("Image rect:", rect)
Reference: https://www.hackingwithswift.com/example-code/uikit/how-to-find-an-aspect-fit-images-size-inside-an-image-view
If you want to show image like it shows in gallery then you can use contraints
"H:|[v0]|" and "V:|[v0]|" and in imageview use .aspectFit
And if you want the image size you can use imageView.image!.size and calculate the amount of image which is getting cut. In aspectFill the width is matched to screenwidth and accordingly the height gets increased. So I guess you can find how how much amount of image is getting cut.
Try this Library ImageCoordinateSpace
I am not sure if it works for you or not, but it has a feature to convert CGPoint from image coordinates to any view coordinates and vice versa.

Rotate a view from its origin towards another view

I am trying to rotate a view towards another views center point(Remember not around, its towards).
Assume I have 2 views placed like this
Now I want to rotate the topview to point the bottom view like this
so this what I did
Change the top views anchor point to its origin. so that it can rotate and point its edge to the bottom view
Calculated the angle between the first views origin point and the bottom views center
And applied the calculated transform to the top view.
Below the code I am using
let rect = CGRect(x: 70, y: 200, width: 300, height: 100)
let rectView = UIView(frame: rect)
rectView.layer.borderColor = UIColor.green.cgColor;
rectView.layer.borderWidth = 2;
let endView = UIView(frame: CGRect(x: 250, y: 450, width: 70, height: 70))
endView.layer.borderColor = UIColor.green.cgColor;
endView.layer.borderWidth = 2;
let end = endView.center;
self.view.addSubview(endView)
self.view.addSubview(rectView!)
rectView.setAnchorPoint(CGPoint.zero)
let angle = rectView.bounds.origin.angle(to: end);
UIView.animate(withDuration: 3) {
rectView.transform = rectView.transform.rotated(by: angle)
}
I am using this extension from Get angle from 2 positions to calculate the angle between 2 points
extension CGPoint {
func angle(to comparisonPoint: CGPoint) -> CGFloat {
let originX = comparisonPoint.x - self.x
let originY = comparisonPoint.y - self.y
let bearingRadians = atan2f(Float(originY), Float(originX))
var bearingDegrees = CGFloat(bearingRadians).degrees
while bearingDegrees < 0 {
bearingDegrees += 360
}
return bearingDegrees
}
}
extension CGFloat {
var degrees: CGFloat {
return self * CGFloat(180.0 / M_PI)
}
}
extension UIView {
func setAnchorPoint(_ point: CGPoint) {
var newPoint = CGPoint(x: bounds.size.width * point.x, y: bounds.size.height * point.y)
var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y);
newPoint = newPoint.applying(transform)
oldPoint = oldPoint.applying(transform)
var position = layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
layer.position = position
layer.anchorPoint = point
}
}
But this isn't working as expected, the rotation is way off. Check the below screen capture of the issue
I assume this issue is related to how the angle is calculated, but I could't figure out what?
any help is much appreciated.
Angles need to be in radians
The rotation angle needs to be specified in radians:
Change:
rectView.transform = rectView.transform.rotated(by: angle)
to:
rectView.transform = rectView.transform.rotated(by: angle / 180.0 * .pi)
or change your angle(to:) method to return radians instead of degrees.
Use frame instead of bounds
Also, you need to use the frame of your rectView when computing the angle. The bounds of a view is its internal coordinate space, which means its origin is always (0, 0). You want the frame which is the coordinates of the view in its parent's coordinate system.
Change:
let angle = rectView.bounds.origin.angle(to: end)
to:
let angle = rectView.frame.origin.angle(to: end)
Note: Because your anchorPoint is the corner of rectView, this will point the top edge of rectView to the center of endView. One way to fix that would be to change your anchorPoint to the center of the left edge of rectView and then use that point to compute your angle.

Get the position of imageview dynamically

I need to get the dynamic x,y position of the image view.
I have tried this
self.imgView.bounds.origin.y
and
self.imgView.frame.origin.x
I am getting 0.0 all the time.
How can i get this?
Thanks
The bounds.origin of any view should be (0,0) at all times since it is the coordinate system of the view itself (relative to its origin)
The frame.origin is in the coordinate system of the its superview, so if it's 0,0, then that might be that its origin is at its superview's origin.
Maybe you want to know the origin with respect to the screen? If so,
let posInWindow = v.convert(v.bounds.origin, to: nil)
Please use below code to get rect in UIImageVIew
This code in swift3 And your ImageViewContentMode should be Aspect fit
func calculateRectOfImageInImageView(imageView: UIImageView) -> CGRect {
let imageViewSize = imageView.frame.size
let imgSize = imageView.image?.size
guard let imageSize = imgSize, imgSize != nil else {
return CGRect.zero
}
let scaleWidth = imageViewSize.width / imageSize.width
let scaleHeight = imageViewSize.height / imageSize.height
let aspect = fmin(scaleWidth, scaleHeight)
var imageRect = CGRect(x: 0, y: 0, width: imageSize.width * aspect, height: imageSize.height * aspect)
// Center image
imageRect.origin.x = (imageViewSize.width - imageRect.size.width) / 2
imageRect.origin.y = (imageViewSize.height - imageRect.size.height) / 2
// Add imageView offset
imageRect.origin.x += imageView.frame.origin.x
imageRect.origin.y += imageView.frame.origin.y
return imageRect
}
Bounds will always return an x and y of 0, you want to use frame. In your example these values may be correct? Try adding your imageView in the centre of the view and logging the:
self.imgView.frame.origin.x
self.imgView.frame.origin.y

UIImageView cropping square

First of all, my app asks the user to pick a photo from collectionview or taken camera pictures, then navigate to cropping image editor, so that our final image will be square according to cropping area position.But, the problem is there is few source detail about it. Without UIImagePicker, how is to be done?
I also tried taking square picture, with no success.
I implemented UIPinchGesture imageview so that user can zoom in or out, but there is no cropping square on the image view. I have to add cropping area.
This is cropping UIImage function:
func croppImageByRect() -> UIImage {
let ratio: CGFloat = 1 // square
let delta: CGFloat
let offSet: CGPoint
//make a new square size, that is the resized imaged width
let newSize = CGSizeMake(size.width, (size.width - size.width / 8))
//figure out if the picture is landscape or portrait, then
//calculate scale factor and offset
if (size.width > size.height) {
delta = (ratio * size.width - ratio * size.height)
offSet = CGPointMake(delta / 2, 0)
} else {
delta = (ratio * size.height - ratio * size.width)
offSet = CGPointMake(0, delta / 2)
}
//make the final clipping rect based on the calculated values
let clipRect = CGRectMake(-offSet.x, -offSet.y, (ratio * size.width) + delta, (ratio * size.height) + delta)
//start a new context, with scale factor 0.0 so retina displays get
//high quality image
UIGraphicsBeginImageContextWithOptions(newSize, true, 0.0)
UIRectClip(clipRect)
drawInRect(clipRect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
Well, to start, this line:
// make a new square size, that is the resized imaged width
let newSize = CGSizeMake(size.width, (size.width - size.width / 8))
Will not result in a square size since width and height are not the same.

UIImageView get the position of the showing Image

I have a UIImageView which shows an UIImage.
The UIImage may change to other UIImage in different size, and the position and the size of the UIImage inside will change according according to it.
My Problem is that i'm trying add a view that will be at the end of the UIImage (which change all the time) and all I can get is the frame of the UIImageView (which stay full screen all the time).
How can i get the "frame" of current showing UIImage ?
Swift 4.2 & 5.0
func calculateRectOfImageInImageView(imageView: UIImageView) -> CGRect {
let imageViewSize = imageView.frame.size
let imgSize = imageView.image?.size
guard let imageSize = imgSize else {
return CGRect.zero
}
let scaleWidth = imageViewSize.width / imageSize.width
let scaleHeight = imageViewSize.height / imageSize.height
let aspect = fmin(scaleWidth, scaleHeight)
var imageRect = CGRect(x: 0, y: 0, width: imageSize.width * aspect, height: imageSize.height * aspect)
// Center image
imageRect.origin.x = (imageViewSize.width - imageRect.size.width) / 2
imageRect.origin.y = (imageViewSize.height - imageRect.size.height) / 2
// Add imageView offset
imageRect.origin.x += imageView.frame.origin.x
imageRect.origin.y += imageView.frame.origin.y
return imageRect
}
Swift 3.0
// MARK: - Create Rect
func calculateRectOfImageInImageView(imageView: UIImageView) -> CGRect {
let imageViewSize = imageView.frame.size
let imgSize = imageView.image?.size
guard let imageSize = imgSize, imgSize != nil else {
return CGRect.zero
}
let scaleWidth = imageViewSize.width / imageSize.width
let scaleHeight = imageViewSize.height / imageSize.height
let aspect = fmin(scaleWidth, scaleHeight)
var imageRect = CGRect(x: 0, y: 0, width: imageSize.width * aspect, height: imageSize.height * aspect)
// Center image
imageRect.origin.x = (imageViewSize.width - imageRect.size.width) / 2
imageRect.origin.y = (imageViewSize.height - imageRect.size.height) / 2
// Add imageView offset
imageRect.origin.x += imageView.frame.origin.x
imageRect.origin.y += imageView.frame.origin.y
return imageRect
}
For Swift < 3.0
Here is the above method in Swift. Again, assuming that contentMode is set to .ScaleAspectFit If there is no image on the given imageView CGRectZero will be returned.
func calculateRectOfImageInImageView(imageView: UIImageView) -> CGRect {
let imageViewSize = imageView.frame.size
let imgSize = imageView.image?.size
guard let imageSize = imgSize where imgSize != nil else {
return CGRectZero
}
let scaleWidth = imageViewSize.width / imageSize.width
let scaleHeight = imageViewSize.height / imageSize.height
let aspect = fmin(scaleWidth, scaleHeight)
var imageRect = CGRect(x: 0, y: 0, width: imageSize.width * aspect, height: imageSize.height * aspect)
// Center image
imageRect.origin.x = (imageViewSize.width - imageRect.size.width) / 2
imageRect.origin.y = (imageViewSize.height - imageRect.size.height) / 2
// Add imageView offset
imageRect.origin.x += imageView.frame.origin.x
imageRect.origin.y += imageView.frame.origin.y
return imageRect
}
The following will answer your question, assuming your UIImageView used UIViewContentModeAspectFit:
You have to regard the image sizing of the image inside UIImageView. This depends on how you set the contentMode. According your description, I assume you are using UIViewContentModeAspectFit. The resulting image will also be centered in the UIImageView so you also have to consider this for the calculation.
-(CGRect )calculateClientRectOfImageInUIImageView:(UIImageView *)imgView
{
CGSize imgViewSize=imgView.frame.size; // Size of UIImageView
CGSize imgSize=imgView.image.size; // Size of the image, currently displayed
// Calculate the aspect, assuming imgView.contentMode==UIViewContentModeScaleAspectFit
CGFloat scaleW = imgViewSize.width / imgSize.width;
CGFloat scaleH = imgViewSize.height / imgSize.height;
CGFloat aspect=fmin(scaleW, scaleH);
CGRect imageRect={ {0,0} , { imgSize.width*=aspect, imgSize.height*=aspect } };
// Note: the above is the same as :
// CGRect imageRect=CGRectMake(0,0,imgSize.width*=aspect,imgSize.height*=aspect) I just like this notation better
// Center image
imageRect.origin.x=(imgViewSize.width-imageRect.size.width)/2;
imageRect.origin.y=(imgViewSize.height-imageRect.size.height)/2;
// Add imageView offset
imageRect.origin.x+=imgView.frame.origin.x;
imageRect.origin.y+=imgView.frame.origin.y;
return(imageRect);
}
For a better illustration of the differences between the three content modes, see below:
I recommend using built in function AVMakeRectWithAspectRatio.
func AVMakeRectWithAspectRatioInsideRect(_ aspectRatio: CGSize, _ boundingRect: CGRect) -> CGRect
Parameters:
aspectRatio:
The width and height ratio (aspect ratio) you want to maintain.
boundingRect:
The bounding rectangle you want to fit into.
Return Value
Returns a scaled CGRect that maintains the aspect ratio specified by aspectRatio that fits within bounding Rect.
let boundingBox = AVMakeRectWithAspectRatioInsideRect(backgroundImage.size, frame)
Based on the wonderfully simple solution from Janusz, here's what I did:
let visibleRect = AVMakeRect(aspectRatio: CGSize(width: image.size.width, height: image.size.height), insideRect: self.frame)
if visibleRect.contains(point) {
// Do something great here...
}
Swift 3.0
I know its quite late but might help someone in future. Its very simple and inbuilt solution provided by iOS. Just need to:
import AVFoundation
let imageRect = AVMakeRect(aspectRatio: image.size, insideRect: self.imageView.bounds)

Resources