Cropping part of UIImage with the screen aspect ratio - ios

I've been struggling with this for quite some time now. I'd like to make image cropper similar to the one included in iOS itself when you select your wallpaper. Basically I want the area that user selects cropped from the image with zoom and aspect ratio of the screen (so the user can use the image as a wallpaper later on). Like this:
https://gfycat.com/TornMaleAlligatorsnappingturtle
I've managed to create the interface with UIScrollView and UIImageView:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var imageView: UIImageView!
var croppedImage: UIImage?
#IBOutlet weak var cropButton: UIButton! {
didSet{
cropButton.backgroundColor = UIColor.gray
}
}
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImage(named: "mountains")!
imageView = UIImageView(image: image)
imageView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size:image.size)
imageView.contentMode = .scaleAspectFill
scrollView = UIScrollView(frame: view.bounds)
scrollView.backgroundColor = UIColor.black
scrollView.contentSize = imageView.bounds.size
scrollView.delegate = self
setZoomScale()
//centerScrollViewContents()
scrollView.addSubview(imageView)
view.addSubview(scrollView)
view.bringSubview(toFront: cropButton)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
#IBAction func crop(_ sender: UIButton) {
let rect = CGRect(x: ?, y: ?, width: ?, height: ?)
let croppedCGImage = imageView.image?.cgImage?.cropping(to: rect)
self.croppedImage = UIImage(cgImage: croppedCGImage!)
}
func setZoomScale() {
let imageViewSize = imageView.bounds.size
let scrollViewSize = scrollView.bounds.size
//let widthScale = scrollViewSize.width / imageViewSize.width
let heightScale = scrollViewSize.height / imageViewSize.height
scrollView.minimumZoomScale = heightScale //min(widthScale, heightScale)
scrollView.maximumZoomScale = 3
scrollView.zoomScale = heightScale
print(heightScale)
}
}.
I can zoom and pan around the image no problem. The problem is I don't know how to create the CGRect rectangle that represents the area that is displayed to the user, which is also the area I want to crop from the original image. Any ideas that will put me out of my misery are greatly appreciated!

snapshotImageFromMyView is the output image
self.btn.isHidden = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
UIGraphicsBeginImageContextWithOptions(self.YourView.bounds.size, self.YourView.isOpaque, 0.0)
self.YourView.drawHierarchy(in: self.YourView.bounds, afterScreenUpdates: false)
let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}

Related

UIPageControl and UIScrollView not scrolling

I'm having a relatively simple UIViewController with a UIScrollView that's taking relatively half the screen and a UIImageView placed inside the UIScrollView that's the exact same size as the UIScrollView.
On top of the UIImageView there's a UIPageControl. The point is have a horizontal scrolling and present an image like a slider based on the amount of images in an array. The problem is that the scroll view is not scrolling and I don't know why.
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var pageControl: UIPageControl!
let imagelist = ["3.jpg", "1.jpg", "2.png", "4.png", "5.png"]
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
configurePageControl()
for i in stride(from: 0, to: imagelist.count, by: 1) {
var frame = CGRect.zero
frame.origin.x = self.scrollView.frame.size.width * CGFloat(i)
frame.origin.y = 0
frame.size = self.scrollView.frame.size
scrollView.isPagingEnabled = true
scrollView.isScrollEnabled = true
let myImage:UIImage = UIImage(named: imagelist[i])!
let bgColorFromImage = myImage.averageColor
imageView.image = myImage
imageView.contentMode = UIViewContentMode.scaleAspectFit
imageView.frame = frame
scrollView.backgroundColor = bgColorFromImage
scrollView.addSubview(imageView)
}
self.scrollView.contentSize = CGSize(width: self.scrollView.frame.size.width * CGFloat(imagelist.count), height: self.scrollView.frame.size.height)
pageControl.addTarget(self, action: #selector(changePage), for: UIControlEvents.valueChanged)
}
func configurePageControl() {
self.pageControl.numberOfPages = imagelist.count
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.red
self.pageControl.pageIndicatorTintColor = UIColor.black
self.pageControl.currentPageIndicatorTintColor = UIColor.green
self.imageView.addSubview(pageControl)
}
#objc func changePage() {
let x = CGFloat(pageControl.currentPage) * scrollView.frame.size.width
scrollView.setContentOffset(CGPoint(x: x,y :0), animated: true)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
pageControl.currentPage = Int(pageNumber)
}
This is what the UIStoryboard looks like:
Inside the for loop you use the same object of imageView , you only set the frame and the image
imageView.frame = frame
imageView.image = myImage
but you have to create a new instance of the UIImageView
//
Suppose you have a scrollView in IB with top , leading and trailing constraints to the superView , and a height of say 200 , you can add imageViews dynamically like this
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<10 {
let f = CGRect(x: CGFloat(i) * scrollView.frame.width, y: 0, width: scrollView.frame.width, height: scrollView.frame.height)
let imgV = UIImageView(frame: f)
imgV.image = UIImage(named: "re-fuel.png")
scrollView.addSubview(imgV)
}
scrollView.contentSize = CGSize(width: CGFloat(10) * scrollView.frame.width, height: scrollView.frame.height)
}
}
Result
The problem is that the scroll view itself is positioned with autolayout. Once you do that, you cannot set the contentSize to make the scroll view scrollable. You must use internal constraints to configure the content size.

