Zoom UIImageView in UIScrollView – not centered - ios

I have an UIScrollView with an UIImageView in it. Just a simple test case: The UIImageView is full width of the scroll view and centered to Y with auto layout. However, in the final result on my device it is not really centered by Y.
But i have the problem that when zooming the UIImageView with the scroll view it "drifts" to the bottom area – not just scaled in the center as it should be. It also "drifts" to the left when zoom out (at the very end of the video) and bounces back to the center.
I've made a small preview video of this behavior: https://www.youtube.com/watch?v=ivRNkzNrcEA
Here is my simple test code:
class TestController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var SCROLL: UIScrollView!
#IBOutlet weak var IMAGE: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
SCROLL.minimumZoomScale = 1;
SCROLL.maximumZoomScale = 6.0;
SCROLL.zoomScale = 1.0;
SCROLL.contentSize = IMAGE.frame.size;
SCROLL.delegate = self;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return IMAGE
}
func scrollViewDidZoom(scrollView: UIScrollView) {
}
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
}
}
And here is my test auto layout:

I am not sure if it is possible to keep centering with just auto-layout. One approach that works for me is to to sub-class UIScrollView and modify layoutSubviews as illustrated in Apple WWDC example. Code below
-(void) layoutSubviews
{
[super layoutSubviews];
UIImageView *v = (UIImageView *)[self.delegate viewForZoomingInScrollView:self];
//Centering code
CGFloat svw = self.bounds.size.width;
CGFloat svh = self.bounds.size.height;
CGFloat vw = v.frame.size.width;
CGFloat vh = v.frame.size.height;
CGRect f = v.frame;
if (svw > vw)
{
f.origin.x = (svw - vw)/2.0;
}
else
{
f.origin.x = 0;
}
if (svh > vh)
{
f.origin.y = (svh - vh)/2.0;
}
else
{
f.origin.y = 0;
}
v.frame = f;
}

Related

Label text not updating after scroll event

I'm trying to update text of a label after a scroll event. I have a print command that prints the correct value but the label is not updating.
Here's my code
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let x = scrollView.contentOffset.x
let w = scrollView.bounds.size.width
let p = Int(x/w)
print("page \(p)") // this prints correct value
self.signalLabel.text = signalText[Int(x/w)] // this does not update
}
what's the deal?
Here's the complete view controller code. This view is called from a button click on the initial view controller. This view contains a UIScrollView and UIPageControl. The UIScrollView contains two images that can be scrolled back and forth. I want to update the label text based on image that is shown.
import UIKit
class SignalOneViewController: UIViewController, UIScrollViewDelegate {
// MARK: Properties
#IBOutlet weak var signalScrollView: UIScrollView!
#IBOutlet weak var signalPageControl: UIPageControl!
#IBOutlet weak var signalLabel: UILabel!
// MARK: - Button Actions
#IBAction func signalOneButton(_ sender: Any) {
print("signal one button clicked")
performSegue(withIdentifier: "SignalOneSegue", sender: self)
}
#IBAction func onCancelButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
let signalImages = ["signal1a.png", "signal1b.png"]
let signalText = ["Ready for play", "Untimed down"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidLayoutSubviews() {
self.loadScrollView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadScrollView() {
let pageCount : CGFloat = CGFloat(signalImages.count)
signalLabel.text = signalText[0]
signalScrollView.backgroundColor = UIColor.clear
signalScrollView.delegate = self
signalScrollView.isPagingEnabled = true
signalScrollView.contentSize = CGSize(width: signalScrollView.frame.size.width * pageCount, height: signalScrollView.frame.size.height)
signalScrollView.showsHorizontalScrollIndicator = false
signalScrollView.showsVerticalScrollIndicator = false
signalPageControl.numberOfPages = Int(pageCount)
signalPageControl.pageIndicatorTintColor = UIColor.lightGray
signalPageControl.currentPageIndicatorTintColor = UIColor.blue
signalPageControl.addTarget(self, action: #selector(self.pageChanged), for: .valueChanged)
for i in 0..<Int(pageCount) {
print(self.signalScrollView.frame.size.width)
let image = UIImageView(frame: CGRect(x: self.signalScrollView.frame.size.width * CGFloat(i), y: 0, width: self.signalScrollView.frame.size.width, height: self.signalScrollView.frame.size.height))
image.image = UIImage(named: signalImages[i])!
image.contentMode = UIViewContentMode.scaleAspectFit
self.signalScrollView.addSubview(image)
}
}
//MARK: UIScrollView Delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewWidth: CGFloat = scrollView.frame.size.width
// content offset - tells by how much the scroll view has scrolled.
let pageNumber = floor((scrollView.contentOffset.x - viewWidth / 50) / viewWidth) + 1
signalPageControl.currentPage = Int(pageNumber)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let x = scrollView.contentOffset.x
let w = scrollView.bounds.size.width
let p = Int(x/w)
print("page \(p)")
self.signalLabel.text = signalText[p]
print(">>> \(signalText[Int(x/w)])")
}
//MARK: page tag action
#objc func pageChanged() {
let pageNumber = signalPageControl.currentPage
var frame = signalScrollView.frame
frame.origin.x = frame.size.width * CGFloat(pageNumber)
frame.origin.y = 0
signalScrollView.scrollRectToVisible(frame, animated: true)
}
}
Make sure signalLabe IBOutlet is attached to your label in storyboard or xib

