如何使用swift垂直遍历页面视图控制器

时间:2017-08-10 09:39:11

标签: ios swift xcode storyboard uipageviewcontroller

我想使用垂直导航在我的应用程序中使用漫游动画,使用此tutorial中的以下代码我可以水平导航。我想知道我应该对垂直穿行做出哪些改变。有人可以帮忙吗?提前谢谢。

BWWalkthroughPageViewController.swift

import UIKit

public enum WalkthroughAnimationType:String{
    case Linear = "Linear"
    case Curve = "Curve"
    case Zoom = "Zoom"
    case InOut = "InOut"

    init(_ name:String){

        if let tempSelf = WalkthroughAnimationType(rawValue: name){
            self = tempSelf
        }else{
            self = .Linear
        }
    }
}

open class BWWalkthroughPageViewController: UIViewController, BWWalkthroughPage {

    fileprivate var animation:WalkthroughAnimationType = .Linear
    fileprivate var subsWeights:[CGPoint] = Array()
    fileprivate var notAnimatableViews:[Int] = [] // Array of views' tags that should not be animated during the scroll/transition

    // MARK: Inspectable Properties
    // Edit these values using the Attribute inspector or modify directly the "User defined runtime attributes" in IB
    @IBInspectable var speed:CGPoint = CGPoint(x: 0.0, y: 0.0);            // Note if you set this value via Attribute inspector it can only be an Integer (change it manually via User defined runtime attribute if you need a Float)
    @IBInspectable var speedVariance:CGPoint = CGPoint(x: 0.0, y: 0.0)     // Note if you set this value via Attribute inspector it can only be an Integer (change it manually via User defined runtime attribute if you need a Float)
    @IBInspectable var animationType:String {
        set(value){
            self.animation = WalkthroughAnimationType(rawValue: value)!
        }
        get{
            return self.animation.rawValue
        }
    }
    @IBInspectable var animateAlpha:Bool = false
    @IBInspectable var staticTags:String {                                 // A comma separated list of tags that you don't want to animate during the transition/scroll 
        set(value){
            self.notAnimatableViews = value.components(separatedBy: ",").map{Int($0)!}
        }
        get{
            return notAnimatableViews.map{String($0)}.joined(separator: ",")
        }
    }

    // MARK: BWWalkthroughPage Implementation

    override open func viewDidLoad() {
        super.viewDidLoad()
        self.view.layer.masksToBounds = true
        subsWeights = Array()

        for v in view.subviews{
            speed.x += speedVariance.x
            speed.y += speedVariance.y
            if !notAnimatableViews.contains(v.tag) {
                subsWeights.append(speed)
            }
        }

    }

    open func walkthroughDidScroll(_ position: CGFloat, offset: CGFloat) {

        for i in 0 ..< subsWeights.count
        {

            // Perform Transition/Scale/Rotate animations
            switch animation
            {

            case .Linear:
                animationLinear(i, offset)

            case .Zoom:
                animationZoom(i, offset)

            case .Curve:
                animationCurve(i, offset)

            case .InOut:
                animationInOut(i, offset)
            }

            // Animate alpha
            if(animateAlpha)
            {
                animationAlpha(i, offset)
            }
        }
    }


    // MARK: Animations (WIP)

    private func animationAlpha(_ index:Int, _ offset:CGFloat){
        var offset = offset
        let cView = view.subviews[index] 

        if(offset > 1.0){
            offset = 1.0 + (1.0 - offset)
        }
        cView.alpha = (offset)
    }

    fileprivate func animationCurve(_ index:Int, _ offset:CGFloat){
        var transform = CATransform3DIdentity
        let x:CGFloat = (1.0 - offset) * 10
        transform = CATransform3DTranslate(transform, (pow(x,3) - (x * 25)) * subsWeights[index].x, (pow(x,3) - (x * 20)) * subsWeights[index].y, 0 )
        applyTransform(index, transform: transform)
    }

    fileprivate func animationZoom(_ index:Int, _ offset:CGFloat){
        var transform = CATransform3DIdentity

        var tmpOffset = offset
        if(tmpOffset > 1.0){
            tmpOffset = 1.0 + (1.0 - tmpOffset)
        }
        let scale:CGFloat = (1.0 - tmpOffset)
        transform = CATransform3DScale(transform, 1 - scale , 1 - scale, 1.0)
        applyTransform(index, transform: transform)
    }

    fileprivate func animationLinear(_ index:Int, _ offset:CGFloat){
        var transform = CATransform3DIdentity
        let mx:CGFloat = (1.0 - offset) * 100
        transform = CATransform3DTranslate(transform, mx * subsWeights[index].x, mx * subsWeights[index].y, 0 )
        applyTransform(index, transform: transform)
    }

