Creating swipe gesture in swift to scroll through image stack - ios

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.

Related

Horizontal UIScrollView is not centering the current page

I would like to have a horizontal scroll layout which displays images. It works fine if setup 0, 0, 0 and 0 the constraints of the UIScrollView. The problem is exactly when I change the constraints to make margins surrounded the UIScrollView. This is what happens:
First image in the UIScrollView
Second image in the UIScrollView
Third image in the UIScrollView
As you can see, each time you scroll, more off-center the current page is.
I have tried to subtract trailing and leading constrains constants to the width of the scrollLayout, play with frames and bouds but without success.
If I run this example in a smaller display like iphone 5S, the problem is more pointed.
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var pageController: UIPageControl!
#IBOutlet weak var scrollView: UIScrollView!
let imagesArray = ["b_1", "b_2", "b_3", "b_4", "b_5"]
override func viewDidLoad() {
super.viewDidLoad()
self.scrollView.isPagingEnabled = true
self.pageController.numberOfPages = imagesArray.count
self.pageController.pageIndicatorTintColor = UIColor.blue
self.pageController.currentPageIndicatorTintColor = UIColor.gray
for i in 0...imagesArray.count - 1{
let imageView = UIImageView()
imageView.contentMode = .scaleToFill
imageView.image = UIImage(named: self.imagesArray[i])
let xPos = CGFloat(i)*self.view.bounds.size.width
imageView.frame = CGRect(x: xPos, y: 0, width: view.frame.size.width, height: self.scrollView.frame.size.height )
self.scrollView.contentSize.width = view.frame.size.width*CGFloat(i+1)
self.scrollView.addSubview(imageView)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let page = scrollView.contentOffset.x/scrollView.frame.width
self.pageController.currentPage = Int(page)
}
}
So, I would like to know how to always obtain the current image centered.
Thank you
EDITED with Rajesh results and view debug:
I would recommend using a UICollectionView in place of a UIScrollView - otherwise you will be building a lot of the basics from scratch. You can use a collection view that centers the images, make sure paging is enabled and you should get the interface you're looking for. Make sure to adopt / conform to the UICollectionViewDelegate & UICollectionViewDelegateFlowLayout protocols with your view controller & set those delegates on your collection view. Hope that helps! Best of luck.

How to display a determined item in a paging UIScrollView instead of just loading it from 0

I want to recreate the same concept that the iOS built-in gallery app does in a basic level, that means having an array of images and being able to look at them individually and swiping right or left to see the other ones in the array. For testing purpose, I've set a small array of UIImages, and then added an individual subview for every image that there is in the array to a scrollView with paging enabled. Everything works as expected, however every time I load the app, the user starts from the first item (item #0 in the array) and I don't find a way to select manually the first image that needs to be shown, for example what if I want the scrollView to first show the third image in the array, and then be able to swipe left or right to page to the other images in the array? I can't find a solution to it.
I've tried to add other subviews but this doesn't work, the app works as expected but always starts from the first item in the array, I want to manually select the item so if I want to display the third image in the array, the collectionView does it like so. I have six images for testing purposes (each called menu_icon_(0...5))
#IBOutlet weak var scrollView: UIScrollView! {
didSet {
scrollView.delegate = self
}
}
var images:[ImagePreviewView] = [ImagePreviewView]()
override func viewDidLoad() {
super.viewDidLoad()
images = generateImages()
setupSlideScrollView(imagesPreviews: images)
}
func generateImages() -> [ImagePreviewView]{
var array:[ImagePreviewView] = []
for i in 0...5 {
let imagePreview = Bundle.main.loadNibNamed("ImagePreview", owner: self, options: nil)?.first as! ImagePreviewView
imagePreview.userSelectedImageView.image = UIImage(named: "menu_icon_\(i)")
array.append(imagePreview)
}
return array
}
func setupSlideScrollView(imagesPreviews : [ImagePreviewView]) {
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(imagesPreviews.count), height: view.frame.height)
scrollView.isPagingEnabled = true
for i in 0 ..< imagesPreviews.count {
imagesPreviews[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(imagesPreviews[i])
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
print("Page Index is \(pageIndex)")
}
I want to implement this code in such a way that the user adds images to a collection view, and when the user taps in an image, that indexPath.item is sent to this viewController and shows the image that the user selected, not the first image in the array, and then is able to swipe right or left. I'm trying to recreate how it would be only by the latter view controller logic.
So you will receive the index when this VC is created. Lets call it selectedIndex
var selectedIndex = 0 //I give 0 as initial value but it will be updated anyways.
Then in viewDidAppear try with following:
let width = view.frame.width
scrollView.setContentOffset(CGPoint(x: width * selectedIndex, y: 0), animated: false)
So when a user taps to an image you will receive the index number of that image in your VC, then setContentOffset of scrollView to width of the screen times selectedIndex, this should give you the result you want.

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.

Swift 3 Horizontal Multiple UIScrollView - Different Speeds

I have created two UIScrollViews ( One named scrollView and one named scrollLevel4 )
when I move scrollLevel4, I can get scrollView to move at the same speed using :-
func scrollViewDidScroll(_ scrollLevel4: UIScrollView) {
scrollView.contentOffset.x = scrollLevel4.contentOffset.x
}
However If I want to move scrollView at a different pace, not sure what to do, whenever I add anything to the end of line :
scrollView.contentOffset.x = scrollLevel4.contentOffset.x
it crashes, even a simple + 10, same pace, staggered offset, still crashes
also tried .scrollRectToVisible() method
Thoughts ?
Without seeing the error or your code it's hard to say for sure, but most likely you are setting the same delegate for both scrollViews. When you drag scrollLevel4, it triggers a scroll on scrollView, so you get an infinite loop and eventually a crash.
If you want to use the same delegate on both scrollViews, you'll need to check which one was passed before operating on them. Here's a basic working implementation. Open a new single view project and replace the code in ViewController.swift with:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
var imageView1: UIImageView!
var imageView2: UIImageView!
var scrollView1: UIScrollView!
var scrollView2: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
imageView1 = UIImageView(image: UIImage(named: "image.jpg"))
imageView2 = UIImageView(image: UIImage(named: "image.jpg"))
scrollView1 = UIScrollView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 200, height: 200)))
scrollView1.contentSize = imageView1.bounds.size
scrollView1.addSubview(imageView1)
view.addSubview(scrollView1)
scrollView2 = UIScrollView(frame: CGRect(origin: CGPoint(x: 0, y: 210), size: CGSize(width: 200, height: 200)))
scrollView2.contentSize = imageView2.bounds.size
scrollView2.addSubview(imageView2)
view.addSubview(scrollView2)
scrollView2.delegate = self
scrollView1.delegate = self
}
func scrollViewDidScroll(_ scrolled: UIScrollView) {
// both scrollViews call this when scrolled, so determine which was scrolled before adjusting
if scrolled === scrollView1 {
scrollView2.contentOffset.x = scrolled.contentOffset.x + 100
} else if scrolled === scrollView2 {
scrollView1.contentOffset.x = scrolled.contentOffset.x - 100
}
}
}
Note that whatever modification you apply to the offset of one, you have to apply the exact inverse (or nothing at all) to the other. Otherwise you'll have an infinite loop of back and forth scrolling ending in a crash.

