Detect when y offset changes in scrollView with paging enabled - ios

I would like to find out which page is visible to the user and also do a certain action when a change in the page is detected. To do this I am using the function scrollViewDidScroll. As I didn't get the result I wanted (nothing happened), I looked for answers on stackoverflow and found the following question which was answered: Detecting UIScrollView page change, to be more precise I used the answer by Michael Waterfall and converted the Objective-C code to swift code.
Below you will find the code I used to create the scrollView in my class HomeController: UIViewController, UIScrollViewDelegate {. Moreover the function by Michael Waterfall is declared and called in the function initiateScrollView(). However, the function doesn't print anything. Do you know where I am doing something wrong? I can't see where I made a mistake. Nothing happens when I scroll (e.g. from page 1 to page 2 (homeView to view2)). The function only prints before: 0 once and after that nothing happens.
Code
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
initiateScrollView()
}
func initiateScrollView() {
//create scrollView with paging enabled
let scrollView = UIScrollView(frame: view.bounds)
scrollView.isPagingEnabled = true
view.addSubview(scrollView)
//get page size
let pageSize = view.bounds.size
//individual views
let homeView = UIView()
homeView.backgroundColor = .green
let view2 = UIView()
view2.backgroundColor = .blue
let view3 = UIView()
view3.backgroundColor = .red
//array with individual views
let pagesViews = [homeView, view2, view3]
//amount of views
let numberOfPages = pagesViews.count
print(numberOfPages) //prints '3'
//add subviews (pages)
for (pageIndex, page) in pagesViews.enumerated(){
//add individual pages to scrollView
page.frame = CGRect(origin: CGPoint(x:0 , y: CGFloat(pageIndex) * pageSize.height), size: pageSize)
scrollView.addSubview(page)
}
func scrollViewDidScroll(scrollView: UIScrollView) {
var previousPage = 0
let fractionalPage: Float = Float(scrollView.contentOffset.y / pageSize.height)
let page = lround(Double(fractionalPage))
print("before:", page) //prints 'before: 0' once
if previousPage != page {
// Page has changed, do your thing!
// ...
// Finally, update previous page
print("before:", page)
previousPage = page
print("after:", page)
} //never prints anything
}
scrollViewDidScroll(scrollView: scrollView)
//define size of scrollView
scrollView.contentSize = CGSize(width: pageSize.width, height: pageSize.height * CGFloat(numberOfPages))
}

You forgot to set the viewController as the delegate for the scroll view, as following:
func initiateScrollView() {
//create scrollView with paging enabled
let scrollView = UIScrollView(frame: view.bounds)
scrollView.isPagingEnabled = true
scrollView.delegate = self
view.addSubview(scrollView)
(...) }
And I recommend that you use the scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) function, instead of scrollViewDidScroll(...)

This is because you haven't set the delegate property of your scrollview.
Add scrollView.delegate = self in your initiateScrollView method.

Related

Swift - How to make a view controller scrollable?

I have a view controller with some elements in it.
class MyViewController: UIViewController {
// ......
}
This is what I want:
example of what I want
This is what my app looks like:
I want to make the two elements (an imageView and a button) to be vertically scrollable like the example.
What should I do? I have checked UIScrollView, but still don't know how to do it. Can you show me some code? Thanks!
There is an UI Component inside UIKit called UIScrollView. This Component can be used to achieve the Scrolling Behaviour you probably aim for.
Check out the official Documentation by Apple
UIScrollView | Apple Developer Documentation
Another great resource for getting started with UIScrollViews is this one.
UIScrollView Tutorial: Getting Started
One way to do this is programmatically create an UIScrollView in your UIViewController.
To control the scrollability you can set the ScrollView contentSize property. In the following sample we match contentSize.width with self.view.frame.size.width to prevent horizontal scroll.
So, to achieve your goal, you must add your components inside the scrollView using scrollView.addSubview
This is a simple sample of a UIScrollView with a red UIView inside:
import UIKit
class MyViewController: UIViewController, UIScrollViewDelegate {
lazy var scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.translatesAutoresizingMaskIntoConstraints = false
scroll.delegate = self
scroll.contentSize = CGSize(width: self.view.frame.size.width, height: 1000)
return scroll
}()
lazy var redView:UIView = {
let redview = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
redview.backgroundColor = .red
return redview
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(redView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let layout = view.safeAreaLayoutGuide
scrollView.centerXAnchor.constraint(equalTo: layout.centerXAnchor).isActive = true
scrollView.centerYAnchor.constraint(equalTo: layout.centerYAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: layout.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: layout.heightAnchor).isActive = true
}
}

