How to enable zoom in UIScrollView - iphone

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

Related

UIScrollview constraints for different devices

Currently, I have the following codes to make UIScrollView work for iPhone that I have set it up in the main storyboard.
var images: [String] = ["image1", "image2"]
var frame = CGRect(x:0,y:0,width:0,height:0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
pageControl.numberOfPages = images.count
for index in 0..<images.count {
frame.origin.x = scrollView.frame.size.width * CGFloat(index)
frame.size = scrollView.frame.size
let imgView = UIImageView(frame: frame)
imgView.image = UIImage(named: images[index])
self.scrollView.addSubview(imgView)
}
scrollView.contentSize = CGSize(width: (scrollView.frame.size.width * CGFloat(images.count)), height: scrollView.frame.size.height)
scrollView.delegate = self
}
I am trying to make a horizontal scrollview in xcode but when I run the project on iPad, the scrollview changes to the screen size but not the images. I looked up solutions online by adding views to the scroll view but it does not work. I am trying to make the images resize accordingly to the scrollview size.
Quick fact: Images are stored in Assets.xcassets.
Any help would be very much appreciated. Thanks!

Resizing NSWindow don't autoresize its contentView correctly

I have an NSWindow with it's contentView. In the awakeFromNib() of the NSWindow I have the following code:
override func awakeFromNib()
{
super.awakeFromNib()
/// Customize Window through XIBs
self.title = "Main Window"
let screenFrame = NSScreen.main?.frame
let windowPercentage: CGFloat = 0.9;
let offset: CGFloat = (1.0 - windowPercentage) / 2.0;
let windowFrame: NSRect = NSRect(x: (screenFrame?.width)! * offset, y: (screenFrame?.height)! * offset, width: (screenFrame?.width)! * windowPercentage, height: (screenFrame?.height)! * windowPercentage )
self.setFrame(windowFrame,display: true,animate: true)
self.backgroundColor = NSColor.lightGray
self.isRestorable = true
// Customize contentView
let viewPercentage: CGFloat = 0.6
self.contentView?.setFrameSize(NSSize(width: self.frame.size.width * viewPercentage, height: self.frame.size.height))
self.contentView?.setFrameOrigin( NSMakePoint( ( (self.frame.width) - (self.contentView?.frame.width)! )/2, ( (self.frame.height) - (self.contentView?.frame.height)!)/2) )
self.contentView?.autoresizingMask = [.width, .height, .minXMargin,.maxXMargin,.maxYMargin,.minYMargin]
}
I am trying to set up the contentView in the center and with a percentage of its NSWindow frame but it's failing when i resize the window. As soon as I start to resize the Window the contentView it's not resizing correctly, as you can see from the following image(the second one):
Image one
Image two
Should I override the resize(withOldSuperviewSize:) method to achieve this? (also the autoresize from interface builder don't resolve the issue)
You shouldn't attempt to change the size of the content view like that. I don't believe it's supported. The window controls the content view's size.
If you want a view of your own to occupy only a portion of the window's content area, you should add your view as a subview of the content view.

Rotate UIImageView inside UIScrollView in 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)

Swift - Visual constraints not working

I have a scrollView which I instantiate programaticaly. What I want to do is to add constraints so it will look good in both horizontal and vertical orientation. The problem that constraints not working
How it looks vertically - good
How it looks horizontally - not good
Code is following
class FAPhoto: UIViewController, UIScrollViewDelegate {
var imageURLsArray = [String]()
var imageViews:[UIImageView] = []
var arrayOfPhotos = [Photo]()
var scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView(frame: self.view.frame)
view.addSubview(scrollView)
scrollView.contentSize = CGSizeMake(view.frame.size.width * CGFloat(arrayOfPhotos.count), scrollView.frame.size.height)
scrollView.pagingEnabled = true
for (var i = 0; i < arrayOfPhotos.count; i++) {
var imageView = UIImageView()
imageView.frame = CGRectMake(CGFloat(i) * view.frame.size.width, scrollView.frame.origin.y, scrollView.frame.size.width, scrollView.frame.size.height)
imageView.contentMode = .ScaleAspectFit
let image = imageView.hnk_setImageFromURL(NSURL(string: arrayOfPhotos[i].url!)!)
scrollView.addSubview(imageView)
}
}
override func viewWillAppear(animated: Bool) {
}
override func viewWillLayoutSubviews() {
let bindings = Dictionary(dictionaryLiteral: ("scrollView", self.scrollView))
let horizontalConstraints =
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-0-[scrollView]-0-|",
options: [],
metrics: nil,
views: bindings)
self.view.addConstraints(horizontalConstraints)
let verticalConstraints =
NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-0-[scrollView]-0-|",
options: [],
metrics: nil,
views: bindings)
self.view.addConstraints(verticalConstraints)
}
}
The problem is that you set contentSize of scrollView and imageView's frames in viewDidLoad. This is OK for portrait mode, but when view rotates to landscape, it's frame also changes, so you have to update these values accordingly. I think you have 3 opportunities here.
Track rotation events and update contentSize of scrollView and imageView's frames, when view rotates.
Use Autolayout for scrollView. You will not have to calculate contentSize or set imageView's frames, just set constraints once in viewDidLoad. Here you can find some examples https://stackoverflow.com/a/20232911/4757335.
Use UICollectionView instead of UIScrollView. It handles rotation much easier.