Swift: Updating UIPageControl from ScrollView

I seem to be having some issues getting the UIPageControl to work.
I have a ViewController that holds a ScrollView. This ScrollView loads nib files that can be swiped. See image:
Here is the code that loads these:
self.addChildViewController(vc0)
self.displayReportView.addSubview(vc0.view)
vc0.didMoveToParentViewController(self)
var frame1 = vc1.view.frame
frame1.origin.x = self.view.frame.size.width
vc1.view.frame = frame1
self.addChildViewController(vc1)
self.displayReportView.addSubview(vc1.view)
vc1.didMoveToParentViewController(self)
// And so on
...
This works fine as in they scroll correctly etc..
Now, on the ViewController (one holding the scrollview) I added the delegate:
UIScrollViewDelegate
created some variables:
var frame: CGRect = CGRectMake(0, 0, 0, 0)
var colors:[UIColor] = [UIColor.redColor(), UIColor.blueColor(), UIColor.greenColor(), UIColor.yellowColor()]
var pageControl : UIPageControl = UIPageControl(frame: CGRectMake(50, 300, 200, 20))
I added some functions that are needed:
func configurePageControl() {
// The total number of pages that are available is based on how many available colors we have.
self.pageControl.numberOfPages = 4
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.redColor()
self.pageControl.pageIndicatorTintColor = UIColor.blackColor()
self.pageControl.currentPageIndicatorTintColor = UIColor.greenColor()
self.view.addSubview(pageControl)
}
// MARK : TO CHANGE WHILE CLICKING ON PAGE CONTROL
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * displayReportView.frame.size.width
displayReportView.setContentOffset(CGPointMake(x, 0), animated: true)
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
pageControl.currentPage = Int(pageNumber)
}
Now, When I run the app the scrollview dots show, but when I swipe they do not update.
Question
How do I update the dots to reflect what view is showing?
let me know if you need anything else from my code to see functionality.
You can certainly do what you're describing, if you have a paging scroll view; I have an example of it that uses this code:
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let x = scrollView.contentOffset.x
let w = scrollView.bounds.size.width
pageControl.currentPage = Int(x/w)
}
Except for your round, that looks a lot your code, which makes me think that your code should work. That makes me think that something else is just misconfigured. Is this a paging scroll view? Did you remember to make this object your scroll view's delegate? Use logging or a breakpoint to be certain that your scrollViewDidEndDecelerating is even being called in the first place.
However, I would just like to point out that the configuration you are describing is effectively what UIPageViewController gives you for free — a scroll view with view controller views, plus a page control — so you might want to use that instead.
I would replace the scroll view with a UICollectionView. This way you get paging for free, and it will be better, because paging will work out of the box, without you having to calculate the frame offsets.
Be sure to set collectionView.pagingEnabled = true
To get the current page number, do collectionView.indexPathsForVisibleItems().first?.item
To change the page:
collectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: CenteredHorizontally, animated: true)

Resources