Trying to zoom a multilayer set of UIViews - ios

Please point me in the right direction - I can find neither a solution nor a tutorial that solves my problem..
I have the following hierarchy of views in a ViewController:
UIView
ScrollView
Container
Drawing
Image
I can zoom in and out using a tap gesture, however, I don't want the pens in the drawing view to resize. I need to be able to draw lines on the image at a greater detail.
If I zoom the views separately, then the pens are out of sync with the image.
The views are setup in IB.
The code in ViewDidLoad to initialize things is as follows:
scrollView.backgroundColor = UIColor.black
scrollView.contentSize = imageView.frame.size
scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let tap = UITapGestureRecognizer(target: self, action: #selector(doubleTapped))
tap.numberOfTapsRequired = 2
imageView.addGestureRecognizer(tap)
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 4.0
scrollView.delegate = self
And the zoom method is:
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
I've tried starting the following alternatives:
scrollView.contentSize = drawingView.frame.size and
..
return drawingView
I've also set imageView as a subView of drawingView.
And finally, I've set both as subViews of the Container View (a UIView, not a UIContainerView.
I am conforming to the UIScrollViewDelegate.
Any help would be appreciated.

Related

Pan Large Image in Scrollview through StoryBoard

I am building an application where I want to display a floor plan where the image is 2952x3600, larger than the size of my view controller. I want the user to be able to pan the image and zoom in and out, similar to how a map behaves in Mapview.
I was able to obtain the desired result programmatically with the below code. However, I want to be able to add buttons to the image and it will be easier to do in the storyboard. How can I accomplish the same result in the Storyboard?
class ViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView(image: UIImage(named: "TorontoPATHNetworkMap_v.2018.08.pdf"))
scrollView = UIScrollView(frame: view.bounds)
scrollView.contentSize = imageView.bounds.size
scrollView.addSubview(imageView)
scrollView.delegate = self
scrollView.minimumZoomScale = 0.3
scrollView.maximumZoomScale = 5
view.addSubview(scrollView)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
If you want to add other UI elements (buttons, labels, subviews, etc) and have them scale and move when the scroll view content is zoomed, you have a couple options:
1) Add your buttons in code as subviews of the image view. They will move / size along with the image view.
2) If you'd find it easier to layout your buttons visually, you can do your layout in Storyboard / Interface Builder. In IB, though, you cannot add subviews to a UIImageView, so you will need to embed the image view and buttons in a "container" UIView. Then in your controller class return that container view from viewForZooming
Edit
Here's an example of setting up the views in Storyboard / IB: https://github.com/DonMag/PDFImagePanZoom

Is there a way to pinch and zoom a programmatically added image?

I am trying to figure out how to add pinch/zoom capabilities to an imageView,
but specifically to one that I added to the scrollView programmatically. I was able to find some great examples on SO to pinch and zoom an image using a scrollview with viewForZooming, but in that instance I had to return the image view being pinched and zoomed, which doesn't work if I am returning it programatically.
My ultimate goal is to have an array of images where the user can scroll left and right to see all the images AND be able to zoom in on them, basically just as if they were flipping through their photoStream. I found an ok tutorial for adding the images dynamically for scrolling here https://www.codementor.io/taiwoadedotun/ios-swift-implementing-photos-app-image-scrolling-with-scroll-views-bkbcmrgz5#comments-bkbcmrgz5 but I am not clear how to add the viewForZooming since the image views are being .addSubview dynamically in a loop.
I created a little example with a collection view of 0-n images associated to a post. Once the collectionViewCell with the image is tapped a hidden scrollView appears with a new dynamic UIImageView added as a subView. All works great but I don't know how to add the pinch/zoom now.
#objc func imageTapped(_ sender: UITapGestureRecognizer) {
print("BlipeFile Image Tapped")
let imageView = sender.view as! UIImageView
let newImageView = UIImageView(image: imageView.image)
//newImageView.frame = UIScreen.main.bounds
newImageView.contentMode = .scaleAspectFit
newImageView.clipsToBounds = true
newImageView.layer.borderColor = UIColor.gray.cgColor
newImageView.layer.borderWidth = 3.0
newImageView.frame = self.view.frame
newImageView.backgroundColor = .black
newImageView.isUserInteractionEnabled = true
newImageView.image = imageView.image
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage))
newImageView.addGestureRecognizer(tap)
scroller.isHidden = false
scroller.addSubview(newImageView)
}
#objc func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
scroller.isHidden = true
self.navigationController?.isNavigationBarHidden = false
self.tabBarController?.tabBar.isHidden = false
sender.view?.removeFromSuperview()
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return image //Can't return dynamically created newImageView?
}
My ultimate goal is to have an array of images where the user can scroll left and right to see all the images AND be able to zoom in on them
Apple has explained many times, in WWDC videos and in sample code that you can download, how this is done. Basically it’s just a scroll view in a scroll view. The outer scroll view permits scrolling horizontally. The inner scroll view contains an image view and permits zooming.
So instead of adding an image view, add a scroll view containing an image view.

