Rotate UIImageView inside UIScrollView in Swift - swift

I'm working in a basic photo editor which is supposed to zoom, rotate and flip a photo. I'm using an image view (aspect fill) inside a scroll view which allows me to zoom easily. But when I try to rotate or flip the result is not what I would expect. The image view keeps the original frame and seems like rotating the image. The scroll view zoom scale changes. Any suggestions on how to do this?
It also would be great to have suggestions about setting the image view anchor point to match the scroll view anchor point before transforming cause I don't want to display a different portion of the image after transforming, just the same portion of the image, but rotated.
View stack before transform:
View stack after applying rotation:
My code so far:
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
setZoomScale()
scrollView.zoomScale = scrollView.minimumZoomScale
}
#IBAction func rotateAnticlockwise(_ sender: UIButton) {
rotationAngle -= 0.5
transformImage()
}
func transformImage(){
var transform = CGAffineTransform.identity
transform = transform.rotated(by: .pi * rotationAngle)
imageView.transform = transform
}
func setZoomScale(){
let imageSize = imageView.image!.size
let smallestDimension = min(imageSize.width, imageSize.height)
scrollView.minimumZoomScale = scrollView.bounds.width / smallestDimension
scrollView.maximumZoomScale = smallestDimension / scrollView.bounds.width
}

I think you are looking for, e.g. :
imageView.transform = CGAffineTransform(rotationAngle: 0.5)

Related

Swift - Camera with device rotation in Object Detection

I was looking at this tutorial (https://developer.apple.com/documentation/vision/recognizing_objects_in_live_capture) and the problem is that when you rotate the device the view of the camera don't change frame size so you'll have a black space.
Any ideas to solve this problem?
In addition, I made my version of the app in SwiftUI (but the problem persists), so solution using SwiftUI are also appreciated!
Thanks in advance
I'm not sure how in SwiftUI, but here's how in UIKit:
Try setting the videoGravity:
cameraView.videoPreviewLayer.videoGravity = .resizeAspectFill
Then, this should take care of the orientation. This is based off of this answer.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let cameraPreviewTransform = self.cameraView.transform
coordinator.animate { (context) in
let deltaTransform = coordinator.targetTransform
let deltaAngle: CGFloat = atan2(deltaTransform.b, deltaTransform.a)
var currentRotation = atan2(cameraPreviewTransform.b, cameraPreviewTransform.a)
// Adding a small value to the rotation angle forces the animation to occur in a the desired direction, preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
currentRotation += -1 * deltaAngle + 0.0001;
self.cameraView.layer.setValue(currentRotation, forKeyPath: "transform.rotation.z")
self.cameraView.layer.frame = self.view.bounds
} completion: { (context) in
let currentTransform : CGAffineTransform = self.cameraView.transform
self.cameraView.transform = currentTransform
}
}

How do you move the uiimage inside the uiimageview to the right while retaining the aspect ratio of the original image?

