美文网首页
实现 iOS PageMenu 滑动或者点击切换 ViewCon

实现 iOS PageMenu 滑动或者点击切换 ViewCon

作者: 阿朱先生 | 来源:发表于2017-07-06 18:28 被阅读0次

    本文会实现一个轻量级的 PageMenu, 用户可以点击按钮或者滑动视图切换各个 ViewController,点击的按钮会放在 titleView 内。导入到项目的时候只需要添加一个类就OK了。

    ScrollView

    我们知道 ScrollView 有一个属性 contentSize,它表示的是 ScrollView 的内容可滚动区域,在实现的时候,如果滚动视图有 n 个, 则可设置:

    scrollView.contentSize = CGSize(width: scrollView.bounds.width * n, height: scrollView.bounds.height)
    

    由于切换各个视图的时候需要一个分页的动画效果,所以需要设置 ScrollView

     scrollView.isPagingEnabled = true
    

    此时,假如都设置好了的话,就可以在各个视图来回滑动切换了,就是这么简单!

    点击切换按钮

    点击切换按钮的时候,ScrollView 需要滚动到相对应的视图,此时需要修改的是 ScrollView 的 contentOffset 属性:

    scrollView.setContentOffset(viewControllersFrame[index].origin, animated: true)
    

    提示动画条

    当用户滑动到第 n 个界面的时候,此时提示条应该移动到第 n 个界面对应的点击按钮

    此时需要在 滑动视图点击按钮 时做相应的处理

    • 滑动视图时的处理:

    在 ScrollView 的代理方法内,根据对应按钮的 frame 比例,执行滚动动画

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
       UIView.animate(withDuration: moveDuration, animations: {
           let x = scrollView.contentOffset.x * self.scale + self.itemsOriginX[0]
           self.indicatorView.frame.origin.x = x
         })
    }
    
    • 点击按钮时的处理

    在点击按钮添加的方法内,添加滚动动画:

    UIView.animate(withDuration: moveDuration, animations: {
        self.indicatorView.frame.origin.x = self.itemsOriginX[index]
     })
    

    我的实现类:

    最终实现的滚动容器类代码如下:

    import UIKit
    
    class MenuContainerViewController: UIViewController {
        
        var menus: [UIButton] = [UIButton]()
        var viewControllers: [UIViewController] = [UIViewController]()
    
        var itemColor = UIColor.black
        var indicatorColor = UIColor.blue {
            didSet {
                indicatorView?.backgroundColor = indicatorColor
            }
        }
        
        // designated init view controller
        init(menus: [UIButton], viewControllers: [UIViewController]) {
            super.init(nibName: nil, bundle: nil)
            
            if menus.count != viewControllers.count {
                fatalError("menus.count != viewControllers.count")
            } 
            
            self.menus = menus
            self.viewControllers = viewControllers
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        // MARK: - Private properties
        private var scrollView: UIScrollView!
    
        private var itemsTitle: [String] = [String]()
        private var viewControllersFrame: [CGRect] = [CGRect]()
        fileprivate var itemsOriginX: [CGFloat] = [CGFloat]()
        
        fileprivate var indicatorView: UIView!
        
        fileprivate var indicatorViewLastOriginX: CGFloat = 0.0 {
            didSet {
                indicatorCopyView?.frame.origin.x = indicatorViewLastOriginX
            }
        }
        
        fileprivate let indicatorViewWidth: CGFloat = 30
        
        fileprivate var scale: CGFloat!
        
        fileprivate let moveDuration: TimeInterval = 0.2
        
        // Due to 'sectionIndicatorView' will reset frame when viewDidDisappear did called,
        // so, add 'indicatorCopyView' as the copy view
        fileprivate var indicatorCopyView: UIView!
        fileprivate var shouldAdjustCopyIndicatorView = false
        
        // MARK: - View controller lifecycle
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            automaticallyAdjustsScrollViewInsets = false
            self.navigationController?.navigationBar.isTranslucent = false
            
            scrollView = UIScrollView()
            let customTitleView = UIView()
            let titleStackView = UIStackView()
            indicatorView = UIView()
            indicatorCopyView = UIView()
            
            scrollView.delegate = self
            scrollView.isPagingEnabled = true
            scrollView.showsVerticalScrollIndicator = false
            scrollView.showsHorizontalScrollIndicator = false
            
            for button in menus {
                button.setTitleColor(itemColor, for: .normal)
                itemsTitle.append(button.currentTitle!)
            }
            
            customTitleView.backgroundColor = UIColor.white
            for item in menus {
                titleStackView.addArrangedSubview(item)
            }
            titleStackView.alignment = .center
            titleStackView.axis = .horizontal
            titleStackView.distribution = .fillEqually
            
            for i in 0 ..< viewControllers.count {
                let subvc = viewControllers[i]
                self.addChildViewController(subvc)
                scrollView.addSubview(subvc.view)
                subvc.didMove(toParentViewController: self)
            }
            
            let titleViewWidth: CGFloat = 200
            let titleViewHeight: CGFloat = 44
            let stackViewHeight: CGFloat = 40
            
            let titleViewFrame = CGRect(x: 0, y: 0, width: titleViewWidth, height: titleViewHeight)
            let stackViewFrame = CGRect(x: 0, y: 0, width: titleViewWidth, height: stackViewHeight)
            let indicatorViewFrame = CGRect(x: 0, y: titleViewHeight - 2, width: indicatorViewWidth, height: 1)
            
            customTitleView.frame = titleViewFrame
            customTitleView.frame.origin.x = self.view.frame.midX - titleViewWidth/2
            
            titleStackView.frame = stackViewFrame
            
            indicatorView.frame = indicatorViewFrame
            indicatorView.backgroundColor = indicatorColor
            
            // for menuItems originX
            var itemOriginX: CGFloat = 0
            let itemWidth: CGFloat = titleViewWidth/3
            for item in menus {
                item.addTarget(self, action: #selector(contentOffSetXForButton(sender:)), for: .touchUpInside)
                let itemFrame = CGRect(x: itemOriginX, y: 0, width: itemWidth, height: stackViewHeight)
                item.frame = itemFrame
                let indicatorOriginX = itemFrame.midX - indicatorViewWidth/2
                itemsOriginX.append(indicatorOriginX)
                itemOriginX += itemWidth
            }
            
            // for sectionIndicatorView
            indicatorView.frame.origin.x = itemsOriginX[0]
            indicatorViewLastOriginX = indicatorView.frame.origin.x
            
            // indicator copy view
            indicatorCopyView.frame = indicatorView.frame
            indicatorCopyView.backgroundColor = indicatorView.backgroundColor
            indicatorCopyView.isHidden = true
            
            // indicator scroll scale
            let indicatorScale = itemsOriginX[1] - itemsOriginX[0]
            scale = indicatorScale / UIScreen.main.bounds.size.width
            
            customTitleView.addSubview(titleStackView)
            customTitleView.addSubview(indicatorView)
            customTitleView.addSubview(indicatorCopyView)
            
            self.parent?.navigationItem.titleView = customTitleView
            
            view.addSubview(scrollView)
        }
        
        
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
            
            var scrollViewFrame = view.frame
            scrollViewFrame.size.height -= 49
            
            scrollView.frame = view.frame
            
            let width = scrollViewFrame.width
            let height = scrollViewFrame.height
            
            scrollView.contentSize = CGSize(width: width * 3, height: height)
            
            // has [viewControllersFrame]
            var vcOriginX: CGFloat = 0
            for _ in 0 ..< viewControllers.count {
                viewControllersFrame.append(CGRect(x: vcOriginX, y: 0, width: width, height: height))
                vcOriginX += width
            }
            
            for i in 0 ..< viewControllers.count {
                viewControllers[i].view.frame = viewControllersFrame[i]
            }
        }
        
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            
            if shouldAdjustCopyIndicatorView {
                UIView.animate(withDuration: 0.0, animations: {
                    self.indicatorView?.frame.origin.x = self.indicatorViewLastOriginX
                }) { (_) in
                    self.indicatorCopyView?.isHidden = true
                    self.indicatorView?.isHidden = false
                    
                    self.shouldAdjustCopyIndicatorView = false
                }
            }
        }
        
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            
            indicatorCopyView.isHidden = false
            indicatorView.isHidden = true
            shouldAdjustCopyIndicatorView = true        
        }
        
        // MARK: - Helper
        @objc private func contentOffSetXForButton(sender: UIButton){
            let currentTitle = sender.currentTitle!
            let index = itemsTitle.index(of: currentTitle)!
            
            scrollView.setContentOffset(viewControllersFrame[index].origin, animated: true)
            UIView.animate(withDuration: moveDuration, animations: {
                self.indicatorView.frame.origin.x = self.itemsOriginX[index]
                self.indicatorViewLastOriginX = self.indicatorView.frame.origin.x
            })
        }
    }
    
    // MRAK: - Scroll view delegate
    
    extension MenuContainerViewController: UIScrollViewDelegate {
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            UIView.animate(withDuration: moveDuration, animations: {
                let x = scrollView.contentOffset.x * self.scale + self.itemsOriginX[0]
                self.indicatorView.frame.origin.x = x
                self.indicatorViewLastOriginX = x
            })
        }
    }
    

    调用的测试类:

    import UIKit
    
    class ViewController: UIViewController {
        
        // *********************************************
        //  Add MenuContainerViewController like this
        private var addedPageViewController = false
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            if !addedPageViewController {
                addedPageViewController = true
                
                let scrollContainerVC = MenuContainerViewController(menus: pageItems(),
                                                                    viewControllers: pageViewContorllers())
                self.addChildViewController(scrollContainerVC)
                scrollContainerVC.view.frame = view.bounds
                view.addSubview(scrollContainerVC.view)
                scrollContainerVC.didMove(toParentViewController: self)
            }
        }
        // *********************************************
        
        // MRAK: Test data
        private func pageItems() -> [UIButton] {        
            let red = UIButton()
            let gray = UIButton()
            let purple = UIButton()
            
            red.setTitle("red", for: .normal)
            gray.setTitle("gray", for: .normal)
            purple.setTitle("purple", for: .normal)
            
            var items = [UIButton]()
    
            items.append(red)
            items.append(gray)
            items.append(purple)
    
            return items
        }
        
        private func pageViewContorllers() -> [UIViewController] {        
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
    
            let firstViewController = storyboard.instantiateViewController(withIdentifier: "FirstViewController") as!
            FirstViewController
            let secondViewController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
            let thirdViewController = storyboard.instantiateViewController(withIdentifier: "ThirdViewController") as!
            ThirdViewController
            
            var vcs = [UIViewController]()
    
            vcs.append(firstViewController)
            vcs.append(secondViewController)
            vcs.append(thirdViewController)
    
            return vcs
        }
    
    }
    

    Demo地址

    相关文章

      网友评论

          本文标题:实现 iOS PageMenu 滑动或者点击切换 ViewCon

          本文链接:https://www.haomeiwen.com/subject/hrqkhxtx.html