Zoom and paging in one ScrollView - ios

i am trying to create something like Photos gallery.
But now i have problem, with zooming in UIScrollView.
for i in 0..<imageArray.count{
let imageView = UIImageView()
imageView.isUserInteractionEnabled = true
imageView.image = imageArray[i]
imageView.contentMode = .scaleAspectFit
let xPosition = self.view.frame.width * CGFloat(i)
imageView.frame = CGRect(x: 0, y: 0, width: self.scrollView.frame.width, height: self.scrollView.frame.height)
scrollView.addSubview(imageView)
scrollView.contentSize.width = scrollView.frame.width * CGFloat(i+1)
}
scrollView.isScrollEnabled = true
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 6.0
scrollView.isUserInteractionEnabled = true
scrollView.delegate = self
Paging works fine, but i dont know now how to enable zooming each imageView.

You will need to implement the following method from the UIScrollViewDelegate protocol:
func viewForZooming(in: UIScrollView) -> UIView?
In that you return the view that you want to be zooming.
Now in this case you have multiple image views (depending on imageArray.count) but I assume you want to scroll into them all (i.e. not an individual image) so the best thing would be to add a 'content view' (UIView) to the scroll view and then add the individual image views to that instead of the scroll view directly and size it appropriately.
Then you can return that 'content view' in the viewForZooming method.
So assuming you have created this 'content view' then the method would look like this:
func viewForZooming(in: UIScrollView) -> UIView? {
return self.contentView
}

Related

How can I fill image in scroll view to fill screen?

I am playing around with scroll views, and I've run into an issue I'be stuck with. I have a view controller create in Storyboard. The view controller contains a scroll view which fills the entire superview.
I then added the images programmatically to the scroll view. The images do show within the scroll view and paging works just fine. Only problem is the scroll view is set ti fill superview but the image view that hold the images seems like it stops above where the navigation bar would be. How can I have the image view fill the whole view within the scroll view?
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pagingView: UIPageControl!
var images = [UIImage]()
var frame = CGRect(x: 0, y: 0,width: 0,height: 0)
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
scrollView.isPagingEnabled = true
images = [UIImage(named: "Slide1")!, UIImage(named: "Slide2")!, UIImage(named: "Slide3")!, UIImage(named: "Slide4")!]
pagingView.numberOfPages = images.count
// This is where I think I'm having the height problem.
for i in 0..<images.count {
let imageView = UIImageView()
let x = self.view.frame.size.width * CGFloat(i)
imageView.frame = CGRect(x: x, y: 0, width: self.view.frame.width, height: self.view.frame.height)
imageView.contentMode = .scaleAspectFill
imageView.image = images[i]
scrollView.contentSize.width = scrollView.frame.size.width * CGFloat(i + 1)
scrollView.addSubview(imageView)
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
pagingView.currentPage = Int(pageNumber)
}
After setting nav bar to hidden, here is the output
Scroll view background color is red
In this case you need to enable the parent view clipsToBounds. Set UIScrollview clipsToBounds property to True.
Programmatically scrollView.clipsToBounds = true
In UIStoryBoard - Click the view->Attributes Inspector
If you would like to see the whole screen, make sure to add the topConstraint of scrollView assigned superView and hide the navigationBar in viewWillAppear,
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
Make sure to remove the status bar by
override var prefersStatusBarHidden: Bool {
return true
}
Update the Y position of Image.
imageView.frame = CGRect(x: x, y: **self.scrollView.frame.minY**, width: self.view.frame.width, height: self.view.frame.height)
Update the scrollView topConstraint by -20.

Adding gestures to uiimageview in uiscrollview (pan, doubletap, pinch)