UIScrollView inside another view not scrolling

So I've seen other SO questions but I can't really figure out mine
I know my UIScrollView code works because I tested it in a regular UIViewController so I don't think contentSize is the problem, but when I place the same code inside a UICollectionReusableView it doesn't scroll anymore
-Working in UIViewController
override func viewDidLoad() {
super.viewDidLoad()
let scrollingView = colorButtonsView(buttonSize: CGSize(width:100.0,height:50.0), buttonCount: 10)
let scrollView = UIScrollView(frame: UIScreen.main.bounds)
scrollView.backgroundColor = UIColor.black
scrollView.contentSize = scrollingView.frame.size
scrollView.showsHorizontalScrollIndicator = true
self.view.addSubview(scrollView)
scrollView.addSubview(scrollingView)
}
-Not working in UICollectionReusableView
override init(frame: CGRect) {
super.init(frame: frame)
//Add category scroll view
let scrollingView = colorButtonsView(buttonSize: CGSize(width:100.0,height:50.0), buttonCount: 10)
let scrollView = UIScrollView(frame: UIScreen.main.bounds)
scrollView.backgroundColor = UIColor.black
scrollView.contentSize = scrollingView.frame.size
scrollView.showsHorizontalScrollIndicator = true
self.addSubview(scrollView) //Only changes self.view to self
scrollView.addSubview(scrollingView)
}
Better late than never...I had the same problem, so maybe this is why;
I wanted to put a UIScrollView inside a UICollectionReusableView but by accident I had created my custom class as;
class CustomCellHeader: UICollectionViewCell {
}
instead of;
class CustomCellHeader: UICollectionReusableView {
}
Once I changed that, the UIScrollView within my header cell came to life!

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)

Animate headerView of TableView while pulling down UITableView in swift