UIScrollView with "Circular" scrolling

I am trying to make "Circular" scrolling in my UIScrollView, but unsuccessful.
What I want to do:
if uiscrollview reaches end, it should move to start
if uiscrollview at start and moving back, it should move to end
Appending scrollview isn't good way in my situation (other methods should get "page id")
Have you any ideas?
I've implemented this method, but it requires paging enabled. Lets assume you have five elements A,B,C,D and E. When you set up your view, you add the last element to the beginning and the first element to the end, and adjust the content offset to view the first element, like this E,[A],B,C,D,E,A. In the UIScrollViewDelegate, check if the user reach any of the ends, and move the offset without animation to the other end.
Imagine the [ ] indicates the view being shown:
E,A,B,C,[D],E,A
User swipes right
E,A,B,C,D,[E],A
User swipes right
E,A,B,C,D,E,[A]
Then, automatically set the content offset to the second element
E,[A],B,C,D,E,A
This way the user can swipe both ways creating the illusion of an infinite scroll.
E,A,[B],C,D,E,A
Update
I've uploaded a complete implementation of this algorithm. It's a very complicated class, because it also has on-click selection, infinite circular scroll and cell reuse. You can use the code as is, modify it or extract the code that you need. The most interesting code is in the class TCHorizontalSelectorView.
Link to the file
Enjoy it!
Update 2
UICollectionView is now the recommended way to achieve this and it can be used to obtain the very same behavior. This tutorial describes in details how to achieve it.
Apple has a Street Scroller demo that appears to have exactly what you want.
There's also a video from WWDC 2011 that demos their demo. ;) They cover infinite scrolling first in the video.
Try to use following code..
Sample code..
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
if (scrollView.contentOffset.x == 0) {
// user is scrolling to the left from image 1 to image n(last image).
// reposition offset to show image 10 that is on the right in the scroll view
[scrollView scrollRectToVisible:CGRectMake(4000,0,320,480) animated:NO];// you can define your `contensize` for scrollview
}
else if (scrollView.contentOffset.x == 4320) {
// user is scrolling to the right from image n(last image) to image 1.
// reposition offset to show image 1 that is on the left in the scroll view
[scrollView scrollRectToVisible:CGRectMake(320,0,320,480) animated:NO];
}
}
Hope, this will help you...
With reference from #redent84, find the code logic.
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
scrollView.contentSize = CGSize(width: scrollView.frame.width * CGFloat(imagesArray.count), height: scrollView.frame.height)
scrollView.isPagingEnabled = true
// imagesArray.insert(imagesArray.last as? UIImage, at: 0)
// imagesArray.insert(imagesArray.first as? UIImage, at: imagesArray.count - 1)
var testArr = ["A", "B", "C", "D"]
let firstItem = testArr.first
let lastItem = testArr.last
testArr.insert(lastItem as! String, at: 0)
testArr.append(firstItem as! String)
print(testArr)
for i in 0..<imagesArray.count{
let imageview = UIImageView()
imageview.image = imagesArray[i]
imageview.contentMode = .scaleAspectFit
imageview.clipsToBounds = true
let xPosition = self.scrollView.frame.width * CGFloat(i)
imageview.frame = CGRect(x: xPosition, y: 0, width: self.scrollView.frame.width, height: self.scrollView.frame.height)
print(imageview)
scrollView.addSubview(imageview)
print("Imageview frames of i \(i) is \(imageview.frame)")
}
newStartScrolling()
}
func newStartScrolling()
{
ViewController.i += 1
let x = CGFloat(ViewController.i) * scrollView.frame.size.width
scrollView.setContentOffset(CGPoint(x: x, y: 0), animated: true)
print("X is \(x) and i is \(ViewController.i)")
if ViewController.i == imagesArray.count - 1 {
ViewController.i = 0
let x = CGFloat(ViewController.i) * scrollView.frame.size.width
//scrollView.setContentOffset(CGPoint(x: x, y: 0), animated: false)
print("X (rotate) is \(x) and i is \(ViewController.i)")
scrollView.contentOffset.x = x
self.newStartScrolling()
}
}
func backScrolling() {
ViewController.i -= 1
let x = CGFloat(ViewController.i) * scrollView.frame.size.width
scrollView.setContentOffset(CGPoint(x: x, y: 0), animated: true)
print("X is \(x) and i is \(ViewController.i)")
if ViewController.i <= 0 {
ViewController.i = imagesArray.count - 1
let x = CGFloat(ViewController.i) * scrollView.frame.size.width
scrollView.setContentOffset(CGPoint(x: x, y: 0), animated: false)
print("X (rotate) is \(x) and i is \(ViewController.i)")
self.backScrolling()
//scrollView.contentOffset.x = x
return
}
}