Currently i am trying to create some kind of an imageviewer, where you can click on an image which is then displayed in full size. Now i want to add gestures to it to zoom. I want to understand and see how to add the pinch gesture to zoom in and out, to be able to pan around the image and to zoom in quickly with the double tap gesture. I did not found much good tutorials.
I know that you zoom into the view, not into the image. That is why you use a ScrollView containing an ImageView. Now what is missing to enable zooming, pinching and moving the image around?
Thank you in advance for any helpful post.
In the following is "my" current code base for this feature. What needs to be added?
class DetailViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var imageView: UIImageView!
override func viewDidLoad()
{
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = false
scrollView=UIScrollView()
scrollView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: (self.view.frame.height - (self.navigationController?.navigationBar.frame.size.height)! + 44))
scrollView.minimumZoomScale=1
scrollView.maximumZoomScale=3
scrollView.bounces=false
scrollView.delegate=self
self.view.addSubview(scrollView)
imageView=UIImageView()
imageView.image = UIImage(named: "inlinelogo.jpg")
imageView.frame = CGRect(x: 0, y: 0, width: scrollView.frame.width, height: scrollView.frame.height - (44 + UIApplication.shared.statusBarFrame.size.height))
imageView.backgroundColor = .black
imageView.contentMode = .scaleAspectFit
scrollView.addSubview(imageView)
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?
{
return imageView
}
}
you missed the userInteractionEnabled property of UIImageView add
imageView.isuserInteractionEnabled = true

Swift - Add UIImageView as subview of UIWebView scrollView and scaling

I have a UIWebView and I have successfully added a UIImage view to the UIWebView’s scrollView like so:
let localUrl = String(format:"%#/%#", PDFFilePath, fileNameGroup)
let url = NSURL.fileURLWithPath(localUrl)
panRecognizer = UITapGestureRecognizer(target: self, action: #selector(panDetected))
pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(pinchDetected))
panRecognizer.delegate = self
pinchRecognizer.delegate = self
webview = UIWebView()
webview.frame = self.view.bounds
webview.scrollView.frame = webview.frame
webview.userInteractionEnabled = true
webview.scalesPageToFit = true
webview.becomeFirstResponder()
webview.delegate = self
webview.scrollView.delegate = self
self.view.addSubview(webview)
webview.loadRequest(NSURLRequest(URL:url))
webview.gestureRecognizers = [pinchRecognizer, panRecognizer]
let stampView:StampAnnotation = StampAnnotation(imageIcon: UIImage(named: "approved.png"), location: CGPointMake(currentPoint.x, currentPoint.y))
self.webview.scrollView.addSubview(stampView)
My UIWebView scrollView is scalable. Now I am looking for away to have my UIImageView (StampAnnotation is a class and UIImageView is its subclass) scale when the scrollView scales. So if the user zooms in on the scrollView, the UIImageView will get bigger and stay in a fixed position and if the user zooms out, the UIImageView will get smaller while the scrollView gets smaller while staying in a fixed position.
I really hope that makes sense. I have tried the following:
func pinchDetected(recognizer:UIPinchGestureRecognizer)
{
for views in webview.scrollView.subviews
{
if(views.isKindOfClass(UIImageView))
{
views.transform = CGAffineTransformScale(views.transform, recognizer.scale, recognizer.scale)
recognizer.scale = 1
}
}
if(appDelegate.annotationSelected == 0)
{
webview.scalesPageToFit = true
}
else
{
webview.scalesPageToFit = false
}
}
but this does nothing, if I remove this line:
recognizer.scale = 1
it scales way too big too fast. My question is, how do I get my UIImageView to scale when the UIWebview’s scrollView scrolls?
Any help would be appreciated.
This solved my problem.
func scrollViewDidZoom(scrollView: UIScrollView) {
for views in webview.scrollView.subviews
{
if(views.isKindOfClass(UIImageView))
{
views.transform = CGAffineTransformMakeScale(scrollView.zoomScale, scrollView.zoomScale)
}
}
}
No it does not stay in a fixed position on the page, but I think that is a constraints issue?
You were close...
1) Add a property to hold onto an external reference for your stampViewFrame:
var stampViewFrame = CGRect(x: 100, y: 100, width: 100, height: 100)
2) Replace your scrollViewDidZoom() with this:
func scrollViewDidZoom(scrollView: UIScrollView) {
for views in webView.scrollView.subviews
{
if(views.isKindOfClass(UIImageView))
{
views.frame = CGRect(x: stampViewFrame.origin.x * scrollView.zoomScale, y: stampViewFrame.origin.y * scrollView.zoomScale, width: stampViewFrame.width * scrollView.zoomScale, height: stampViewFrame.height * scrollView.zoomScale)
}
}
}
3) Finally, because the zoom scale resets to 1 at the begining of each new zooming action, you need to adjust the value of your stampViewFrame property:
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
stampViewFrame = CGRect(x: stampViewFrame.origin.x * scale, y: stampViewFrame.origin.y * scale, width: stampViewFrame.width * scale, height: stampViewFrame.height * scale)
}
I also tried to answer your other question about layout during orientation change, but I now have a much better understanding of what you are trying to do. If you want your stampView to always be on in the same place relative to the web content, you have to get into HTML/JS because the webpage lays itself out dynamically. A much much more simple (and hopefully close enough) solution would be to add the following:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
webView.frame = view.bounds
stampView.frame = stampViewFrame
}
Use the scroll delegate method of scrollViewDidZoom :
func scrollViewDidZoom(scrollView: UIScrollView){
//Change the subview of scroll frame as per the scroll frame scale
//rect = initial position & size of the image.<class instance>
stampView.frame = CGRectMake((CGRectGetMaxX(rect)-rect.size.width)*webView.scrollView.zoomScale, (CGRectGetMaxY(rect)-rect.size.height)*webView.scrollView.zoomScale, rect.width*webView.scrollView.zoomScale,rect.height*webView.scrollView.zoomScale)
}

