Scrollview only zooms to upper left corner - ios

I've created a scrollview programmatically and added an image as it's subview but when I try to zoom into the image it only zooms into the upper corner and it doesn't let me scroll around to see different parts of the image.
Here's the code I used to create the scrollview:
let scrollView = UIScrollView()
var image: NSData!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 3.0
scrollView.zoomScale = 1.0
scrollView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
scrollView.delegate = self
scrollView.isPagingEnabled = false
scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
view.addSubview(scrollView)
scrollView.addSubview(imageView)
imageView.image = UIImage(data: image as Data)
imageView.contentMode = .scaleAspectFit
imageView.isUserInteractionEnabled = true
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(share))
longPress.minimumPressDuration = 0.5
longPress.numberOfTapsRequired = 0
imageView.addGestureRecognizer(longPress)
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(recognizer:)))
doubleTap.numberOfTapsRequired = 2
scrollView.addGestureRecognizer(doubleTap)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
func share(sender: UILongPressGestureRecognizer) {
if sender.state == .ended {
if UIImage(data: image as Data) != nil {
let Item = UIImage(data: self.image! as Data)
let activity = UIActivityViewController(activityItems: [Item!], applicationActivities: nil)
activity.excludedActivityTypes = [UIActivityType.print, UIActivityType.addToReadingList, UIActivityType.openInIBooks]
self.present(activity, animated: true, completion: nil)
}
}
}
func handleDoubleTap(recognizer: UITapGestureRecognizer) {
if scrollView.zoomScale > scrollView.minimumZoomScale {
scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
} else {
scrollView.setZoomScale(scrollView.maximumZoomScale, animated: true)
}
}
Thank you very much for any kind of help

The issue is in your code when you set the zoomscale of your scrollView. You don't specify where it should zoom to. So naturally, it zooms and stays in the top left corner.
You need to get the location of the touch in your handleDoubleTap(recognizer: UITapGestureRecognizer), calculate the rect for zoom, and call scrollRectToVisible on your scrollview.

Related

Swift 3: Can you save the position of a cropped image and access it later when the full image is displayed?