I want to move an image to the right when a user imports it using uiimagepicker but when I set content mode = .right this occurs: The image enlarges for some reason and it looks like it moves to the left
Is there any way to keep the aspect ratio of the uiimageview and the aspect ratio of the imported image, and while also moving it to the right inside the image view.
This is how I want it to be
Here is one approach: custom view, using a sublayer with the content set to the image...
add a CALayer as a sublayer
calculate the aspect-scaled rectangle for the image inside the view's bounds
set the image layer's frame to that scaled rect
then set the layer's origin based on the desired alignment
A simple example:
class AspectAlignImageView: UIView {
enum AspectAlign {
case top, left, right, bottom, center
}
// this is an array so we can set two options
// if, for example, we don't know if the image will be
// taller or narrower
// for example:
// [.top, .right] will put a
// wide image aligned top
// narrow image aligned right
public var alignment: [AspectAlign] = [.center]
public var image: UIImage?
private let imgLayer: CALayer = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
// make sure we have an image
if let img = image {
// only add the sublayer once
if imgLayer.superlayer == nil {
layer.addSublayer(imgLayer)
}
imgLayer.contentsGravity = .resize
imgLayer.contents = img.cgImage
// calculate the aspect-scaled rect inside our bounds
var scaledImageRect = CGRect.zero
let aspectWidth:CGFloat = bounds.width / img.size.width
let aspectHeight:CGFloat = bounds.height / img.size.height
let aspectRatio:CGFloat = min(aspectWidth, aspectHeight)
scaledImageRect.size.width = img.size.width * aspectRatio
scaledImageRect.size.height = img.size.height * aspectRatio
// set image layer frame to aspect-scaled rect
imgLayer.frame = scaledImageRect
// align as specified
if alignment.contains(.top) {
imgLayer.frame.origin.y = 0
}
if alignment.contains(.left) {
imgLayer.frame.origin.x = 0
}
if alignment.contains(.bottom) {
imgLayer.frame.origin.y = bounds.maxY - scaledImageRect.height
}
if alignment.contains(.right) {
imgLayer.frame.origin.x = bounds.maxX - scaledImageRect.width
}
}
}
}
class TestAlignViewController: UIViewController {
let testView = AspectAlignImageView()
override func viewDidLoad() {
super.viewDidLoad()
testView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testView)
NSLayoutConstraint.activate([
// constrain test view 240x240 square
testView.widthAnchor.constraint(equalToConstant: 240.0),
testView.heightAnchor.constraint(equalTo: testView.widthAnchor),
// centered in view
testView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
testView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
if let img = UIImage(named: "bottle") {
testView.image = img
}
testView.alignment = [.right]
// so we can see the actual view frame
testView.backgroundColor = .green
}
}
Using this image:
in a 240x240 view (view background set green so we can see its frame), we get this result:
Set your UIImage Content mode to aspect fill or aspect fit. Then use auto layout.

Change height and width of view according to scrollview scroll horizontally using auto layout

Container View hierarchy:-
Container View
Scrollview
view left
view center
view right
FloatingBottomView
I need to change height and width constrain of FloatingBottomView according to scrollview scroll horizontally.
Initial Outlet:-
#IBOutlet weak var constantFloatingBottomViewWidth: NSLayoutConstraint! // 300
#IBOutlet weak var constantFloatingBottomViewHeight: NSLayoutConstraint! // 70
override func viewWillAppear(_ animated: Bool) {
self.scrollView.contentOffset.x = self.view.bounds.width
// view center in scrollview
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x < self.view.bounds.width {
// when scrollview move view center to view left, constantFloatingBottomViewWidth and height goes to down at some point
}
else if scrollView.contentOffset.x > self.view.bounds.width {
// when scrollview move view left to view center, constantFloatingBottomViewWidth & height goes up to same point range while it down and back to original constantFloatingBottomViewWidth and height
}
}
i have tried using this way to get some scale in scrollViewDidScroll method.
scroll quick left to right or vice-versa did not get exactly out-put
let scale = abs(scrollView.contentOffset.x / self.view.bounds.width)
print("scale:=\(scale)")
I think you need to give button animation like this
For this you have nothing to do just add animation to button like follow
call setupButtonanimation in viewDidLoad
func setupButtonAnimation()
{
let animation = CABasicAnimation.init(keyPath: "transform.scale")
animation.fromValue = 1.0
animation.toValue = 0.45
animation.duration = 1.0
//Set the speed of the layer to 0 so it doesn't animate until we tell it to
self.btn1.layer.speed = 0.0;
self.btn1.layer.add(animation, forKey: "transform");
}
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
let screenSize = self.view.bounds.size
if let btn = self.btn1
{
var factor:CGFloat = 1.0
factor = scrollView.contentOffset.x / screenSize.width
if factor > 1
{
factor = 2 - factor
}
print(factor)
//This will change the size
let timeOffset = CFTimeInterval(1-factor)
btn.layer.timeOffset = timeOffset
}
}

