Swift - 分页菜单的实现(使用PagingMenuController库实现tab标签切换)
作者:hangge | 2017-07-21 08:10
分页菜单(分段菜单)在许多 App 上都会用到。比如大多数新闻 App,如网易新闻、今日头条等,顶部都有个导航菜单。这个导航菜单是一组标签的集合,每个标签表示一个新闻类别,我们点击这个标签后下面就会切换到相应的分页面。同时左右滑动分页面,上方的标签也会跟着移动。
本文介绍一个优秀的第三方分页视图控件:PagingMenuController,不仅可以自定义菜单,而且可以自由地修改样式。
一、安装配置
(1)从 GitHub 上下载最新的代码:https://github.com/kitasuke/PagingMenuController
(2)将下载下来的源码包中 PagingMenuController.xcodeproj 拖拽至你的工程中。
(3)工程 -> General -> Embedded Binaries 项,把 PagingMenuController.framework 添加进来。
(4)最后,在需要使用 PagingMenuController 的地方 import 进来就可以了。
import PagingMenuController
二、纯代码使用样例
1,效果图
(1)主视图顶部分页菜单中有两个菜单标签,分别对应两个子视图。
(2)点击菜单标签,下方便会切换显示相应的子视图。
(3)也可以直接左右滑动子视图进行切换,上方的菜单标签状态也会同步更新。
2,样例代码
(1)子视图控制器1(ViewController1.swift)
import UIKit //子视图控制器1 class ViewController1: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.orange let textLabel = UILabel(frame: CGRect(x: 0, y: 100, width: self.view.frame.width, height: 30)) textLabel.textAlignment = .center textLabel.font = UIFont.systemFont(ofSize: 33) textLabel.textColor = .white textLabel.text = "电影" view.addSubview(textLabel) } }
(2)子视图控制器2(ViewController2.swift)
(3)主视图控制器(ViewController.swift)
import UIKit //子视图控制器2 class ViewController2: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.darkGray let textLabel = UILabel(frame: CGRect(x: 0, y: 100, width: self.view.frame.width, height: 30)) textLabel.textAlignment = .center textLabel.font = UIFont.systemFont(ofSize: 33) textLabel.textColor = .white textLabel.text = "音乐" view.addSubview(textLabel) } }
(3)主视图控制器(ViewController.swift)
import UIKit import PagingMenuController //分页菜单配置 private struct PagingMenuOptions: PagingMenuControllerCustomizable { //第1个子视图控制器 private let viewController1 = ViewController1() //第2个子视图控制器 private let viewController2 = ViewController2() //组件类型 fileprivate var componentType: ComponentType { return .all(menuOptions: MenuOptions(), pagingControllers: pagingControllers) } //所有子视图控制器 fileprivate var pagingControllers: [UIViewController] { return [viewController1, viewController2] } //菜单配置项 fileprivate struct MenuOptions: MenuViewCustomizable { //菜单显示模式 var displayMode: MenuDisplayMode { return .segmentedControl } //菜单项 var itemsOptions: [MenuItemViewCustomizable] { return [MenuItem1(), MenuItem2()] } } //第1个菜单项 fileprivate struct MenuItem1: MenuItemViewCustomizable { //自定义菜单项名称 var displayMode: MenuItemDisplayMode { return .text(title: MenuItemText(text: "电影")) } } //第2个菜单项 fileprivate struct MenuItem2: MenuItemViewCustomizable { //自定义菜单项名称 var displayMode: MenuItemDisplayMode { return .text(title: MenuItemText(text: "音乐")) } } } //主视图控制器 class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() //分页菜单配置 let options = PagingMenuOptions() //分页菜单控制器初始化 let pagingMenuController = PagingMenuController(options: options) //分页菜单控制器尺寸设置 pagingMenuController.view.frame.origin.y += 64 pagingMenuController.view.frame.size.height -= 64 //建立父子关系 addChildViewController(pagingMenuController) //分页菜单控制器视图添加到当前视图中 view.addSubview(pagingMenuController.view) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }源码下载:hangge_1656.zip
三、Storyboard 使用样例
1,效果图
具体功能同上面的是一样的。
2,Storyboard 相关操作
(1)在主视图中添加一个 Container View,并设置好相关约束。(2)Container View 默认embed的是 UIViewController,我们将其改成 PagingMenuController。
3,样例代码
(1)子视图控制器(ViewController1.swift、ViewController2.swift)
//具体参考上面的纯代码实现部分
(2)主视图控制器(ViewController.swift)
高亮处表示与上面纯代码实现不相同的地方。
import UIKit import PagingMenuController //分页菜单配置 private struct PagingMenuOptions: PagingMenuControllerCustomizable { //第1个子视图控制器 private let viewController1 = ViewController1() //第2个子视图控制器 private let viewController2 = ViewController2() //组件类型 fileprivate var componentType: ComponentType { return .all(menuOptions: MenuOptions(), pagingControllers: pagingControllers) } //所有子视图控制器 fileprivate var pagingControllers: [UIViewController] { return [viewController1, viewController2] } //菜单配置项 fileprivate struct MenuOptions: MenuViewCustomizable { //菜单显示模式 var displayMode: MenuDisplayMode { return .segmentedControl } //菜单项 var itemsOptions: [MenuItemViewCustomizable] { return [MenuItem1(), MenuItem2()] } } //第1个菜单项 fileprivate struct MenuItem1: MenuItemViewCustomizable { //自定义菜单项名称 var displayMode: MenuItemDisplayMode { return .text(title: MenuItemText(text: "电影")) } } //第2个菜单项 fileprivate struct MenuItem2: MenuItemViewCustomizable { //自定义菜单项名称 var displayMode: MenuItemDisplayMode { return .text(title: MenuItemText(text: "音乐")) } } } //主视图控制器 class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() //获取分页菜单配置 let options = PagingMenuOptions() //设置分页菜单配置 let pagingMenuController = self.childViewControllers.first as! PagingMenuController pagingMenuController.setup(options) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }源码下载:hangge_1656.zip
四、标签、页面切换响应
有时我们需要监听页面切换事件进行一些操作,比如当切换到新的页面时可以去请求数据等。这个通过 PagingMenuController 的 onMove 回调就可以实现。每当菜单移动前后、页面切换前后、手指滑动页面前后,该方法都会被调用。
1,样例代码
//分页菜单控制器初始化 let pagingMenuController = PagingMenuController(options: options) //....省略一些代码 view.addSubview(pagingMenuController.view) //页面切换响应 pagingMenuController.onMove = { state in switch state { case let .willMoveItem(menuItemView, previousMenuItemView): print("--- 标签将要切换 ---") print("老标签:\(previousMenuItemView.titleLabel.text!)") print("新标签:\(menuItemView.titleLabel.text!)") case let .didMoveItem(menuItemView, previousMenuItemView): print("--- 标签切换完毕 ---") print("老标签:\(previousMenuItemView.titleLabel.text!)") print("新标签:\(menuItemView.titleLabel.text!)") case let .willMoveController(menuController, previousMenuController): print("--- 页面将要切换 ---") print("老页面:\(previousMenuController)") print("新页面:\(menuController)") case let .didMoveController(menuController, previousMenuController): print("--- 页面切换完毕 ---") print("老页面:\(previousMenuController)") print("新页面:\(menuController)") case .didScrollStart: print("--- 分页开始左右滑动 ---") case .didScrollEnd: print("--- 分页停止左右滑动 ---") } }
2,运行效果
(1)我们点击顶部菜单标签进行页面切换,可以看到控制台打印出如下信息:(2)而如果通过手指滑动切换页面,控制台打印出如下信息:
五、使用代码切换标签
比如下面代码自动将分页菜单控制器切换到第 2 个页面。
pagingMenuController.move(toPage: 1, animated: true)
六、自定义分页控制器样式
PagingMenuControllerCustomizable 对象有如下几个属性可以对整个视图控制器进行自定义:
1,defaultPage: Int
设置默认页面的索引,如果不指定则默认显示第一个视图页。
2,animationDuration: TimeInterval
页面切换动画时间。
3,isScrollEnabled: Bool
是否允许手指左右滑动进行页面切换,设置为 false 则只能点击菜单标签切换页面。
4,backgroundColor: UIColor
设置页面背景色。
5,lazyLoadingPage: LazyLoadingPage
lazy loading 的页面数量,这个是一个枚举,可选值如下:
public enum LazyLoadingPage { case one // Currently sets false to isScrollEnabled at this moment. Should be fixed in the future. case three case all // Currently not available for Infinite mode }
6,menuControllerSet: MenuControllerSet
这个不太清楚做什么用的,也是一个枚举可选值如下:public enum MenuControllerSet { case single case multiple }
7,componentType: ComponentType
这个很重要,用来配置整个分页菜单控制器包含的页面和菜单标签。这也是一个枚举,可选值如下:
其中的 MenuItemWidthMode、MenuScrollingMode 也是枚举:
public enum ComponentType { case menuView(menuOptions: MenuViewCustomizable) case pagingController(pagingControllers: [UIViewController]) case all(menuOptions: MenuViewCustomizable, pagingControllers: [UIViewController]) }
8,下面是一个完整的配置样例
//分页菜单配置 private struct PagingMenuOptions: PagingMenuControllerCustomizable { //默认显示第2页 var defaultPage: Int = 1 //页面切换动画播放时间为0.5秒 var animationDuration: TimeInterval = 0.5 //不允许手指左右滑动页面切换 var isScrollEnabled: Bool = false //页面背景色为紫色 var backgroundColor: UIColor = .purple //lazy loading的页面数量(默认值就是.three) var lazyLoadingPage: LazyLoadingPage = .three //不太清楚干嘛用的(默认值就是.multiple) var menuControllerSet: MenuControllerSet = .multiple //第1个子视图控制器 private let viewController1 = ViewController1() //第2个子视图控制器 private let viewController2 = ViewController2() //组件类型 fileprivate var componentType: ComponentType { return .all(menuOptions: MenuOptions(), pagingControllers: pagingControllers) } //所有子视图控制器 fileprivate var pagingControllers: [UIViewController] { return [viewController1, viewController2] } //菜单配置项 fileprivate struct MenuOptions: MenuViewCustomizable { //菜单显示模式 var displayMode: MenuDisplayMode { return .segmentedControl } //菜单项 var itemsOptions: [MenuItemViewCustomizable] { return [MenuItem1(), MenuItem2()] } } //第1个菜单项 fileprivate struct MenuItem1: MenuItemViewCustomizable { //自定义菜单项名称 var displayMode: MenuItemDisplayMode { return .text(title: MenuItemText(text: "电影")) } } //第2个菜单项 fileprivate struct MenuItem2: MenuItemViewCustomizable { //自定义菜单项名称 var displayMode: MenuItemDisplayMode { return .text(title: MenuItemText(text: "音乐")) } } }
七、自定义菜单栏样式
MenuViewCustomizable 对象有如下几个属性对菜单栏进行自定义:
1,backgroundColor: UIColor
设置未选中的菜单标签背景色。
2,selectedBackgroundColor: UIColor
设置选中的菜单标签背景色。
3,height: CGFloat
设置菜单标签的高度。
4,animationDuration: TimeInterval
设置菜单标签切换时动画持续时长。
5,deceleratingRate: CGFloat
设置菜单切换动画减速率(默认为:UIScrollViewDecelerationRateFast)
6,menuSelectedItemCenter: Bool
不太清楚干嘛的。
7,displayMode: MenuDisplayMode
菜单显示模式,它是一个枚举有三个可选值:
public enum MenuDisplayMode { case standard(widthMode: MenuItemWidthMode, centerItem: Bool, scrollingMode: MenuScrollingMode) case segmentedControl case infinite(widthMode: MenuItemWidthMode, scrollingMode: MenuScrollingMode) //这个至少要求有3个页面 }
其中的 MenuItemWidthMode、MenuScrollingMode 也是枚举:
//设置菜单标签的宽度 public enum MenuItemWidthMode { case flexible //自动 case fixed(width: CGFloat) //固定宽度 } //菜单如何滚动 public enum MenuScrollingMode { case scrollEnabled //可以跨多个菜单项切换 case scrollEnabledAndBouces //可以跨多个菜单项切换 case pagingEnabled //菜单只能一个接一个切换 }
- 样例1:分段模式
//菜单显示模式(分段模式) var displayMode: MenuDisplayMode { return .segmentedControl }
- 样例2:标准模式,这里我让选中标签始终居中
//菜单显示模式(标准模式) var displayMode: MenuDisplayMode { return .standard(widthMode: .fixed(width: 30), centerItem: true, scrollingMode: .pagingEnabled) }
- 样例3:无限循环模式(这个必需至少有三个菜单项)
//菜单显示模式(无限循环模式) var displayMode: MenuDisplayMode { return .infinite(widthMode: .flexible, scrollingMode: .pagingEnabled) }
8,focusMode: MenuFocusMode
选中菜单标签的样式,这个也是枚举:public enum MenuFocusMode { case none case underline(height: CGFloat, color: UIColor, horizontalPadding: CGFloat, verticalPadding: CGFloat) case roundRect(radius: CGFloat, horizontalPadding: CGFloat, verticalPadding: CGFloat, selectedColor: UIColor) }
- 样例1:无样式
//选中项无样式 var focusMode: MenuFocusMode = .none
- 样例2:下划线样式
//选中项为橙色下划线样式 var focusMode: MenuFocusMode = .underline(height: 3, color: .orange, horizontalPadding: 0, verticalPadding: 0)
- 样例3:圆角矩形背景样式
//选中项为橙色矩形背景 var focusMode: MenuFocusMode = .roundRect(radius: 6, horizontalPadding: 5, verticalPadding: 8, selectedColor: .orange)
9,dummyItemViewsSet: Int
不太清楚干嘛用的。10,menuPosition: MenuPosition
菜单栏的位置,我们可以指定菜单栏在页面视图的上方还是下方。这个也是个枚举:
public enum MenuPosition { case top case bottom }
11,dividerImage: UIImage?
菜单标签间的分隔图片,显示在每个标签的右侧。//设置标签间的分隔图片 var dividerImage: UIImage? = UIImage(named: "dividerImage.png")!
12,下面是完整的使用样例
//菜单栏配置 fileprivate struct MenuOptions: MenuViewCustomizable { //设置未选中的菜单标签背景为深灰色 var backgroundColor: UIColor = .darkGray //设置选中的菜单标签背景为浅浅灰色 var selectedBackgroundColor: UIColor = .lightText //设置菜单标签高度为40 var height: CGFloat = 40 //菜单切换动画播放时间为0.5秒 var animationDuration: TimeInterval = 0.5 //菜单切换动画减速率(默认为:UIScrollViewDecelerationRateFast) var deceleratingRate: CGFloat = UIScrollViewDecelerationRateFast //不知道干嘛的 var menuSelectedItemCenter: Bool = false //菜单显示模式(分段模式) var displayMode: MenuDisplayMode { return .segmentedControl } //选中项为橙色矩形背景 var focusMode: MenuFocusMode = .roundRect(radius: 6, horizontalPadding: 5, verticalPadding: 8, selectedColor: .orange) //不知道干嘛的 var dummyItemViewsSet: Int = 3 //设置菜单栏在下方 var menuPosition: MenuPosition = .bottom //设置标签间的分隔图片 var dividerImage: UIImage? = UIImage(named: "dividerImage.png")! //菜单项 var itemsOptions: [MenuItemViewCustomizable] { return [MenuItem1(), MenuItem2(), MenuItem3() , MenuItem4()] } }
八、自定义单独的菜单标签样式
MenuItemViewCustomizable 对象有如下几个属性可以对单独的菜单项进行自定义:1,horizontalMargin: CGFloat
设置该菜单标签的左右水平间距。
//第2个菜单项 fileprivate struct MenuItem2: MenuItemViewCustomizable { //该标签的水平边距设为50 var horizontalMargin: CGFloat = 50 //自定义菜单项名称 var displayMode: MenuItemDisplayMode = .text(title: MenuItemText(text: "音乐e")) }
2,displayMode: MenuItemDisplayMode
设置该菜单标签的显示模式,它是一个枚举:
public enum MenuItemDisplayMode { case text(title: MenuItemText) //普通标题文本 case multilineText(title: MenuItemText, description: MenuItemText) //标题+描述文本 case image(image: UIImage, selectedImage: UIImage?) //图片 case custom(view: UIView) //自定义视图 }
- 样例1:修改标签文字的颜色和字体(包括选中和未选中)
//第1个菜单项 fileprivate struct MenuItem1: MenuItemViewCustomizable { //自定义菜单项名称 var displayMode: MenuItemDisplayMode { return .text(title: MenuItemText(text: "电影", color: .lightGray, selectedColor: .orange, font: UIFont.systemFont(ofSize: 13) , selectedFont: UIFont.systemFont(ofSize: 18))) } } //第2个菜单项 fileprivate struct MenuItem2: MenuItemViewCustomizable { //自定义菜单项名称 var displayMode: MenuItemDisplayMode { return .text(title: MenuItemText(text: "音乐", color: .lightGray, selectedColor: .orange, font: UIFont.systemFont(ofSize: 13) , selectedFont: UIFont.systemFont(ofSize: 18))) } }
- 样例2:标签显示为标题+描述文本
//第2个菜单项 fileprivate struct MenuItem2: MenuItemViewCustomizable { //该标签的水平边距设为50 var horizontalMargin: CGFloat = 50 //自定义菜单项标题和描述 var displayMode: MenuItemDisplayMode { let desFont = UIFont.systemFont(ofSize: 10) let description = MenuItemText(text: "Music", font: desFont , selectedFont: desFont ) return .multilineText(title: MenuItemText(text: "音乐"), description: description) } }
- 样例3:将菜单标签显示为图片
//第2个菜单项 fileprivate struct MenuItem2: MenuItemViewCustomizable { //该标签的水平边距设为50 var horizontalMargin: CGFloat = 50 //菜单项显示为图片 var displayMode: MenuItemDisplayMode { return .image(image: UIImage(named: "forward")!, selectedImage: UIImage(named: "forward")!) } }
全部评论(3)
请问一下站长. 如果想要在子视图里面有 SB 布局. 应该怎么关联!
站长回复:
暂时不支持4.0啊
站长回复:我又测试了下,是可以支持的啊。
选中字体颜色 怎么设置
站长回复:MenuItemText可以设置文字、字体(选中/未选中)、颜色(选中/未选中)
public struct MenuItemText {
public init(text: String = default, color: UIColor = default, selectedColor: UIColor = default, font: UIFont = default, selectedFont: UIFont = default)
}