I am trying to create a growing UITableViewHeader on UITableView. I have a UITableView and a mapView set in the tableHeaderView of UITableView.
tblView.bounces = true
tblView.bouncesZoom = true
tblView.alwaysBounceVertical = true
mapView.frame = CGRectMake(0, 0, self.view.frame.size.width, CGFloat(kMapHeaderHeight))
mapView.mapType = MKMapType.Standard
mapView.zoomEnabled=true
mapView.scrollEnabled = true
mapView.delegate = mapHelper
tblView.tableHeaderView = mapView
And also implemented scrollViewDidScroll delegate and whenever it scrolls down, I have changed the frame of headerview as
func scrollViewDidScroll(scrollView: UIScrollView) {
var scrollOffset = scrollView.contentOffset.y
println("\(scrollOffset)")
var headerFrame : CGRect = self.mapView.frame
if (scrollOffset < 0){
headerFrame.size.height -= scrollView.contentOffset.y/3
}
self.mapView.frame = headerFrame
}
However, it does not grow as expected without bouncing.Seems very unclear. Any help?
I am following these tutorials to create a Growing UITableViewheader when pulling down as
UITableVIew header without bouncing when pull down ,
Expand UITableView Header View to Bounce Area When Pulling Down
Here is the link of the project :
https://drive.google.com/file/d/0B6dTvD1JbkgBVENUS1ROMzI0Wnc/vie
EDITED: i somehow managed to have the effect but the animation seems very slow
func scrollViewDidScroll(scrollView: UIScrollView) {
let yPos: CGFloat = -scrollView.contentOffset.y
if (yPos > 0) {
var mapViewRect: CGRect = self.mapView.frame
mapViewRect.origin.y = scrollView.contentOffset.y
mapViewRect.size.height = kHeaderHeight+yPos
self.mapView.frame = mapViewRect
}
}
let kHeaderHeight:CGFloat = 200
I suggest you to use this tutorial.
The most important parts of it:
Create a scrollView (or whatever that is a subclass of a UIScrollView) and a separate view (which will be functioning as a headerView, so let's call if headerView)
add the headerView and the scrollView as a subView of your view
implement the scrollViewDidScroll method, and put the framing logic there (of course, if you're using autolayout, you have to manage constraints there)
Actually the animation was not working well in simulator of xcode6.3. I tried 2 days for this and posted a bounty here but when i finally I tested it on real device and found the MapView was properly bouncing.If anyone needs it..here is the piece of logic.
let kHeaderHeight:CGFloat = 380
class NewBookingVC: UIViewController {
#IBOutlet weak var tblView: UITableView!
let mapView : MKMapView = MKMapView()
var customTableHeaderView:UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
tblView.delegate = self
tblView.dataSource = self
mapView.frame = CGRectMake(0, 0, self.view.frame.size.width, 380)
mapView.mapType = MKMapType.Standard
mapView.zoomEnabled=true
mapView.scrollEnabled = true
customTableHeaderView = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 380))
customTableHeaderView.addSubview(mapView)
tblView.tableHeaderView = customTableHeaderView
}
func scrollViewDidScroll(scrollView: UIScrollView) {
let yPos: CGFloat = -scrollView.contentOffset.y
if (yPos > 0) {
var mapViewRect: CGRect = self.mapView.frame
mapViewRect.origin.y = scrollView.contentOffset.y
mapViewRect.size.height = kHeaderHeight+yPos
self.mapView.frame = mapViewRect
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Actually what you have to do is implement the scrollview delegate for table view because it inherits from scrollview and you can implement scrollViewDidScroll delegate and whenever it scrolls down, change the frame of headerview.
Idea behind this while scrolling you always have to keep the y-position of the MKMapView at the zero position....and height should incerase accordingly...
func scrollViewDidScroll(scrollView: UIScrollView) {
let yPos: CGFloat = -scrollView.contentOffset.y
if (yPos > 0) {
//adjust your mapview y-pos and height
//adjusting new position is all about real math
}
}
Performance in the iOS Simulator is not expected to match performance on device. The iOS Simulator is meant as a tool for rapid prototyping and fast iteration.In your case too, redrawing of MapView performance is quite slow in the simulator because at each Scroll you are calculating its new frame and height. So it takes some time adjusting new frame and seems very slow.
However it works well while tuning on real devices.

UIScrollView and Page Control, Page Control Disrupting View

So, I know its been done many times before, but I am building a ImageViewer with a UIScrollView and a UIPageControl. I am building it in Swift. Yet I have come across a issue which I can't seem to diagnose. I have a interesting setup to minimize long pages of code by splitting a view into containers. One for a tableview, the other for paging images. Here is a image of the storyboard. http://imgur.com/ZqvNgjy So in the first container view, it is a simple UIViewController with a UIScrollView and a UIPageControl. I can confirm that the UIPageControl is not in the view hierarchy of the UIScrollView. Now here is the code I used to setup everything.
import UIKit
//Set Image Array
var imagelist: String[] = ["Home Image 1.png", "info.png"]
//Set Page Numbers
var numpages = imagelist.count
class HomeImageView: UIViewController, UIScrollViewDelegate {
//IBOutlets & Propertys
#IBOutlet var scrollView: UIScrollView
#IBOutlet var pageControl: UIPageControl
override func viewDidLoad() {
super.viewDidLoad()
//Setup Page Control
pageControl.numberOfPages = numpages
pageControl.currentPage = 0
//Setup Scroll View
scrollView.pagingEnabled = true;
scrollView.scrollEnabled = true
scrollView.showsHorizontalScrollIndicator = false;
scrollView.showsVerticalScrollIndicator = false;
scrollView.bounces = true;
scrollView.scrollsToTop = false
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * CGFloat(numpages), scrollView.frame.size.height)
}
override func viewWillAppear(animated: Bool) {
//Setup UIViews with Images
for var z = 0; z < numpages; z++ {
let mainFrame: CGRect = CGRectMake(scrollView.frame.size.width * CGFloat(z), 0, scrollView.frame.size.width, scrollView.frame.size.height)
var image: UIImageView = UIImageView(frame: mainFrame)
image.image = UIImage(named: imagelist[z])
image.clipsToBounds = true
image.contentMode = UIViewContentMode.ScaleAspectFit
scrollView.addSubview(image)
}
func scrollViewDidScroll(scrollview: UIScrollView) {
var pageSize: Float = Float(scrollView.bounds.size.width)
var page: Float = floorf((Float(scrollview.contentOffset.x) - pageSize / 2.0) / pageSize) + 1
// Bound page limits
if (page >= Float(numpages)) {
page = Float(numpages) - 1;
} else if (page < 0) {
page = 0;
}
pageControl.currentPage = Int(page)
println(pageControl.currentPage)
}
}
}
So with that out of the way, here is the problem. When I run it, it does not do exactly what I expected. The simulator starts like this http://imgur.com/RVlKxdf and I can move the view around outside the bounds of the image.. Yet when I change the view hierarchy so that UIPageControl is behind the UIScrollView (Basically Removing it). The problem disappears completely. Aligned and allowing movement only in the bounds like I want. I don't know why the UIScrollView would react to the UIPageControl when it is outside its view hierarchy. Thanks for any assistance.

Resources