Double Tap to Zoom In/Out

I have an app that displays a bunch of pictures when the user presses the button from a text list view screen. I have a specific subclass that allows the user to pinch and zoom in, but I am curious as to what code I need to enter in my particular swift file to allow the user to double tap to zoom in and out. I get three errors using this code. Two say "Value of type 'ZoomingScrollView' has no member 'scrollview' or 'imageview' " and also " 'CGRectZero' is unavaiable in Swift" My code for the subclass that allows the user to zoom in is below along with the code I am using to double tap to zoom.
import UIKit
class ZoomingScrollView: UIScrollView, UIScrollViewDelegate {
#IBOutlet weak var viewForZooming: UIView? = nil {
didSet {
self.delegate = self
} }
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return viewForZooming }
#IBAction func userDoubleTappedScrollview(recognizer: UITapGestureRecognizer) {
if let scrollV = self.scrollview {
if (scrollV.zoomScale > scrollV.minimumZoomScale) {
scrollV.setZoomScale(scrollV.minimumZoomScale, animated: true)
}
else {
//(I divide by 3.0 since I don't wan't to zoom to the max upon the double tap)
let zoomRect = self.zoomRectForScale(scrollV.maximumZoomScale / 3.0, center: recognizer.locationInView(recognizer.view))
self.scrollview?.zoomToRect(zoomRect, animated: true)
}
} }
func zoomRectForScale(scale : CGFloat, center : CGPoint) -> CGRect {
var zoomRect = CGRectZero
if let imageV = self.imageView {
zoomRect.size.height = imageV.frame.size.height / scale;
zoomRect.size.width = imageV.frame.size.width / scale;
let newCenter = imageV.convertPoint(center, fromView: self.scrollview)
zoomRect.origin.x = newCenter.x - ((zoomRect.size.width / 2.0));
zoomRect.origin.y = newCenter.y - ((zoomRect.size.height / 2.0));
}
return zoomRect; }}
The issue seems to be that you copied some code from a view controller that had a scrollView and imageView outlet. I'm guessing your viewForZooming: UIView is your image view that you are zooming on. You also don't have to reference self.scrollView anymore because self is the scroll view since you're subclassing scroll view :) I think the code below should fix your problem (note: it's in the latest swift syntax. what you posted was not, so you may have to switch the code back to the old style if necessary. You should try to use the latest Swift though). Good luck and let me know if you have questions.
import UIKit
class ZoomingScrollView: UIScrollView, UIScrollViewDelegate {
#IBOutlet weak var viewForZooming: UIView? = nil {
didSet {
self.delegate = self
} }
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return viewForZooming }
#IBAction func userDoubleTappedScrollview(recognizer: UITapGestureRecognizer) {
if (zoomScale > minimumZoomScale) {
setZoomScale(minimumZoomScale, animated: true)
}
else {
//(I divide by 3.0 since I don't wan't to zoom to the max upon the double tap)
let zoomRect = zoomRectForScale(scale: maximumZoomScale / 3.0, center: recognizer.location(in: recognizer.view))
zoom(to: zoomRect, animated: true)
}
}
func zoomRectForScale(scale : CGFloat, center : CGPoint) -> CGRect {
var zoomRect = CGRect.zero
if let imageV = self.viewForZooming {
zoomRect.size.height = imageV.frame.size.height / scale;
zoomRect.size.width = imageV.frame.size.width / scale;
let newCenter = imageV.convert(center, from: self)
zoomRect.origin.x = newCenter.x - ((zoomRect.size.width / 2.0));
zoomRect.origin.y = newCenter.y - ((zoomRect.size.height / 2.0));
}
return zoomRect;
}
}