Zooming into an image from user's touch point with UIScrollView in Swift

The below Swift code manages zooming in and out of an UIImage inside a UIScrollView.
When double tapping, the image zooms into the centre and zooms out to the centre.
Question:
What code changes need to be made to set the zoom in point to be the centre of the image area the user touches on screen?
(For example, if the user double taps the top left of the image, the image would correspondingly zoom into the top left of the image.)
class ScrollViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView(image: UIImage(named: "image.png"))
scrollView = UIScrollView(frame: view.bounds)
scrollView.backgroundColor = UIColor.blackColor()
scrollView.contentSize = imageView.bounds.size
scrollView.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
scrollView.contentOffset = CGPoint(x: 1000, y: 450)
scrollView.addSubview(imageView)
view.addSubview(scrollView)
scrollView.delegate = self
setZoomScale()
setupGestureRecognizer()
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
override func viewWillLayoutSubviews() {
setZoomScale()
}
func setZoomScale() {
let imageViewSize = imageView.bounds.size
let scrollViewSize = scrollView.bounds.size
let widthScale = scrollViewSize.width / imageViewSize.width
let heightScale = scrollViewSize.height / imageViewSize.height
scrollView.minimumZoomScale = min(widthScale, heightScale)
scrollView.zoomScale = 1.0
}
func scrollViewDidZoom(scrollView: UIScrollView) {
let imageViewSize = imageView.frame.size
let scrollViewSize = scrollView.bounds.size
let verticalPadding = imageViewSize.height < scrollViewSize.height ? (scrollViewSize.height - imageViewSize.height) / 2 : 0
let horizontalPadding = imageViewSize.width < scrollViewSize.width ? (scrollViewSize.width - imageViewSize.width) / 2 : 0
scrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
}
func setupGestureRecognizer() {
let doubleTap = UITapGestureRecognizer(target: self, action: "handleDoubleTap:")
doubleTap.numberOfTapsRequired = 2
scrollView.addGestureRecognizer(doubleTap)
}
func handleDoubleTap(recognizer: UITapGestureRecognizer) {
if (scrollView.zoomScale > scrollView.minimumZoomScale) {
scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
} else {
scrollView.setZoomScale(scrollView.maximumZoomScale, animated: true)
}
}
}
According to this post. It works fine with me.
func handleDoubleTap(recognizer: UITapGestureRecognizer) {
if (scrollView.zoomScale > scrollView.minimumZoomScale) {
scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
} else {
let touchPoint = recognizer.locationInView(view)
let scrollViewSize = scrollView.bounds.size
let width = scrollViewSize.width / scrollView.maximumZoomScale
let height = scrollViewSize.height / scrollView.maximumZoomScale
let x = touchPoint.x - (width/2.0)
let y = touchPoint.y - (height/2.0)
let rect = CGRectMake(x, y, width, height)
scrollView.zoomToRect(rect, animated: true)
}
}
Here's a Swift 2.3 function for returning the location of a touch inside a zoomable UIScrollView:
UIScrollView with UIImageView inside
Zoomed or not - scrollView.zoomScale
imageView.contentMode = .ScaleAspectFit
The image can be bigger, high res than imageView and scrollView
Any shape of the image, portrait, landscape or square
imageView.contentMode = .ScaleAspectFit
//import AVFoundation // needed for AVMakeRectWithAspectRatioInsideRect()
func getLocationOfTouchInImageInScrollView(paintLocation:CGPoint)->CGPoint {
let imageSize = imageViewInScrollView.image!.size
let imageFrame = scrollView.frame
let imageRect = AVMakeRectWithAspectRatioInsideRect(imageSize, imageFrame)
let imageHeightToViewHeight = max(imageSize.height, imageSize.width) / imageFrame.size.height
let px = (max(0, min(imageSize.width, ((paintLocation.x - imageRect.origin.x) * imageHeightToViewHeight))))
let py = (max(0, min(imageSize.height, ((paintLocation.y - imageRect.origin.y ) * imageHeightToViewHeight))))
var imageTouchPoint = CGPointMake(CGFloat(px), CGFloat(py))
return imageTouchPoint
}
Took me DAYS to write this one since I couldn't figure out the imageHeightToViewHeight variable, thought there was something else funny going on.