    fileprivate func animationInOut(_ index:Int, _ offset:CGFloat){
        var transform = CATransform3DIdentity
        //var x:CGFloat = (1.0 - offset) * 20

        var tmpOffset = offset
        if(tmpOffset > 1.0){
            tmpOffset = 1.0 + (1.0 - tmpOffset)
        }
        transform = CATransform3DTranslate(transform, (1.0 - tmpOffset) * subsWeights[index].x * 100, (1.0 - tmpOffset) * subsWeights[index].y * 100, 0)
        applyTransform(index, transform: transform)
    }

    fileprivate func applyTransform(_ index:Int, transform:CATransform3D){
        let subview = view.subviews[index]
        if !notAnimatableViews.contains(subview.tag){
            view.subviews[index].layer.transform = transform
        }
    }

}

BWWalkthroughViewController.swift:

import UIKit

// MARK: - Protocols -

/**
Walkthrough Delegate:
This delegate performs basic operations such as dismissing the Walkthrough or call whatever action on page change.
Probably the Walkthrough is presented by this delegate.
**/

@objc public protocol BWWalkthroughViewControllerDelegate{

    @objc optional func walkthroughCloseButtonPressed()              // If the skipRequest(sender:) action is connected to a button, this function is called when that button is pressed.
    @objc optional func walkthroughNextButtonPressed()               //
    @objc optional func walkthroughPrevButtonPressed()               //
    @objc optional func walkthroughPageDidChange(_ pageNumber:Int)     // Called when current page changes

}

/** 
Walkthrough Page:
The walkthrough page represents any page added to the Walkthrough.
At the moment it's only used to perform custom animations on didScroll.
**/
@objc public protocol BWWalkthroughPage
{
    // While sliding to the "next" slide (from right to left), the "current" slide changes its offset from 1.0 to 2.0 while the "next" slide changes it from 0.0 to 1.0
    // While sliding to the "previous" slide (left to right), the current slide changes its offset from 1.0 to 0.0 while the "previous" slide changes it from 2.0 to 1.0
    // The other pages update their offsets whith values like 2.0, 3.0, -2.0... depending on their positions and on the status of the walkthrough
    // This value can be used on the previous, current and next page to perform custom animations on page's subviews.

    @objc func walkthroughDidScroll(_ position:CGFloat, offset:CGFloat)   // Called when the main Scrollview...scrolls
}


@objc open class BWWalkthroughViewController: UIViewController, UIScrollViewDelegate{

    // MARK: - Public properties -

    weak open var delegate:BWWalkthroughViewControllerDelegate?

    // TODO: If you need a page control, next or prev buttons add them via IB and connect them with these Outlets
    @IBOutlet open var pageControl:UIPageControl?
    @IBOutlet open var nextButton:UIButton?
    @IBOutlet open var prevButton:UIButton?
    @IBOutlet open var closeButton:UIButton?

    open var currentPage:Int{    // The index of the current page (readonly)
        get{
            let page = Int((scrollview.contentOffset.x / view.bounds.size.width))
            return page
        }
    }

    open var currentViewController:UIViewController{ //the controller for the currently visible page
        get{
            let currentPage = self.currentPage;
            return controllers[currentPage];
        }
    }

    open var numberOfPages:Int{ //the total number of pages in the walkthrough
        get{
            return self.controllers.count;
        }
    }


    // MARK: - Private properties -

    open let scrollview:UIScrollView!
    fileprivate var controllers:[UIViewController]!
    fileprivate var lastViewConstraint:NSArray?


    // MARK: - Overrides -