How to fix current page does not display true? [Swift]

Why my current page in Page Control does not show correct output?
Page 1 and Page 2 display in one dot? Images here:
http://i.stack.imgur.com/498ap.png, http://i.stack.imgur.com/41kdg.png
Last page is page 6 display in dot 5th, doesn't last dot? Image: http://i.stack.imgur.com/NP9u1.png
My code here:
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
let totalPages = 6
let sampleBGColors: Array<UIColor> = [UIColor.redColor(), UIColor.yellowColor(), UIColor.greenColor(), UIColor.magentaColor(), UIColor.orangeColor(), UIColor.lightGrayColor()] #IBOutlet weak var scrollView: UIScrollView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
configureScrollView()
configurePageControl()
}
func configureScrollView() {
// Enable paging.
scrollView.pagingEnabled = true
// Set the following flag values.
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
scrollView.scrollsToTop = false
// Set the scrollview content size.
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * CGFloat(totalPages), scrollView.frame.size.height)
// Set self as the delegate of the scrollview.
scrollView.delegate = self
// Load the TestView view from the TestView.xib file and configure it properly.
for i in 0 ..< totalPages {
// Load the TestView view.
let testView = NSBundle.mainBundle().loadNibNamed("TestView", owner: self, options: nil)[0] as! UIView
// Set its frame and the background color.
testView.frame = CGRectMake(CGFloat(i) * scrollView.frame.size.width, scrollView.frame.origin.y, scrollView.frame.size.width, scrollView.frame.size.height)
testView.backgroundColor = sampleBGColors[i]
// Set the proper message to the test view's label.
let label = testView.viewWithTag(1) as! UILabel
label.text = "Page #\(i + 1)"
// Add the test view as a subview to the scrollview.
scrollView.addSubview(testView)
}
}
func configurePageControl() {
// Set the total pages to the page control.
pageControl.numberOfPages = totalPages
// Set the initial page.
pageControl.currentPage = 0
}
// MARK: UIScrollViewDelegate method implementation
func scrollViewDidScroll(scrollView: UIScrollView) {
// Calculate the new page index depending on the content offset.
let currentPage = floor(scrollView.contentOffset.x / UIScreen.mainScreen().bounds.size.width);
// Set the new page index to the page control.
pageControl.currentPage = Int(currentPage)
}
// MARK: IBAction method implementation
#IBAction func changePage(sender: AnyObject) {
// Calculate the frame that should scroll to based on the page control current page.
var newFrame = scrollView.frame
newFrame.origin.x = newFrame.size.width * CGFloat(pageControl.currentPage)
scrollView.scrollRectToVisible(newFrame, animated: true)
}
Please help me! Thank you.
Sorry for my English is bad.
Change the pageControl.currentPage in UIScrollViewDelegate's implemention scrollViewDidEndScrollingAnimation and scrollViewDidEndDecelerating, and I improved the calculation with scrollView's width, not screen's width:
// MARK: UIScrollViewDelegate method implementation
func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
// Calculate the new page index depending on the content offset.
let currentPage = floor(scrollView.contentOffset.x / scrollView.bounds.size.width)
// Set the new page index to the page control.
pageControl.currentPage = Int(currentPage)
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView){
scrollViewDidEndScrollingAnimation(scrollView)
}

How to disable scrolling while using UIPanGestureRecognizer on a UIView

In my app I have a draggable UIView contained in a UIScrollView so that I can zoom it. This moving UIView has a UIPanGestureRecognizer but if I zoom in first and then I try to move the UIView, that does not happen and instead the scrolling is performed as if the UIPanGestureRecognizer is not detected.
This is my associated method to the UIPanGestureRecognizer:
func handlePan(recognizer: UIPanGestureRecognizer) {
if (self.panRec.state == UIGestureRecognizerState.Began) {
self.scrollViewContainer.scrollEnabled = false
}
else if (self.panRec.state == UIGestureRecognizerState.Ended) {
self.scrollViewContainer.scrollEnabled = true
}
let translation = recognizer.translationInView(self)
if let view = recognizer.view {
self.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPointZero, inView: self)
self.updatePinsPosition()
}
As you can see I try to disable scrolling when the the user taps in the draggable UIView and reactivate it at the end but it doesn't work. Where am I making a mistake? What am I missing?
UPDATE
So explain myself better, I have a UIScrollView which contains a UIView and inside this last there are some other UIViews acting as pinch, the azure ones. I decided to use a further UIView as container so that the sub views are zoomed all together by scrolling. So, my zoom code is the following:
class PhotoViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var containerView: ContainerController!
override func viewDidLoad() {
...
self.scrollView.delegate = self
self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.flashScrollIndicators()
self.scrollView.minimumZoomScale = 1.0
self.scrollView.maximumZoomScale = 10.0
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return self.containerView
}
}
You have to override this method of Scrollview Delegate in your ViewController for both zooming and panning
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
scale *= [[[self.scrollView window] screen] scale];
[view setContentScaleFactor:scale]; // View which you want to Zoom & pan.
for (UIView *subview in view.subviews) {
[subview setContentScaleFactor:scale]; // all the Container Views
}
}
Hope it will help you to solve your problem.