Language: Swift 3
Task: Allow users to crop their profile image on upload
Question: Initially I was going to try and figure out to save the cropped image and the full image to my server because I need both, then came up with the question... Is there a way to save the cropped position of the full image to my mysql database(php) then access it when I need to display the cropped image?
I'm using
self.imagePicker.allowsEditing = true
To allow the users to crop photo
let chooseAction = UIAlertAction(title: "Choose Image", style: .default, handler: { (alert: UIAlertAction!) -> Void in
if UIImagePickerController.isSourceTypeAvailable(.savedPhotosAlbum) {
self.imagePicker.delegate = self
self.imagePicker.sourceType = .savedPhotosAlbum
self.imagePicker.allowsEditing = true
self.present(self.imagePicker, animated: true, completion: nil) }
})
let takeAction = UIAlertAction(title: "Take Image", style: .default, handler: { (alert: UIAlertAction!) -> Void in
if UIImagePickerController.isSourceTypeAvailable(.camera) {
self.imagePicker.delegate = self
self.imagePicker.sourceType = .camera
self.imagePicker.allowsEditing = true
self.present(self.imagePicker, animated: true, completion: nil) }
})
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: { (alert: UIAlertAction!) -> Void in })
optionMenu.addAction(chooseAction)
optionMenu.addAction(takeAction)
optionMenu.addAction(cancelAction)
self.present(optionMenu, animated: true, completion: nil)
}
EDIT
I am saving the full image to server
I need the simplest way to get the cropped square position if possible then store the position in a variable (I will store this on my server)
On certain views when the image is displayed I only want to show the cropped part of the full image. I will retrieve the saved cropped position set on upload then only show that part of the image.
I decided to try this out since there are a few issues that can occur doing this. I made this example:
class ViewController: UIViewController {
private let scrollViewPanel: HitForwardingView = HitForwardingView()
private let scrollView: UIScrollView = UIScrollView()
private let imageView: UIImageView = UIImageView()
private var image: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
scrollViewPanel.frame = view.bounds
scrollViewPanel.listenerView = scrollView
view.addSubview(scrollViewPanel)
scrollView.frame = CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200.0)
scrollView.center = CGPoint(x: scrollViewPanel.bounds.midX, y: scrollViewPanel.bounds.midY)
scrollViewPanel.addSubview(scrollView)
scrollView.clipsToBounds = false
scrollView.delegate = self
scrollView.minimumZoomScale = 0.2
scrollView.maximumZoomScale = 5.0
if let image = UIImage(named: "testImage") {
setupWithImage(image)
scrollToCenter()
}
// Just some masking to make things look nicer
scrollViewPanel.addSubview({
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: scrollViewPanel.bounds.width, height: scrollView.frame.minY))
view.isUserInteractionEnabled = false
view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
return view
}())
scrollViewPanel.addSubview({
let view = UIView(frame: CGRect(x: 0.0, y: scrollView.frame.minY, width: scrollView.frame.minX, height: scrollView.frame.height))
view.isUserInteractionEnabled = false
view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
return view
}())
scrollViewPanel.addSubview({
let view = UIView(frame: CGRect(x: scrollView.frame.maxX, y: scrollView.frame.minY, width: scrollViewPanel.frame.width - scrollView.frame.maxX, height: scrollView.frame.height))
view.isUserInteractionEnabled = false
view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
return view
}())
scrollViewPanel.addSubview({
let view = UIView(frame: CGRect(x: 0.0, y: scrollView.frame.maxY, width: scrollViewPanel.bounds.width, height: scrollViewPanel.frame.height - scrollView.frame.maxY))
view.isUserInteractionEnabled = false
view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
return view
}())
// Add a trigger to show crop
view.addGestureRecognizer({
let recognizer = UITapGestureRecognizer(target: self, action: #selector(debugSnapshot))
recognizer.numberOfTapsRequired = 2
return recognizer
}())
}
#objc private func debugSnapshot() {
// Going to use relative coordinates depending on the original image. The result should be a frame normalized depending on original:
// x=0 is left-most
// x=1 is right-most
// y=0 is top-most
// y=1 is bottom-most
let convertedScrollViewFrame = scrollView.convert(scrollView.bounds, to: scrollViewPanel)
let convertedImageViewFrame = imageView.convert(imageView.bounds, to: scrollViewPanel)
var frame = convertedScrollViewFrame
frame.origin.x -= convertedImageViewFrame.origin.x
frame.origin.y -= convertedImageViewFrame.origin.y
frame.origin.x /= convertedImageViewFrame.width
frame.origin.y /= convertedImageViewFrame.height
frame.size.width /= convertedImageViewFrame.width
frame.size.height /= convertedImageViewFrame.height
print(frame)
// Do a reconstruction
let previewPanelView = UIView(frame: self.view.bounds)
previewPanelView.backgroundColor = UIColor.black.withAlphaComponent(0.8)
previewPanelView.addSubview({
let panel = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 300.0, height: 300.0))
panel.center = CGPoint(x: previewPanelView.bounds.midX, y: previewPanelView.bounds.midY)
panel.clipsToBounds = true
let imageView = UIImageView(image: self.imageView.image)
// Frame now relative to where we display it (panel)
imageView.frame = CGRect(x: -frame.origin.x * panel.bounds.width / frame.size.width,
y: -frame.origin.y * panel.bounds.height / frame.size.height,
width: panel.bounds.width / frame.size.width,
height: panel.bounds.height / frame.size.height)
panel.addSubview(imageView)
return panel
}())
view.addSubview(previewPanelView)
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
previewPanelView.removeFromSuperview()
}
}
private func setupWithImage(_ image: UIImage) {
imageView.image = image
imageView.frame = CGRect(x: 0.0, y: 0.0, width: image.size.width, height: image.size.height)
scrollView.addSubview(imageView)
scrollView.contentSize = imageView.frame.size
}
private func scrollToCenter() {
scrollView.zoomScale = 1.0
scrollView.contentOffset = CGPoint(x: scrollView.contentSize.width*0.5 - scrollView.bounds.size.width*0.5,
y: scrollView.contentSize.height*0.5 - scrollView.bounds.size.height*0.5)
}
}
extension ViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
private extension ViewController {
class HitForwardingView: UIView {
weak var listenerView: UIView?
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let original = super.hitTest(point, with: event)
if (original == self || original == nil) && self.bounds.contains(point) {
return listenerView
} else {
return original
}
}
}
}
To make it work you can simply create a new project and copy this code into your ViewController. You need a test image as well named "testImage". You can scroll your image around, zoom it in or out. If you double-tap a new overlay will appear which should show a cropped image.
Explanations:
The whole view controller is done in code just to make it easier to explain. In reality all the views would be done in storyboard with constraints.
Because we use a "small" scroll view we need to forward touch events to the scroll view even when you drag outside of it. To do so HitForwardingView is introduced. Basically it is collecting events and forwarding them to the scroll view.
Both "position" construction and deconstruction is demonstrated in debugSnapshot. To your backend you would send data from received frame in this method. You would then later use this same frame from backend to position image correctly to mimic cropped image.
I hope the rest is pretty much self-explanatory from the code. If there are any questions about it please go ahead.