Zoom and Scroll support UICollectionView

I have a requirement to support zoom in UICollectionView.
Requirements:
After zoom in, it has to support to view the UICollectionViewCell’s hidden area ( area out of viewport) by horizontal and vertical scroll.
After Zoom out/in, it has to support the selection of UICollectionViewCell and able to scroll the UICollectionView ( Basically the default UICollectionView behavior on going back to no zoom state. ).
The list of approaches tried:
Added GestureRecognizer
a. Added UIPinchGestureRecognizer to transform the UICollectionView by scale.
b. After Zoom in, it was not possible to move the UICollectionViewcell to view the hidden area.
c. Added UIPanGestureRecognizer to move the center of UICollectionView
d. It was working fine to move the UICollectionView.
e. Now we can’t able to select the UICollectionViewCell and can’t able to scroll UICollectionView.
Added UICollectionView inside UIScrollView
a. Added UIScrollView with delegates.
b. Added UICollectionView as sub view of UIScrollView
c. Zoom out is not happening because UICollectionView (inherited by UIScrollView) consumes the zoom gesture
Added UIColectionView and UIScrollView both as siblings
a. Added UIScrollView and UICollectionView to parent.
b. Bring UIScrollView to front.
c. Zoom is working but not able to pan to see the hidden area.
Please suggest if there any way to fix above approaches or a better strategy to achieve zoom in a collectionView.
I have solved this using a UIScrollView and a UICollectionViewLayout subclass.
1) place a UIScrollView on top of the UICollectionView with the same frame.
self.view.addSubview(scrollView)
scrollView.addSubview(dummyViewForZooming)
scrollView.frame = collectionView.frame
scrollView.bouncesZoom = false
scrollView.minimumZoomScale = 0.5
scrollView.maximumZoomScale = 3.0
2) Set the contentSize of the UIScrollView and zoomingView to be the same as the UICollectionView
override func viewDidLayoutSubviews() {
super.viewWillLayoutSubviews()
scrollView.contentSize = layout.collectionViewContentSize
dummyViewForZooming.frame = CGRect(origin: .zero, size: layout.collectionViewContentSize)
scrollView.frame = collectionView.frame
}
3) Remove all gesture recognizers from the UICollectionView and add a delegate for the UIScrollView. Add a tap gesture recognizer to the UIScrollview
collectionView.gestureRecognizers?.forEach {
collectionView.removeGestureRecognizer($0)
}
let tap = UITapGestureRecognizer.init(target: self, action: #selector(scrollViewWasTapped(sender:)))
tap.numberOfTapsRequired = 1
scrollView.addGestureRecognizer(tap)
scrollView.delegate = self
4) When the ScrollView scrolls or zooms, set the contentOffset of the UICollectionView to be the same as the ScrollView contentOffset, set the layoutScale of your UICollectionViewLayout as the zoomscale and invalidate the layout.
func scrollViewDidZoom(_ scrollView: UIScrollView) {
if let layout = self.layout, layout.getScale() != scrollView.zoomScale {
layout.layoutScale = scrollView.zoomScale
self.layout.invalidateLayout()
collectionView.contentOffset = scrollView.contentOffset
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return dummyViewForZooming
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
collectionView.contentOffset = scrollView.contentOffset
}
5) override the prepare method in the UICollectionViewLayout, scan through all your layoutAttributes and set a transform:
attribute.transformedFrame = attribute.originalFrame.scale(layoutScale)
let ts = CGAffineTransform(scaleX: layoutScale, y: layoutScale)
attribute.transform = ts
let xDifference = attribute.frame.origin.x - attribute.transformedFrame.origin.x
let yDifference = attribute.frame.origin.y - attribute.transformedFrame.origin.y
let t1 = CGAffineTransform(translationX: -xDifference, y: -yDifference)
let t = ts.concatenating(t1)
attribute.transform = t
6) ensure you scale the collectionView content size:
override var collectionViewContentSize: CGSize {
return CGSize(width: width * layoutScale, height: height * layoutScale)
}
7) Intercept taps from the tap gesture recognizer and convert the location in view to a point in the collection view, you can then get the indexPath of that cell using indexPathForItem(point:) and select the cell or pass on events to the underlying views of the cell etc..
hope this helps