When to use UITouch vs UIScroll

I would like to achieve the design you see in dating apps. Where you can vertically scroll images of a profile and also horizontally scroll to view the next or previous person in the list.
Currently I have my views laid out as such.
Previous-UIView - current UIView - next UIView
UIScrollView. UIScrollView. UIScrollView
Images. Images. Images
UIView. UIView. UIView
Profile info. Profile info. Profile info
UIPageControl. UIPageControl UIPageControl.
Only one of the Views occupies the main view with next and previous off screen. Ideally when the user moves the view left I would programmatically remove the previous view, make current the previous, the next current and add a new view for next. Visa versa for moving right.
What is the best way to scroll the views horizontally?
Should I wrap them all in a UIScrollView? And would that interfere with the UIScrollView sub Views?
Or should I program touch controls to move the views?
Or is there a better way?
I'm still a newbie at iOS development so any help would be greatly appreciated.
So I've tried some experimenting with a test app and I'm pleased to say you can have UIScrollviews inside UIScrollviews.
I was able to get it running perfectly. Here is my code below.
override func viewDidLoad() {
super.viewDidLoad()
self.superView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
var subImages1 = ["IMG_0004.JPG","IMG_0005.JPG","IMG_0008.JPG"]
var subImages2 = ["IMG_0009.JPG","IMG_0010.JPG","IMG_0011.JPG"]
var subImages3 = ["IMG_0013.JPG","IMG_0017.JPG","IMG_0018.JPG"]
self.images.append(subImages1)
self.images.append(subImages2)
self.images.append(subImages3)
self.superView.frame = self.view.frame
self.superView.contentSize = CGSizeMake(self.view.frame.width*3, self.view.frame.height)
self.superView.contentOffset = CGPoint(x:self.view.frame.width,y:0)
self.superView.pagingEnabled = true
self.view.addSubview(self.superView)
//layout the UIVeiws into the master ScrollView
for i in 0...2{
var offset = self.view.frame.width * CGFloat(i)
var pView = UIView()
pView.frame = CGRectMake(offset, 0, self.view.frame.width, self.view.frame.height)
pView.backgroundColor = colours[i]
self.superView.addSubview(pView)
self.profileViews.append(pView)
}
// Add sub Scroll views and images to the Views.
for (index, view) in enumerate(self.profileViews){
var scrollView = UIScrollView()
scrollView.delegate = self
scrollView.frame = CGRectMake(10, 10, self.view.frame.width-20, self.view.frame.height-20)
scrollView.pagingEnabled = true
scrollView.contentSize = CGSizeMake(scrollView.frame.width, scrollView.frame.height * CGFloat(images[index].count))
for (index2, image) in enumerate(images[index]){
var subImage = UIImageView()
subImage.frame = CGRectMake(0, scrollView.frame.height * CGFloat(index2), scrollView.frame.width, scrollView.frame.height)
subImage.contentMode = UIViewContentMode.ScaleAspectFit
subImage.image = UIImage(named: image as! String)
scrollView.addSubview(subImage)
}
view.addSubview(scrollView)
self.scrollViews.append(scrollView)
}
}
//Use the did end decelerating as it executes the code once the scoll has finished moving.
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
if(scrollView == self.superView){
var contentOffset = scrollView.contentOffset
var pageWidth = self.superView.frame.width
var fractionalPage:Double = Double(self.superView.contentOffset.x / pageWidth)
var page = lround(fractionalPage)
// In this example I take the last UIView from the stack and move it to the first.
// I would do the same in the real app but update the contents of the view after
if(page == 0){
var tempView = self.profileViews[2]
self.profileViews[2].removeFromSuperview()
self.profileViews.removeAtIndex(2)
for view in self.profileViews{
view.frame = CGRectMake(view.frame.minX + self.view.frame.width, 0, view.frame.width, view.frame.height)
println(view.frame)
}
tempView.frame = CGRectMake(0, 0, tempView.frame.width, tempView.frame.height)
self.profileViews.insert(tempView, atIndex: 0)
self.superView.addSubview(tempView)
var newOffset = contentOffset.x + pageWidth
self.superView.contentOffset = CGPoint(x: newOffset, y: 0)
}
// Take the first view and move it to the last.
if(page == 2){
var tempView = self.profileViews[0]
self.profileViews[0].removeFromSuperview()
self.profileViews.removeAtIndex(0)
for view in self.profileViews{
view.frame = CGRectMake(view.frame.minX - self.view.frame.width, 0, view.frame.width, view.frame.height)
println(view.frame)
}
tempView.frame = CGRectMake(tempView.frame.width*2, 0, tempView.frame.width, tempView.frame.height)
self.profileViews.append(tempView)
self.superView.addSubview(tempView)
var newOffset = contentOffset.x - pageWidth
self.superView.contentOffset = CGPoint(x: newOffset, y: 0)
}
}
}