zoom image with scrollview swift ios

I have a problem with zooming an image with scroll view.
If I put the scroll view on the left with width 125 and height 375, it does not zoom the image.
It only works if I put the scroll view on the center of the view.
Screenshot:
Why is the scroll view not zooming when it's on the left side?
Here is my code
var imgview:UIImageView!
var imagepicked:UIImage!
var minZoomScale:CGFloat!
let picker = UIImagePickerController()
#IBOutlet weak var scrollViewSquare: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
picker.delegate = self
scrollViewSquare.delegate = self
picker.allowsEditing = false
picker.sourceType = .PhotoLibrary
}
func imagePickerController(
picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject])
{
imagepicked = (info[UIImagePickerControllerOriginalImage] as? UIImage)!
ImageViewInit()
dismissViewControllerAnimated(false, completion: nil)
}
#IBAction func Pick(sender: AnyObject) {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.SavedPhotosAlbum){
self.presentViewController(picker, animated: true, completion: nil)
}
}
func ImageViewInit(){
imgview = UIImageView()
imgview.frame = CGRectMake(0, 0, imagepicked.size.width, imagepicked.size.height)
imgview.image = imagepicked
imgview.contentMode = .ScaleAspectFit
imgview.backgroundColor = UIColor.greenColor()
scrollViewSquare.maximumZoomScale = 4;
scrollViewSquare.minimumZoomScale = 0.02;
scrollViewSquare.bounces = true;
scrollViewSquare.bouncesZoom = true;
scrollViewSquare.contentMode = .ScaleAspectFit
scrollViewSquare.contentSize = imagepicked.size
scrollViewSquare.autoresizingMask = UIViewAutoresizing.FlexibleWidth
scrollViewSquare.addSubview(imgview)
setZoomScale()
}
func setZoomScale(){
let imageViewSize = imgview.bounds.size
let scrollViewSize = scrollViewSquare.bounds.size
let widthScale = scrollViewSize.width / imageViewSize.width
let heightScale = scrollViewSize.height / imageViewSize.height
minZoomScale = max(widthScale, heightScale)
scrollViewSquare.minimumZoomScale = minZoomScale
scrollViewSquare.zoomScale = minZoomScale
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imgview
}
Hello #Bensalem Ilyes,
I've tried your code and zooming does not work on simulator because the centre of pinch gesture is in the midle so pinch on scrollview is translated as simple pan gesture. I tried your code on real device and zooming works fine.
For repositioning pinch gesture on simulator:
alt+shift key

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.