Image Cropping grabbing the wrong portion of UIImage during crop

I've been working on making a view controller that will crop an image down to a specific size with some draggable control points and the background image outside of the crop zone dimmed.
For some reason whenever the image is cropped, it is grabbing the wrong reference. I've looked at just about every other post on this to deal with cropping.
Here is my setup for the Storyboard:
I've asked a few other people including a tutor and mentor from a course that I'm taking, but we all seem to be stumped.
I can select a frame by dragging the UL UR DL DR corners around the view controller like this:
But when I press the button and use the crop function I've written, I get something that is not the correct crop based on the framed selection.
I also get this error message during the cropping proceedure:
2016-09-07 23:36:38.962 ImageCropView[33133:1056024]
<UIView: 0x7f9cfa42c730; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x7f9cfa408400>>'s window
is not equal to <ImageCropView.CroppedImageViewController: 0x7f9cfa43f9b0>'s view's window!
The offending part of the code must be somewhere in one of the functions below.
Here is the cropping function:
func cropImage(image: UIImage, toRect rect: CGRect) -> UIImage {
func rad(deg: CGFloat) -> CGFloat {
return deg / 180.0 * CGFloat(M_PI)
}
// determine the orientation of the image and apply a transformation to the crop rectangle to shift it to the correct position
var rectTransform: CGAffineTransform
switch image.imageOrientation {
case .Left:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(90)), 0, -image.size.height)
case .Right:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-90)), -image.size.width, 0)
case .Down:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-180)), -image.size.width, -image.size.height)
default:
rectTransform = CGAffineTransformIdentity
}
// adjust the transformation scale based on the image scale
rectTransform = CGAffineTransformScale(rectTransform, UIScreen.mainScreen().scale, UIScreen.mainScreen().scale)
// apply the transformation to the rect to create a new, shifted rect
let transformedCropSquare = CGRectApplyAffineTransform(rect, rectTransform)
// use the rect to crop the image
let imageRef = CGImageCreateWithImageInRect(image.CGImage, transformedCropSquare)
// create a new UIImage and set the scale and orientation appropriately
let result = UIImage(CGImage: imageRef!, scale: image.scale, orientation: image.imageOrientation)
return result
}
Here are the functions to set and translate the mask view
func setTopMask(){
let path = CGPathCreateWithRect(cropViewMask.frame, nil)
topMaskLayer.path = path
topImageView.layer.mask = topMaskLayer
}
func translateMask(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(self.view)
sender.view!.center = CGPointMake(sender.view!.center.x + translation.x, sender.view!.center.y + translation.y)
// print(sender.translationInView(self.view))
sender.setTranslation(CGPointZero, inView: self.view)
// print("panned mask")
if sender.state == .Ended {
printFrames()
}
}
func setCropMaskFrame() {
let x = ulCorner.center.x
let y = ulCorner.center.y
let width = urCorner.center.x - ulCorner.center.x
let height = blCorner.center.y - ulCorner.center.y
cropViewMask.frame = CGRectMake(x, y, width, height)
setTopMask()
}
I know this was long time ago...Just a thought, I ran into similar problem and what I found is that the frames for cropping are most probably correct. The problem lies in the actual size of the picture you're trying to crop. I solved the issue by aligning sizes of my view which holds the picture, with the actual picture size (in points). Then the cropping area cropped what was selected. I know this is probably not a solution, just sharing my experience, hope it helps to turn on some lightbulbs :)

How to enable zoom in UIScrollView