Image overlaps previous view when tapping/swiping back

I have a UIScrollView which covers one of my views entirely. I have added a background image to this same view which scrolls at a slightly different rate to the actual UIScrollView. This works absolutely fine unless I use the back swipe gesture or tap the 'Back' button. What happens is the image covers the view for about 0.5 seconds before disappearing, and it looks pretty bad.
This is what I mean:
As you can see, that is mid way through the gesture, and rather than being able to see the previous view, you just see the part of the image that is off to the left. It doesn't happen on the first page of the UIScrollView so I guess it's because the image is overlapping the previous view.
Here is my code:
override func viewDidLoad() {
let pagesScrollViewSize = scrollView.frame.size
scrollView.contentSize = CGSize(width: pagesScrollViewSize.width * CGFloat(images.count), height: pagesScrollViewSize.height)
backgroundImageView.frame = CGRect(x: 0, y: 0, width: 2484, height: 736)
backgroundImageView.image = UIImage(named: "SF.png")
var visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light)) as UIVisualEffectView
visualEffectView.frame = backgroundImageView.bounds
backgroundImageView.addSubview(visualEffectView)
view.addSubview(backgroundImageView)
view.sendSubviewToBack(backgroundImageView)
scrollView.backgroundColor = UIColor.clearColor()
}
func scrollViewDidScroll(scrollView: UIScrollView) {
loadVisiblePages()
var factor = scrollView.contentOffset.x / (scrollView.contentSize.width - 414);
if factor < 0 {
factor = 0
}
if factor > 1 {
factor = 1
}
var frame: CGRect = backgroundImageView.frame
frame.origin.x = factor * (414 - backgroundImageView.frame.size.width)
backgroundImageView.frame = frame
}
Anyone have any suggestions?
You have to add the following in your viewDidLoad function:
self.view.clipsToBounds = true or scrollView.clipsToBounds = true if you just want to clip the subviews in your UIScrollView.
Setting this value to true causes subviews to be clipped to the bounds of the receiver. If set to false, subviews whose frames extend beyond the visible bounds of the receiver are not clipped. The default value is false.
From Apple' doc : https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/index.html#//apple_ref/occ/instp/UIView/clipsToBounds

Resources