Paging UIScrollView seems to place content off centre

I have a UIScrollView that I want to have paging functionality (think an initial splash screen). I want that content (a UILabel and a UIImageView) to be placed centrally in each paging view on the scrollView. My problem is is that it is always slightly off centre ().
Here is the complete code:
var splashScreenObjects = [SplashScreenObject]()
var imageViewArray = [UIImageView]()
var subtitleViewArray = [UILabel]()
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var pageControl: UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
createSplashScreenObjects()
configurePageControl()
configureScrollView()
}
func createSplashScreenObjects() {
let firstScreen: SplashScreenObject = SplashScreenObject(subtitle: "Medication reminders on your phone. Never miss your next dose", image: UIImage(named: "splashScreen1")!)
let secondScreen: SplashScreenObject = SplashScreenObject(subtitle: "Track how good you have been with your medication", image: UIImage(named: "splashScreen2")!)
let thirdScreen: SplashScreenObject = SplashScreenObject(subtitle: "The better you are with your medication, the more points you'll earn!", image: UIImage(named: "splashScreen3")!)
splashScreenObjects.append(firstScreen)
splashScreenObjects.append(secondScreen)
splashScreenObjects.append(thirdScreen)
}
func configureScrollView() {
self.scrollView.layoutIfNeeded()
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.pagingEnabled = true
self.scrollView.delegate = self
let width = view.frame.size.width
for index in 0..<splashScreenObjects.count {
let subtitle = UILabel(frame: CGRectMake((width * CGFloat(index)) + 25, self.scrollView.frame.size.height-75, width-50, 75))
subtitle.text = splashScreenObjects[index].subtitle
subtitle.textAlignment = NSTextAlignment.Center
subtitle.textColor = UIColor.whiteColor()
subtitle.font = UIFont(name:"Ubuntu", size: 16)
subtitle.numberOfLines = 2
subtitle.backgroundColor = UIColor.clearColor()
self.scrollView.addSubview(subtitle)
self.subtitleViewArray.append(subtitle)
subtitle.alpha = 0
let mainImage = UIImageView(frame: CGRectMake((width * CGFloat(index)), 50, width, self.scrollView.frame.size.height-150))
mainImage.image = splashScreenObjects[index].image
mainImage.contentMode = UIViewContentMode.ScaleAspectFit
self.scrollView.addSubview(mainImage)
self.imageViewArray.append(mainImage)
mainImage.alpha = 0
}
self.scrollView.contentSize = CGSizeMake(width * CGFloat(splashScreenObjects.count), self.scrollView.frame.size.height-50)
animateViews(Int(0))
}
func configurePageControl() {
self.pageControl.numberOfPages = splashScreenObjects.count
self.pageControl.currentPage = 0
self.view.addSubview(pageControl)
pageControl.addTarget(self, action: #selector(SplashViewController.changePage(_:)), forControlEvents: UIControlEvents.ValueChanged)
}
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * self.view.frame.size.width
scrollView.setContentOffset(CGPointMake(x, 0), animated: true)
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / self.view.frame.size.width)
pageControl.currentPage = Int(pageNumber)
animateViews(Int(pageNumber))
}
func animateViews(pageNumber: Int) {
UIView.animateWithDuration(0.5, animations: {
self.imageViewArray[pageNumber].alpha = 1.0
self.subtitleViewArray[pageNumber].alpha = 1.0
})
}
Here are my auto layout constraints for the UIScrollView:
Your leading and trailing spaces are both -20, which means that the scroll view is 40 points wider than its superview. Change these to 0.
You should replace
self.scrollView.layoutIfNeeded()
to
self.view.layoutIfNeeded()
because layoutIfNeeded layout caller subviews, not itself. So, scrollView, when you add subtitle and mainImage on it, has wrong frame.

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.

Resources