Creating swipe gesture in swift to scroll through image stack

I'm a swift newbie. Any help is appreciated.
I have a stack of 10 images. I want the user to be able to swipe up and down to update the the image view to new images.
For example: the Image View starts of by displaying 1.jpg. Then the user gestures down on the Image View and it updates the image to 2.jpg then 3.jpg... etc. depending on how far the gesture is held (kind of like an animation). I am able to update the image by one increment using the following code:
#IBAction func down(sender: UISwipeGestureRecognizer) {
image.image = UIImage(named: "2.jpg")
}
How can I make this a continuous gesture?
The end result should look something like this: https://figure1.com/sections/blog/wp-content/uploads/2014/08/resized.gif
So let's start off first things first.
Based off the idea that this is distance dragged
1) We need a UIScrollView()
2) Set the Scrollview Frame to the frame of the screen
3) Allow use of scrollview delegate, so add UIScrollViewDelegate to the list of subclasses (At the top where it says ClassName: list, of, subclasses, here, separated, by, commas)
4) In your class (If you are in your View Controller class), add the delegate to the scrollview
5) Let's set the contentSize of the scrollView
6) Let's add the images to your view, using a loop
7) Before step 6, make sure that your images are named something with numbers, and the same name, such as Image1 Image2 Image3 so that we can loop through them.
8) Declare a UIImage array
9) I was gonna keep typing steps but here you go :P
class ViewController:UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
let images:[UIImage] = []
let imageView:UIImageView!
override func viewDidLoad() {
//Setting up stuff
for index in 0 ..< 10 {
//It's either %t, %i or %d (I don't recall which is which lol)
images.append(UIImage(named: String(format: "Image %i", index)))
}
imageView = UIImageView(frame: view.bounds)
imageView.contentMode = .ScaleAspectFit
imageView.image = images[0]
imageView.backgroundColor = UIColor.clearColor()
self.view.addSubview(imageView)
scrollView.frame = view.bounds
scrollView.delegate = self
scrollView.backGroundColor = UIColor.clearColor()
//Let's say every time the user drags to 1/10 of the screen, the picture will change
scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height+(self.view.frame*(1/10)*CGFloat(images.count)))
self.view.addSubview(scrollView)
}
//Now let's add some UIScrollView delegates to be called whenever the user scrolls!
func scrollViewDidScroll(scrollView: UIScrollView) {
if(scrollView.contentOffSet.y > self.view.frame.height) {
//(1/10) because we are changing the scrollView every 1/10 of the screen
let pictureCount = scrollView.contentOffset.y/scrollView.frame.height*(1/10)
imageView.image = images[pictureCount]
}
}
}
Don't quote me off this, mainly because I pulled it out of the air, so if it has errors/bugs, I apologize, but feel free to comment and I will try to adjust what is needed.

Best way to implement view like map

I'm making view that represent something like map, with pan and pinch gestures.
At current moment it's implemented via overriden drawRect that called in handlePan with setNeedsDisplay.
So, the resulting performance a little bit awkward. Is there any more appropriate approaches?
So, more appropriate solution here is UIScrollView.
To get it work:
Set up scrollView delegate, like
scrollView.delegate = self
Implement
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?
{
return fieldView
}
Optionally, you can set
scrollView.minimumZoomScale = scrollView.frame.width /
fieldView.frame.width
scrollView.maximumZoomScale = 2
Where fieldView is target custom view:
fieldView = FieldView(frame: frameRect)
scrollView.addSubview(fieldView)
scrollView.contentSize = frameRect.size

Resources