Swift Fade In Animation for Background Animation between A Single View to a PageView Controller

So I am trying to create a fade in animation for a UI Image that is in a page view scroller after a button is clicked in the main view controller. This is my main storyboard.
class MainWorkoutViewController: UIViewController {
// Outlet used in storyboard
#IBOutlet var scrollView: UIScrollView?;
override func viewDidLoad() {
super.viewDidLoad();
func scrollViewDidEndDragging(MainWorkoutViewController: UIScrollView,
withVelocity: CGPoint,
targetContentOffset : UnsafeMutablePointer<CGPoint>){
}
// 1) Create the three views used in the swipe container view
var AVc :AViewController = AViewController(nibName: "AViewController", bundle: nil);
var BVc :BViewController = BViewController(nibName: "BViewController", bundle: nil);
var CVc :CViewController = CViewController(nibName: "CViewController", bundle: nil);
// 2) Add in each view to the container view hierarchy
// Add them in opposite order since the view hieracrhy is a stack
self.addChildViewController(CVc);
self.scrollView!.addSubview(CVc.view);
CVc.didMoveToParentViewController(self);
self.addChildViewController(BVc);
self.scrollView!.addSubview(BVc.view);
BVc.didMoveToParentViewController(self);
self.addChildViewController(AVc);
self.scrollView!.addSubview(AVc.view);
AVc.didMoveToParentViewController(self);
// 3) Set up the frames of the view controllers to align
// with eachother inside the container view
var adminFrame :CGRect = BVc.view.frame;
adminFrame.origin.x = adminFrame.width;
AVc.view.frame = adminFrame;
var BFrame :CGRect = AVc.view.frame;
BFrame.origin.x = 2*BFrame.width;
CVc.view.frame = BFrame;
// 4) Finally set the size of the scroll view that contains the frames
var scrollWidth: CGFloat = 3 * self.view.frame.width
var scrollHeight: CGFloat = self.view.frame.size.height
self.scrollView!.contentSize = CGSizeMake(scrollWidth, scrollHeight)
var frame: CGRect = self.view.frame
frame.origin.x = frame.size.width * CGFloat(1);
frame.origin.y = 0;
self.scrollView!.scrollRectToVisible(frame, animated: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
self.tabBarController?.selectedIndex = 2
}
}
This is the .swift file for the first Viewcontroller that I have, which contains the image I want to fade in when the screen loads.
import UIKit
class AViewController: UIViewController {
#IBOutlet var Background: UIImageView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(1.5, animations: {
self.Background.alpha = 1.0
})
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I looked around for a way to do it and that is why I have the animatedWithDuration, but instead all I am getting is the screen swiping up from the bottom of the phone. Any ideas?
Try setting the alpha to 0.0 before the UIView.animateWithDuration function.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.background.alpha = 0.0
UIView.animateWithDuration(1.5, animations: {
self.background.alpha = 1.0
})
}
Please note that viewDidAppear is not called after you have scrolled to a view in the scrollview. It is called when the view is added to the view hierarchy. You have to manually write the code for making the label appear using scrollView delegate functions if you want to make it appear when you scroll with your fingers or scroll automatically later.
You can do a cross dissolve animation instead of scrolling by doing
CATransaction.flush()
self.scrollView.contentOffset = CGPointMake(self.view.frame.width,0)
UIView.transitionWithView(self.scrollView, duration: 1.0, options:
.TransitionCrossDissolve, animations: { () -> Void in
}) { (finished) -> Void in
}
Either use CATransactionFlush or put the code inside viewDidAppear.
Remove the scrollView.scrollRectToVisible code.

Resources