Swift image do not fit in the scrollview

i have a scrollview wich load a image to zoom in and zoom out , the problem is that i want the image to be loaded full like this
http://www.capital.cl/wp-content/uploads/2015/04/avengers.jpg
so the user can see the complete image first
but it looks like this
this is the code
import UIKit
class Paso2: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var noCheckBox2: CheckBox!
#IBOutlet weak var siCheckBox2: CheckBox!
var imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
// imageView.contentMode = UIViewContentMode.ScaleAspectFill
// imageView.clipsToBounds = true
imageView.image = UIImage(named: "avengers.jpg")
let imagee = UIImage(named: "avengers.jpg")
let size = imagee?.size
imageView.frame = CGRectMake(0, 0, size!.width, size!.height)
imageView.contentMode = .Top
scrollView.addSubview(imageView)
scrollView.contentSize = size!
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
let scaleHeight = scrollViewFrame.size.height / scrollView.contentSize.height
let minScale = min(scaleHeight, scaleWidth)
scrollView.minimumZoomScale = 1
scrollView.maximumZoomScale = 4
scrollView.zoomScale = minScale
centerScrollViewContents()
}
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
}
else {
contentsFrame.origin.x = 0
}
if contentsFrame.size.height < boundsSize.height {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2
}
else {
contentsFrame.origin.y = 0
}
imageView.frame = contentsFrame
// scrollView.frame = contentsFrame
}
func scrollViewDidZoom(scrollView: UIScrollView) {
centerScrollViewContents()
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
Help plz
Just change this.
imageView.contentMode = .ScaleAspectFit
without zoom
with zoom
You should use .contentMode property of the UIImageView for the proper representation of the image.
This is small comparison of the content modes.
You want to display full image without scrolling or you want to display scrollable image?
Try this code for fast start:
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
var imageView = UIImageView()
private var imageViewOriginalSize = CGRect ()
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = UIImage(named: "avengers.jpg")
imageView.contentMode = .ScaleAspectFit
let size = UIScreen.mainScreen().bounds
imageViewOriginalSize = size
imageView.frame = CGRectMake(0, 0, size.width, size.height)
scrollView.addSubview(imageView)
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(ViewController.pinchGestureRecognizerAction))
self.scrollView.addGestureRecognizer(pinch)
}
func pinchGestureRecognizerAction(gestureRecoginzer: UIPinchGestureRecognizer) {
imageView.frame.size.height = imageViewOriginalSize.height*gestureRecoginzer.scale
imageView.frame.size.width = imageViewOriginalSize.width*gestureRecoginzer.scale
imageView.frame.origin.x = imageViewOriginalSize.origin.x*gestureRecoginzer.scale
imageView.frame.origin.y = imageViewOriginalSize.origin.y*gestureRecoginzer.scale
scrollView.contentSize = imageView.frame.size
}}