    required public init?(coder aDecoder: NSCoder) {
        // Setup the scrollview
        scrollview = UIScrollView()
        scrollview.showsHorizontalScrollIndicator = false
        scrollview.showsVerticalScrollIndicator = false
        scrollview.isPagingEnabled = true

        // Controllers as empty array
        controllers = Array()

        super.init(coder: aDecoder)
    }

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?){
        scrollview = UIScrollView()
        controllers = Array()
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    override open func viewDidLoad() {
        super.viewDidLoad()

        // Initialize UI Elements

        pageControl?.addTarget(self, action: #selector(BWWalkthroughViewController.pageControlDidTouch), for: UIControlEvents.touchUpInside)


        // Scrollview

        scrollview.delegate = self
        scrollview.translatesAutoresizingMaskIntoConstraints = false

        view.insertSubview(scrollview, at: 0) //scrollview is inserted as first view of the hierarchy

        // Set scrollview related constraints

        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[scrollview]-0-|", options:[], metrics: nil, views: ["scrollview":scrollview]))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[scrollview]-0-|", options:[], metrics: nil, views: ["scrollview":scrollview]))

    }

    override open func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated);

        pageControl?.numberOfPages = controllers.count
        pageControl?.currentPage = 0
    }


    // MARK: - Internal methods -

    /**
     * Progresses to the next page, or calls the finished delegate method if already on the last page
     */
    @IBAction open func nextPage(){
        if (currentPage + 1) < controllers.count {

            delegate?.walkthroughNextButtonPressed?()
            gotoPage(currentPage + 1)
        }
    }

    @IBAction open func prevPage(){

        if currentPage > 0 {

            delegate?.walkthroughPrevButtonPressed?()
            gotoPage(currentPage - 1)
        }
    }

    // TODO: If you want to implement a "skip" button
    // connect the button to this IBAction and implement the delegate with the skipWalkthrough
    @IBAction open func close(_ sender: AnyObject){
        delegate?.walkthroughCloseButtonPressed?()
    }

    func pageControlDidTouch(){

        if let pc = pageControl{
            gotoPage(pc.currentPage)
        }
    }

    fileprivate func gotoPage(_ page:Int){

        if page < controllers.count{
            var frame = scrollview.frame
            frame.origin.x = CGFloat(page) * frame.size.width
            scrollview.scrollRectToVisible(frame, animated: true)
        }
    }

    /**
    addViewController
    Add a new page to the walkthrough. 
    To have information about the current position of the page in the walkthrough add a UIVIewController which implements BWWalkthroughPage    
    */
    open func addViewController(_ vc:UIViewController)->Void{

        controllers.append(vc)

        // Setup the viewController view

        vc.view.translatesAutoresizingMaskIntoConstraints = false
        scrollview.addSubview(vc.view)

        // Constraints

        let metricDict = ["w":vc.view.bounds.size.width,"h":vc.view.bounds.size.height]

        // - Generic cnst

        vc.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[view(h)]", options:[], metrics: metricDict, views: ["view":vc.view]))
        vc.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[view(w)]", options:[], metrics: metricDict, views: ["view":vc.view]))
        scrollview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[view]|", options:[], metrics: nil, views: ["view":vc.view,]))

        // cnst for position: 1st element

        if controllers.count == 1{
            scrollview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[view]", options:[], metrics: nil, views: ["view":vc.view,]))

            // cnst for position: other elements

        }else{

            let previousVC = controllers[controllers.count-2]
            let previousView = previousVC.view;

            scrollview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[previousView]-0-[view]", options:[], metrics: nil, views: ["previousView":previousView!,"view":vc.view]))

            if let cst = lastViewConstraint{
                scrollview.removeConstraints(cst as! [NSLayoutConstraint])
            }
            lastViewConstraint = NSLayoutConstraint.constraints(withVisualFormat: "H:[view]-0-|", options:[], metrics: nil, views: ["view":vc.view]) as NSArray
            scrollview.addConstraints(lastViewConstraint! as! [NSLayoutConstraint])
        }
    }

    /** 
    Update the UI to reflect the current walkthrough status
    **/

    fileprivate func updateUI(){

        // Get the current page

        pageControl?.currentPage = currentPage

        // Notify delegate about the new page

        delegate?.walkthroughPageDidChange?(currentPage)

        // Hide/Show navigation buttons

        if currentPage == controllers.count - 1{
            nextButton?.isHidden = true
        }else{
            nextButton?.isHidden = false
        }

        if currentPage == 0{
            prevButton?.isHidden = true
        }else{
            prevButton?.isHidden = false
        }
    }

    // MARK: - Scrollview Delegate -

    open func scrollViewDidScroll(_ sv: UIScrollView) {

        for i in 0 ..< controllers.count {

            if let vc = controllers[i] as? BWWalkthroughPage{

                let mx = ((scrollview.contentOffset.x + view.bounds.size.width) - (view.bounds.size.width * CGFloat(i))) / view.bounds.size.width

                // While sliding to the "next" slide (from right to left), the "current" slide changes its offset from 1.0 to 2.0 while the "next" slide changes it from 0.0 to 1.0
                // While sliding to the "previous" slide (left to right), the current slide changes its offset from 1.0 to 0.0 while the "previous" slide changes it from 2.0 to 1.0
                // The other pages update their offsets whith values like 2.0, 3.0, -2.0... depending on their positions and on the status of the walkthrough
                // This value can be used on the previous, current and next page to perform custom animations on page's subviews.

                // print the mx value to get more info.
                // println("\(i):\(mx)")

                // We animate only the previous, current and next page
                if(mx < 2 && mx > -2.0){
                    vc.walkthroughDidScroll(scrollview.contentOffset.x, offset: mx)
                }
            }
        }
    }

    open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        updateUI()
    }

    open func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        updateUI()
    }

    /* WIP */
    override open func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        print("CHANGE")
    }

    override open func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        print("SIZE")
    }

}

0 个答案:

没有答案