- 功能需求
- 由于采用了多视图控制器的设计方式,因此需要通过代码的方式向主控制器中添加子控制器
一、初始化部署
- 0、效果图
- 1、初始化window的根控制器
import UIKit
/*
1. command + shift + j ->快速定位到层级结构
2.按下回车 -->改文件夹的名称
3.command + c -->拷贝文件名称
4.command + n -->新建文件
5.command + v -->粘贴文件名称
6.回车
7.重复以上操作
*/
// T的含义: 外界传入什么就是什么
func JPLog<T>(message: T, file: NSString = __FILE__, method: String = __FUNCTION__, line: Int = __LINE__)
{
#if DEBUG
print("\(method)[\(line)]: \(message)")
#endif
}
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
// 1.创建window
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.backgroundColor = UIColor.whiteColor()
// 2.创建根控制器
window?.rootViewController = MainViewController()
// 3.显示界面
window?.makeKeyAndVisible()
return true
}
}
- MainViewController(window的根控制器)
- 根据子控制器添加
import UIKit
class MainViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
addChildViewController(HomeTableViewController(), title: "首页", imageName: "tabbar_home")
addChildViewController(MessageTableViewController(), title: "消息", imageName: "tabbar_message_center")
addChildViewController(DiscoverTableViewController(), title: "发现", imageName: "tabbar_discover")
addChildViewController(ProfileTableViewController(), title: "我", imageName: "tabbar_profile")
}
// MARK: - 内部控制方法
func addChildViewController(childController: UIViewController, title: String, imageName: String) {
// 1.添加子控制器
// 不写该句,图片为蓝色默认,如果是在iOS8以前, 只有文字有效果, 而图片没有效果,而ios8设tintColor图片文字颜色都一样了,如果ios8前是设图片的渲染模型才能解决
tabBar.tintColor = UIColor.orangeColor()
// 1.1创建一个子控制器
let homeVC = HomeTableViewController()
// 1.2设置子控制器的相关属性
homeVC.tabBarItem.image = UIImage(named: imageName)
homeVC.tabBarItem.selectedImage = UIImage(named: imageName
+ "_highlighted")
// homeVC.tabBarItem.title = title
// homeVC.navigationItem.title = title
// 系统会由内向外的设置标题
homeVC.title = title
// 1.3给子控制器包装一个导航控制器
let nav = UINavigationController(rootViewController: homeVC)
// 1.4添加
addChildViewController(nav)
}
}
二、根据类名字符串(加命名空间)来创建对应的控制器,重新设计MainViewController
- 注意:
- 1.在Swift中, 如果想通过字符串创建一个类, 那么必须加上命名空间
- 2.动态获取的命名空间是不包含.的, 所以需要我们自己手动拼接
- 3.动态获取命名空间
let nameSpace = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"]
```
+ 实现方式一:利用可选绑定,判断命名空间存在,就创建
```objc
import UIKit
class MainViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
addChildViewController("HomeTableViewController", title: "首页", imageName: "tabbar_home")
addChildViewController("MessageTableViewController", title: "消息", imageName: "tabbar_message_center")
addChildViewController("DiscoverTableViewController", title: "发现", imageName: "tabbar_discover")
addChildViewController("ProfileTableViewController", title: "我", imageName: "tabbar_profile")
}
// MARK: - 内部控制方法
// func addChildViewController(childController: UIViewController, title: String, imageName: String) {
func addChildViewController(childControllerName: String, title: String, imageName: String) {
// <Weibo.HomeTableViewController: 0x7f8b22990ea0>
// JPLog(childController)
// print(childController)
// 0.动态获取命名空间
let nameSpace = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"]
if let ns = nameSpace as? String
{
// 注意: 1.在Swift中, 如果想通过字符串创建一个类, 那么必须加上命名空间
// 2.动态获取的命名空间是不包含.的, 所以需要我们自己手动拼接
let cls: AnyClass? = NSClassFromString(ns + "." + childControllerName)
// 1.将AnyClass类型转换为UIViewController类型
// AnyClass本质: AnyObject.Type
// UIViewController本质: UIViewController.Type
if let clsType = cls as? UIViewController.Type
{
// 2.根据控制器类类型创建一个控制器对象
let homeVC = clsType.init()
print(homeVC)
// 1.添加子控制器
// 如果是在iOS8以前, 只有文字有效果, 而图片没有效果
tabBar.tintColor = UIColor.orangeColor()
// 1.2设置子控制器的相关属性
homeVC.tabBarItem.image = UIImage(named: imageName)
homeVC.tabBarItem.selectedImage = UIImage(named: imageName
+ "_highlighted")
// 系统会由内向外的设置标题
homeVC.title = title
// 1.3给子控制器包装一个导航控制器
let nav = UINavigationController(rootViewController: homeVC)
// 1.4添加
addChildViewController(nav)
}
}
}
}
- 实现方式二:利用guard来判断命名空间是否存在,存在才创建
import UIKit
class MainViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
addChildViewController("HomeTableViewController", title: "首页", imageName: "tabbar_home")
addChildViewController("MessageTableViewController", title: "消息", imageName: "tabbar_message_center")
addChildViewController("DiscoverTableViewController", title: "发现", imageName: "tabbar_discover")
addChildViewController("ProfileTableViewController", title: "我", imageName: "tabbar_profile")
}
// MARK: - 内部控制方法
func addChildViewController(childControllerName: String, title: String, imageName: String) {
/*
苹果也意识到了, Swift会不知不觉的形成多层嵌套, 然代码变得非常丑陋
所以Swift2.0的时候专门退出了一个条件语句(guard)来解决这个问题
格式:
guard 条件表达式 else {
需要执行的语句
return
}
特点: 只要条件为假才会执行else中的代码
作用: 用于过滤数据
对比if else 这哥们只有else{}没有if{}
*/
// 0.动态获取命名空间
let nameSpace = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"]
guard let ns = nameSpace as? String else{
print("命名空间不存在")
return
}
// 注意: 1.在Swift中, 如果想通过字符串创建一个类, 那么必须加上命名空间
// 2.动态获取的命名空间是不包含.的, 所以需要我们自己手动拼接
let cls: AnyClass? = NSClassFromString(ns + "." + childControllerName)
// 1.将AnyClass类型转换为UIViewController类型
guard let clsType = cls as? UIViewController.Type else{
print("传入的字符串不能当做UIViewController来使用")
return
}
// 2.根据控制器类类型创建一个控制器对象
let homeVC = clsType.init()
print(homeVC)
// 1.添加子控制器
// 如果是在iOS8以前, 只有文字有效果, 而图片没有效果
tabBar.tintColor = UIColor.orangeColor()
// 1.2设置子控制器的相关属性
homeVC.tabBarItem.image = UIImage(named: imageName)
homeVC.tabBarItem.selectedImage = UIImage(named: imageName
+ "_highlighted")
// 系统会由内向外的设置标题
homeVC.title = title
// 1.3给子控制器包装一个导航控制器
let nav = UINavigationController(rootViewController: homeVC)
// 1.4添加
addChildViewController(nav)
}
}
三、动态创建控制器
- 方案:根据json文件配置所有子控制器列表,来搭建项目
- 该方式,简答,灵活
- 根据json文件配置所有子控制器列表来搭建项目更加灵活,使用方案:我们可以通过发网络请求到服务器来返回对应的json数据 -》得到配置显示的子控制器列表的json,然后App根据解析json文件来创建对应的子控制器
1、MainVCSettings.json配置文件
[
{
"vcName": "HomeTableViewController",
"title": "首页",
"imageName": "tabbar_home"
},
{
"vcName": "MessageTableViewController",
"title": "消息",
"imageName": "tabbar_message_center"
},
{
"vcName": "DiscoverTableViewController",
"title": "广场",
"imageName": "tabbar_discover"
},
{
"vcName": "ProfileTableViewController",
"title": "我",
"imageName": "tabbar_profile"
}
]
- 2、解析json,创建对应控制器
- 如果有JSON数据解析JOSN数据来创建,没JOSN数据就根据以前的方式创建
import UIKit
class MainViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// 1.获取文件路径
let path = NSBundle.mainBundle().pathForResource("MainVCSettings.json", ofType: nil)!
// 2.根据文件创建
let data = NSData(contentsOfFile: path)!
// 在OC中处理异常是通过传入一个NSError的指针来保存错误
// Swfit中提供了专门处理异常机制 throws -> AnyObject
// Swift中提供 try catch, 将有可能发生错误的代码放到do中, 如果真的发生了异常就会执行catch
// try作用: 如果抛出(throws)异常, 那么就会执行catch
// try!作用: 告诉一定一定没有错误, 不需要处理, 但是如果使用try!发生了错误, 那么程序就会崩溃, 开发中不推荐使用
// try?作用: 告诉系统可能有错也可能没有错, 如果发生错误会返回一个nil, 如果没有发生错误, 会将数据包装成可选类型
do{
let dictArr = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers)
for dict in dictArr as! [[String: AnyObject]]
{
addChildViewController(dict["vcName"] as? String, title: dict["title"] as? String,imageName: dict["imageName"] as? String)
}
}catch
{
addChildViewController("HomeTableViewController", title: "首页", imageName: "tabbar_home")
addChildViewController("MessageTableViewController", title: "消息", imageName: "tabbar_message_center")
addChildViewController("DiscoverTableViewController", title: "发现", imageName: "tabbar_discover")
addChildViewController("ProfileTableViewController", title: "我", imageName: "tabbar_profile")
}
}
// MARK: - 内部控制方法
func addChildViewController(childControllerName: String?, title: String?, imageName: String?) {
// 0.动态获取命名空间
let nameSpace = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"]
guard let ns = nameSpace as? String else{
JPLog("命名空间不存在")
return
}
guard let vcName = childControllerName else
{
JPLog("控制器名称是nil")
return
}
// 注意: 1.在Swift中, 如果想通过字符串创建一个类, 那么必须加上命名空间
// 2.动态获取的命名空间是不包含.的, 所以需要我们自己手动拼接
let cls: AnyClass? = NSClassFromString(ns + "." + vcName)
// 1.将AnyClass类型转换为UIViewController类型
guard let clsType = cls as? UIViewController.Type else{
print("传入的字符串不能当做UIViewController来使用")
return
}
// 2.根据控制器类类型创建一个控制器对象
let homeVC = clsType.init()
// 1.添加子控制器
// 如果是在iOS8以前, 只有文字有效果, 而图片没有效果
tabBar.tintColor = UIColor.orangeColor()
// 1.2设置子控制器的相关属性
if let iName = imageName
{
homeVC.tabBarItem.image = UIImage(named: iName)
homeVC.tabBarItem.selectedImage = UIImage(named: iName
+ "_highlighted")
}
// 系统会由内向外的设置标题
homeVC.title = title
// 1.3给子控制器包装一个导航控制器
let nav = UINavigationController(rootViewController: homeVC)
// 1.4添加
addChildViewController(nav)
}
}
四、自定义 TabBar,添加加号按钮
-
1、效果图
- 2、配置
- 分析:在 4 个控制器切换按钮中间增加一个撰写按钮,添加占位控制器,计算控制器按钮位置,在中间添加一个
撰写
按钮 - 2.1 重新配置MainVCSettings.json文件,添加一个NullViewController配置
- 分析:在 4 个控制器切换按钮中间增加一个撰写按钮,添加占位控制器,计算控制器按钮位置,在中间添加一个
[
{
"vcName": "HomeTableViewController",
"title": "首页",
"imageName": "tabbar_home"
},
{
"vcName": "MessageTableViewController",
"title": "消息",
"imageName": "tabbar_message_center"
},
{
"vcName": "NullViewController",
"title": "",
"imageName": ""
},
{
"vcName": "DiscoverTableViewController",
"title": "广场",
"imageName": "tabbar_discover"
},
{
"vcName": "ProfileTableViewController",
"title": "我",
"imageName": "tabbar_profile"
}
]
2.2 创建一个NullViewController.swift控制器文件
2.3 添加NullViewController 控制器,设为加号按钮弹出的控制器,并配置加号按钮点击事件
import UIKit
class MainViewController: UITabBarController {
// MARK: - 生命周期方法
override func viewDidLoad() {
super.viewDidLoad()
/// 1.添加所有子控制器
addChildViewControllers()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// 1.初始化加号按钮
setupComposeButton()
}
// MARK: - 内部控制方法
// 如果在方法前面加上private, 代表这个方法只能在当前文件中访问
// 如果在属性前面加上private, 代表这个属性只能在当前文件中访问
// 如果在类前面加上private, 代表这个类只能在当前文件中访问
/// 添加加号按钮
private func setupComposeButton()
{
// 0.将加号按钮添加到tabbar上
tabBar.addSubview(composeButton)
// 1.计算宽度
let width = UIScreen.mainScreen().bounds.width / CGFloat(childViewControllers.count)
// 2.计算高度
let height = tabBar.bounds.height
// 3.修改frame
let rect = CGRect(origin: CGPointZero, size: CGSize(width: width, height: height))
/*
CGRectOffset
第一个参数:控件的frame
第二个参数:控件x方向偏移的值
第三个参数:控件y方法偏移的值
*/
composeButton.frame = CGRectOffset(rect, 2 * width, 0)
}
/// 添加所有子控制器
private func addChildViewControllers() {
// 1.获取文件路径
let path = NSBundle.mainBundle().pathForResource("MainVCSettings.json", ofType: nil)!
// 2.根据文件创建
let data = NSData(contentsOfFile: path)!
do{
// 解析JSON创建控制器
let dictArr = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers)
for dict in dictArr as! [[String: AnyObject]]
{
addChildViewController(dict["vcName"] as? String, title: dict["title"] as? String,imageName: dict["imageName"] as? String)
}
}catch
{
// 使用本地数据
addChildViewController("HomeTableViewController", title: "首页", imageName: "tabbar_home")
addChildViewController("MessageTableViewController", title: "消息", imageName: "tabbar_message_center")
// 设加号按钮对应的控制器 NullViewController
addChildViewController(NullViewController())
addChildViewController("DiscoverTableViewController", title: "发现", imageName: "tabbar_discover")
addChildViewController("ProfileTableViewController", title: "我", imageName: "tabbar_profile")
}
}
/**
添加子控制器
- parameter childControllerName: 控制器的名称
- parameter title: 控制器标题
- parameter imageName: 控制器图片
*/
private func addChildViewController(childControllerName: String?, title: String?, imageName: String?) {
// 0.动态获取命名空间
let nameSpace = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"]
guard let ns = nameSpace as? String else{
JPLog("命名空间不存在")
return
}
guard let vcName = childControllerName else
{
JPLog("控制器名称是nil")
return
}
// 注意: 1.在Swift中, 如果想通过字符串创建一个类, 那么必须加上命名空间
// 2.动态获取的命名空间是不包含.的, 所以需要我们自己手动拼接
let cls: AnyClass? = NSClassFromString(ns + "." + vcName)
// 1.将AnyClass类型转换为UIViewController类型
guard let clsType = cls as? UIViewController.Type else{
print("传入的字符串不能当做UIViewController来使用")
return
}
// 2.根据控制器类类型创建一个控制器对象
let homeVC = clsType.init()
// 1.添加子控制器
// 如果是在iOS8以前, 只有文字有效果, 而图片没有效果
tabBar.tintColor = UIColor.orangeColor()
// 1.2设置子控制器的相关属性
if let iName = imageName
{
homeVC.tabBarItem.image = UIImage(named: iName)
homeVC.tabBarItem.selectedImage = UIImage(named: iName
+ "_highlighted")
}
// 系统会由内向外的设置标题
homeVC.title = title
// 1.3给子控制器包装一个导航控制器
let nav = UINavigationController(rootViewController: homeVC)
// 1.4添加
addChildViewController(nav)
}
// MARK: - 监听点击事件
// 注意: 由于点击事件是由NSRunLoop发起的, 并不是当前类发起的, 所以如果在点击方法前面加上private, 那么NSRunLoop无法找到该方法
// OC是基于运行时动态派发事件的, 而Swift是编译时就已经确定了方法
// 如果想给监听点击的方法加上private, 并且又想让系统动态派发时能找到这个方法, 那么可以在前面加上@objc, @objc就能让这个方法支持动态派发
@objc private func composeBtnClick()
{
JPLog("")
}
// MARK: - 懒加载
private lazy var composeButton: UIButton = {
/*
let btn = UIButton()
// 1.设置背景图片
btn.setBackgroundImage(UIImage(named: "tabbar_compose_button"), forState: UIControlState.Normal)
btn.setBackgroundImage(UIImage(named: "tabbar_compose_button_highlighted"), forState: UIControlState.Highlighted)
// 2.设置普通图片
btn.setImage(UIImage(named: "tabbar_compose_icon_add"), forState: UIControlState.Normal)
btn.setImage(UIImage(named: "tabbar_compose_icon_add_highlighted"), forState: UIControlState.Highlighted)
// 3.监听按钮点击
btn.addTarget(self, action: Selector("composeBtnClick"), forControlEvents: UIControlEvents.TouchUpInside)
btn.sizeToFit()
*/
// let btn = UIButton.create("tabbar_compose_button", backImageName: "tabbar_compose_icon_add")
let btn = UIButton(imageName: "tabbar_compose_button", backImageName: "tabbar_compose_icon_add") // 使用了UIButton分类的定义的方法,如下UIButton+Extension.swift分类文件可得
btn.addTarget(self, action: Selector("composeBtnClick"), forControlEvents: UIControlEvents.TouchUpInside)
return btn;
}()
}
- 注意: UIButton+Extension.swift分类实现抽取如下
import UIKit
extension UIButton
{
// 虽然以下方法可以快速创建一个UIButton对象, 但是Swift风格不是这样写的
// 在Swift开发中, 如果想快速创建一个对象, 那么可以提供一个便利构造器(便利构造方法)
// 只要在普通构造方法前面加上一个convenience, 那么这个构造方法就是一个便利构造方法
// 注意: 如果定义一个便利构造器, 那么必须在便利构造器中调用指定构造器(没有加convenience单词的构造方法)
/*
class func create(imageName: String, backImageName: String) -> UIButton
{
let btn = UIButton()
// 1.设置背景图片
btn.setBackgroundImage(UIImage(named: imageName), forState: UIControlState.Normal)
btn.setBackgroundImage(UIImage(named: imageName + "highlighted"), forState: UIControlState.Highlighted)
// 2.设置普通图片
btn.setImage(UIImage(named:backImageName), forState: UIControlState.Normal)
btn.setImage(UIImage(named: backImageName + "highlighted"), forState: UIControlState.Highlighted)
btn.sizeToFit()
return btn
}
*/
/*
定义便利构造器步骤:
1.编写一个构造方法
2.在构造方法前面加上 convenience
3.在构造方法中调用当前类的其他"非便利构造器"初始化对象
*/
convenience init(imageName: String, backImageName: String)
{
self.init()
// 1.设置背景图片
setBackgroundImage(UIImage(named: imageName), forState: UIControlState.Normal)
setBackgroundImage(UIImage(named: imageName + "highlighted"), forState: UIControlState.Highlighted)
// 2.设置普通图片
setImage(UIImage(named:backImageName), forState: UIControlState.Normal)
setImage(UIImage(named: backImageName + "highlighted"), forState: UIControlState.Highlighted)
sizeToFit()
}
}
阶段性小结
- 整体开发思路与使用 OC 几乎一致
- Swift 语法更加简洁,字符串拼接非常简单
vc.tabBarItem.selectedImage = UIImage(named: imageName + "_highlighted")
- Swift 对类型校验更加严格,不同类型的变量不允许直接计算
v.frame = CGRectOffset(rect, CGFloat(index) * w, 0)
- Swift 中的懒加载写法
lazy var composedButton: UIButton = {
let btn = UIButton()
btn.setImage(UIImage(named: "tabbar_compose_icon_add"), forState: UIControlState.Normal)
btn.setImage(UIImage(named: "tabbar_compose_icon_add_highlighted"), forState: UIControlState.Highlighted)
btn.setBackgroundImage(UIImage(named: "tabbar_compose_button"), forState: UIControlState.Normal)
btn.setBackgroundImage(UIImage(named: "tabbar_compose_button_highlighted"), forState: UIControlState.Highlighted)
// 懒加载中需要使用 self.
self.addSubview(btn)
return btn
}()
懒加载的代码是一个闭包,因此在代码内部需要使用
self.
不希望暴露的方法,应该使用
private
修饰符按钮点击事件的调用是由
运行循环
监听并且以消息机制
传递的,因此,按钮监听函数不能设置为private
Swift 中的类名是包含命名空间的,如果希望用字符串动态创建并且实例化类,需要按照以下代码格式
let namespace = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"] as! String
let clsName = namespace + "." + vcName
// 告诉编译器暂时就是AnyClass
let cls: AnyClass! = NSClassFromString(ns + "." + vcName)
// 告诉编译器真实类型是UIViewController
let vcCls = cls as! UIViewController.Type
// 实例化控制器
let vc = vcCls.init()
tabBar 的 items 是在
视图将要出现之前
才被创建的