Rotating UIView in UIScrollview Swift

When I rotate the view in the scrollview it moves out of the scrollview and disappears completely after some rotation/zoom gestures. It works fine as long as the zoom scale is 1.
What do I have to do with my code to avoid this?
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
let rotationView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
view.addSubview(scrollView)
scrollView.delegate = self
scrollView.minimumZoomScale = 0.5
scrollView.maximumZoomScale = 2
let mapImage = UIImage(named: "BMS2_300.jpg")
imageView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size:mapImage!.size)
imageView.image = mapImage
let rotationViewframe = CGRectMake(0, 0, imageView.frame.size.width, imageView.frame.size.height)
rotationView.frame = rotationViewframe
rotationView.addSubview(imageView)
//rotationView.sizeToFit()
scrollView.addSubview(rotationView)
scrollView.contentSize = CGSize(width: rotationView.bounds.width, height: rotationView.bounds.height)
scrollView.bringSubviewToFront(rotationView)
scrollView.contentOffset = CGPoint(x: rotationView.frame.width/2, y: rotationView.frame.height/2)
let mapRotGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(ViewController.rotateMap(_:)))
rotationView.addGestureRecognizer(mapRotGestureRecognizer)
}
func rotateMap(sender: UIRotationGestureRecognizer) {
let radians = sender.rotation
if let senderView = sender.view {
senderView.transform = CGAffineTransformRotate(senderView.transform, radians)
sender.rotation = 0
}
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return self.rotationView
}
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
scrollView.contentSize = rotationView.frame.size
}
The solution is to add an extra uiview under the scrollview, so the new holderView is a subview of the scrollview and the rotatonView a subview of the holderView.

Difference between instantiating VC from storyboard vs. programatically

The comparison is between this:
let viewController = storyboard!.instantiateViewControllerWithIdentifier("ViewController") as! AViewController
versus this:
let viewController = AViewController()
Unfortunately, this question is not able to answer my question.
I've created this view controller:
final class ImageVC: UIViewController {
var imageView: UIImageView!
var scrollView: UIScrollView!
var originLabel: UILabel!
var image: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
guard let image = image else { fatalError() }
imageView = UIImageView(image: image)
scrollView = UIScrollView(frame: view.bounds)
scrollView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
scrollView.backgroundColor = UIColor.blackColor()
scrollView.contentSize = imageView.bounds.size
scrollView.addSubview(imageView)
view.addSubview(scrollView)
originLabel = UILabel(frame: CGRect(x: 20, y: 30, width: 0, height: 0))
originLabel.backgroundColor = UIColor.blackColor()
originLabel.textColor = UIColor.whiteColor()
view.addSubview(originLabel)
scrollView.delegate = self
setZoomParametersForSize(scrollView.bounds.size)
}
override func viewWillLayoutSubviews() {
print("layout")
setZoomParametersForSize(scrollView.bounds.size)
}
func setZoomParametersForSize(scrollViewSize: CGSize) {
let imageSize = imageView.bounds.size
let widthScale = scrollViewSize.width / imageSize.width
let heightScale = scrollViewSize.height / imageSize.height
let minScale = min(widthScale, heightScale)
scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = 3.0
scrollView.zoomScale = minScale
}
}
extension ImageVC: UIScrollViewDelegate {
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
func scrollViewDidScroll(scrollView: UIScrollView) {
originLabel.text = "\(scrollView.contentOffset)"
originLabel.sizeToFit()
}
}
This is meant to take an image, and when presented, will allow the user to zoom/pan through the image.
When I instantiate this VC using the first method (by instantiating from storyboard with identifier), it behaves fine.
However, when instantiating it the second way; let viewController = ImageVC(), viewWillLayoutSubviews will be triggered whenever scrollView detects movement, disallowing the ability to zoom in and out.
Advice appreciated.

Resources