Paging view with control

Before talking about boring(no it’s absolutely not) programming, some babbles about my life. I’m going back to America tomorrow and will arrive at midnight. Let’s hope I won’t get much jet lag. As for the semester, I kind of need to learn to drive and get a license so life won’t suck. I’m taking a pretty hard algorithm class and at the same time having two jobs so wish me luck!

In the project StoreSearch, I used scroll view and UIPageControl to implement a paging view with control once, but what did I know I just mindlessly followed the detailed tutorial. Thankfully, I used the money granted by Duke CoLab to subscribe for online tutorials on Ray Wenderlich’s website and learned again about paging view there.

There are two ways to do this: one is using UIScrollView and set constraints on pages manually, the other is taking advantage of UIPageViewController and have things set up. I personally prefer the former because it’s more customizable, so selfishly I’ll write about that here.

First, we should have a view controller set up. This controller takes care of each page in our paging view. Then in the initial view controller put in a UIScrollViewController and set proper constraints.

Screen Shot 2016-01-09 at 8.20.46 PM

Next we accomplish paging through the following steps:

  1. set pagingEnabled to true:
    scrollView.pagingEnabled = true
  2. keep view controllers in a dictionary so that we can set constraints later:
    pages = [page1, page2, page3, page4, page5]
            let views = ["view": view, "page1": page1.view, "page2": page2.view, "page3": page3.view, "page4": page4.view, "page5": page5.view]
    /*"pageX" means the Xth view controller.*/
  3. set constraints on views:
    let metrics = ["edgeMargin": 10, "betweenMargin": 20]
     
    let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|[page1(==view)]|", options: [], metrics: nil, views: views)
    NSLayoutConstraint.activateConstraints(verticalConstraints)
     
    let horizontalConstraints =
        NSLayoutConstraint.constraintsWithVisualFormat("H:|-edgeMargin-[page1(==view)]-betweenMargin-[page2(==view)]-betweenMargin-[page3(==view)]-betweenMargin-[page4(==view)]-betweenMargin-[page5(==view)]-edgeMargin-|", options: [.AlignAllTop, .AlignAllBottom], metrics: metrics, views: views)
     
    NSLayoutConstraint.activateConstraints(horizontalConstraints)

Oh, before all these remember to set the view controllers for each page not to autoresize:

view.translatesAutoresizingMaskIntoConstraints = false

And voila we have a nice looking five pages view.

To add a page control, first add it in storyboard and set proper constraints and outlet:

Screen Shot 2016-01-09 at 8.21.30 PM

Then follow the steps below:

  1. set its number of pages:
    pageControl.numberOfPages = pages.count
  2. show the correct current page. We need to round the value of offset.x/width because we would like it to show the correct page when we are in between pages:
    extension TutorialViewController: UIScrollViewDelegate {
        func scrollViewDidScroll(scrollView: UIScrollView) {
            let pageWidth = CGRectGetWidth(scrollView.bounds)
            let pageFraction = scrollView.contentOffset.x / pageWidth
            pageControl.currentPage = Int(round(pageFraction))
        }
    }
  3. control the change of page with an action outlet:
    @IBAction func pageChanged(sender: AnyObject) {
            let currentPage = sender.currentPage
            let pageWidth = CGRectGetWidth(scrollView.bounds)
            let targetContentOffsetX = CGFloat(currentPage) * pageWidth
     
            UIView.animateWithDuration(0.33, delay: 0,
                options: .CurveEaseInOut, animations: { () -> Void in
                    self.scrollView.contentOffset.x = targetContentOffsetX
                }, completion: nil)
        }

    Here we’re using a cute little animation.

And there we go! Paging view with control. I uploaded the code to GitHub in case I need the template in future development. If one intends to use it be aware of its license.

Elementary Layer Animations

Today I learned that apart from UIView animations provided by UIKit, we can also use Layer Animations integrated inside any ViewController. Layer animation is also faster in performance because it is relatively “lower-level”.

For an animation, if we use UIView, the code would be like:

UIView.animateWithDuration(0.5, delay: 0.5, options: [], animations: {
    // something about UI change
}, completion: nil)

One little problem with this method is that we have to specify the UI modification in the animation closure. If we have ten different views we would like to animate, we would have to cut and paste this chunk for at least ten times. Obviously we would like to reuse code, so layer animation is the way to go:

animationName = CABasicAnimation(keyPath:"some property")
animationName.fromValue = 0.0
animationName.toValue = 1.0
animationName.duration = 5.0
object.layer.addAnimation(animationName, forKey: “key”)
animationName.setValue(object.layer, forKey: “layer”)

The above code does the same thing, but notice that it does not specify the target layer. We could simply modify fromValue or toValue and add it to another layer and it would work.

This is not all. If we set the delegate of a layer animation object to self (every NSObject already implements the delegate methods) and overrides these two functions:

func animationDidStart(anim: CAAnimation )
func animationDidStop(anim: CAAnimation , finished flag: Bool )

We would be able to monitor the status of layer animations. Neat.

Elsewhere in the class, we could use the keys that we set earlier to get either the animation object or the layer. I’ll save the code here.

Now that we have fromValue, toValue and duration, we would also like to set the delay time of our animations. Again, this is pretty easy:

animationName.beginTime = CACurrentMediaTime() + 1.0

Note that the beginTime is relative to the fixed currentMediaTime(), not that the animation is delayed by the time after the former animation.

Animations can also be grouped together with CAAnimationGroup:

let group = CAAnimationGroup()
group.animations = [animation1, animation2, animation3]

Then it is convenient to set the common properties of animations to the group.

That’s pretty much it.