How do I enable zooming in a UIScrollView?
Answer is here:
A scroll view also handles zooming and panning of content. As the user makes a pinch-in or pinch-out gesture, the scroll view adjusts the offset and the scale of the content. When the gesture ends, the object managing the content view should update subviews of the content as necessary. (Note that the gesture can end and a finger could still be down.) While the gesture is in progress, the scroll view does not send any tracking calls to the subview.
The UIScrollView class can have a delegate that must adopt the UIScrollViewDelegate protocol. For zooming and panning to work, the delegate must implement both viewForZoomingInScrollView: and scrollViewDidEndZooming:withView:atScale:; in addition, the maximum (maximumZoomScale) and minimum (minimumZoomScale) zoom scale must be different.
So:
You need a delegate that implements UIScrollViewDelegate and is set to delegate on your UIScrollView instance
On your delegate you have to implement one method: viewForZoomingInScrollView: (which must return the content view you're interested in zooming). You can also implement scrollViewDidEndZooming:withView:atScale: optionally.
On your UIScrollView instance, you have to set the minimumZoomScale and the maximumZoomScale to be different (they are 1.0 by default).
Note: The interesting thing about this is what if you want to break zooming. Is it enough to return nil in the viewForZooming... method? It does break zooming, but some of the gestures will be messed up (for two fingers). Therefore, to break zooming you should set the min and max zoom scale to 1.0.
Have a read through this Ray Wenderlich tutorial:
http://www.raywenderlich.com/76436/use-uiscrollview-scroll-zoom-content-swift
If you follow through the section 'Scrolling and Zooming a Larger Image' it will get a image up and enable you to pinch and zoom.
In case the link gets altered, here's the main info:
Put this code in your view controller (this sets the main functionality):
override func viewDidLoad() {
super.viewDidLoad()
// 1
let image = UIImage(named: "photo1.png")!
imageView = UIImageView(image: image)
imageView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size:image.size)
scrollView.addSubview(imageView)
// 2
scrollView.contentSize = image.size
// 3
var doubleTapRecognizer = UITapGestureRecognizer(target: self, action: "scrollViewDoubleTapped:")
doubleTapRecognizer.numberOfTapsRequired = 2
doubleTapRecognizer.numberOfTouchesRequired = 1
scrollView.addGestureRecognizer(doubleTapRecognizer)
// 4
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
let scaleHeight = scrollViewFrame.size.height / scrollView.contentSize.height
let minScale = min(scaleWidth, scaleHeight);
scrollView.minimumZoomScale = minScale;
// 5
scrollView.maximumZoomScale = 1.0
scrollView.zoomScale = minScale;
// 6
centerScrollViewContents()
}
Add this to the class:
func centerScrollViewContents() {
let boundsSize = scrollView.bounds.size
var contentsFrame = imageView.frame
if contentsFrame.size.width < boundsSize.width {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0
} else {
contentsFrame.origin.x = 0.0
}
if contentsFrame.size.height < boundsSize.height {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0
} else {
contentsFrame.origin.y = 0.0
}
imageView.frame = contentsFrame
}
And then this if you want the double tap gesture to be recognised:
func scrollViewDoubleTapped(recognizer: UITapGestureRecognizer) {
// 1
let pointInView = recognizer.locationInView(imageView)
// 2
var newZoomScale = scrollView.zoomScale * 1.5
newZoomScale = min(newZoomScale, scrollView.maximumZoomScale)
// 3
let scrollViewSize = scrollView.bounds.size
let w = scrollViewSize.width / newZoomScale
let h = scrollViewSize.height / newZoomScale
let x = pointInView.x - (w / 2.0)
let y = pointInView.y - (h / 2.0)
let rectToZoomTo = CGRectMake(x, y, w, h);
// 4
scrollView.zoomToRect(rectToZoomTo, animated: true)
}
If you want more detail read the tutorial, but that pretty much covers it.
Make sure you set your viewController as the scrollViews delegate and implement:
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
I don't think this is working for iOS 5.0 and Xcode 4.3+
Im looking for the same here, I found this its for images but it may help you.
http://www.youtube.com/watch?v=Ptm4St6ySEI