一 总体功能图一 : (ipad竖屏)
二 总体功能图二 : (ipad横屏)
三 讲解内容
1 搭建美团界面(掌握)
2 ios8.0之后的Popover的运用(重点)
3 协议(掌握)
4 通知(掌握)
5 细节处理
四 总体界面
1 由总体的app界面效果,能看出来,一个UIViewController控制器作为UINavigationController的根控制器就能满足条件.
五 导航条设置
1 自定义导航条 : (系统的导航条不能满足需求)
2 创建导航控制器类
3 获取全局的导航条 : (用来作为设置)
let navigationBar = UINavigationBar.appearanceWhenContainedInInstancesOfClasses([self .classForCoder])
—-> 3.1 注意 : 一定要用上面的方法来获取,不要用下面的方法来获取,因为通过下面方法获取对导航条的设置,很有可能会造成导航条呈现黑色的情况.
UINavigationBar .appearance ()
4 用图片包装导航条
navigationBar.setBackgroundImage(UIImage(named: "bg_navigationBar_normal" ), forBarMetrics: .Default )
5 注意不要忘了将导航控制器的类型改为自定义类型,否则会加载不出来的
六 导航条相关内容处理
1 思路 : 通过观察导航条中的按钮,我们可以看出, 按钮都是由图片;主标题;子标题组成的,所以我们可以通过xib来描述,并且用一个UIView将按钮包裹住,方便修改设置
2 创建继承UIView的类,同时创建同类名的xib
3 xib图
4 xib内部分布结构图
5 如何在xib中分离图片和标题之间的距离?
—-> 5.1 下图解答
6 通过给xib拖线,拿到内部属性
@IBOutlet weak var iconButton: UIButton!
@IBOutlet weak var subtitleLabel: UILabel!
@IBOutlet weak var title_Label: UILabel!
7 给xib中的属性提供set方法和对应的get方法,方便外边调用修改
var title : String ? {
didSet{
return title_Label.text = title
}
}
var subtitle : String ? {
didSet{
return subtitleLabel.text = subtitle
}
}
var normalName : String ? {
didSet{
return iconButton.setImage(UIImage(named: normalName!), forState: .Normal)
}
}
var heightName : String ? {
didSet{
return iconButton.setImage(UIImage(named: heightName!), forState: .Highlighted)
}
}
var getIconButton : UIButton {
get{
return iconButton
}
}
8 提供一个加载xib的类方法,让外界能通过该方法快速创建xib
//MARK: - 给类扩展一个方法(加载xib)
extension XFJTopView {
//提供一个快速创建xib的类方法
class func topView(title : String, subTitle : String, normalImageName : String, heightImageName : String) ->XFJTopView {
let topView = NSBundle.mainBundle ().loadNibNamed ("XFJTopView" , owner: nil, options: nil).last ! as! XFJTopView
//给属性赋值
topView.title _Label.text = title
topView.subtitleLabel .text = subTitle
topView.iconButton .setImage (UIImage(named: normalImageName), forState: .Normal )
topView.iconButton .setImage (UIImage(named: heightImageName), forState: .Highlighted )
//返回通过xib创建的对象
return topView
}
}
9 添加顶部的按钮
—-> 9.1 思路 : 通过添加到由导航条管理的item中的数组中来实现对加载xib的时候按钮的添加
—-> 9.2 导航控制器的根控制器
—-> 9.3 XFJHomeViewController类对导航条中的item的管理(该部分代码比较多,我们通过类扩展来实现)
extension XFJHomeViewController {
@objc private func setUpTabBarItem() {
let logoItem = UIBarButtonItem(image: UIImage(named: "icon_meituan_logo" ), style: . Plain, target: nil, action: nil)
navigationItem. leftBarButtonItem = logoItem
logoItem. enabled = false
let item1 = XFJTopView. topView("美团" , subTitle: "全部分类" , normalImageName: "icon_category_-1" , heightImageName: "icon_category_highlighted_-1" )
let topItem = UIBarButtonItem(customView: item1)
item1. getIconButton. addTarget(self , action: "presentPopTopViewClick" , forControlEvents: . TouchUpInside)
self . topItem = topItem
let item2 = XFJTopView. topView("广州" , subTitle: "全部" , normalImageName: "icon_district" , heightImageName: "icon_district_highlighted" )
let gzItem = UIBarButtonItem(customView: item2)
item2. getIconButton. addTarget(self , action: "presentPopGzViewClick" , forControlEvents: . TouchUpInside)
self . gzItem = gzItem
let item3 = XFJTopView. topView("排序" , subTitle: "默认排序" , normalImageName: "icon_sort" , heightImageName: "icon_sort_highlighted" )
let sortItem = UIBarButtonItem(customView: item3)
item3. getIconButton. addTarget(self , action: "presentPopSortViewClick" , forControlEvents: . TouchUpInside)
self . sortItem = sortItem
navigationItem. leftBarButtonItems = [ logoItem,topItem,gzItem,sortItem]
}
}
七 Popover的弹出
1 分别创建三个类来管理弹出的Popover
2 对顶部三个item所弹出的控制做懒加载创建,保证用到的时候在创建
private lazy var categoryVC : XFJCategoryViewController = {
let categoryVC = XFJCategoryViewController()
categoryVC.modalPresentationStyle = .Popover
return categoryVC
}()
private lazy var districtVC : XFJDistrictViewController = {
let districtVC = XFJDistrictViewController()
districtVC.modalPresentationStyle = .Popover
return districtVC
}()
private lazy var sortsVC : XFJSortsViewController = {
let sortsVC = XFJSortsViewController()
sortsVC.modalPresentationStyle = .Popover
return sortsVC
}()
3 根据弹出的Popover类型,我们也可以看出是由两个UITableView组成,并且各占控制器的一半,那么我们这部分也可以通过xib来实现.
—-> 3.1 创建一个类来管理,同时创建xib
—-> 3.2 xib内部图
4 弹出Popover(通过在9.3中对item的监听)
—-> 4.1 弹出Popover代码块一 :
extension XFJHomeViewController {
@objc private func presentPopTopViewClick () {
categoryVC.popoverPresentationController?.barButtonItem = topItem
categoryVC.popoverPresentationController?.backgroundColor = UIColor.clearColor()
presentViewController(categoryVC, animated: true , completion: nil)
setDisabled()
categoryVC.popoverPresentationController?.delegate = self
}
}
—-> 4.1 弹出Popover代码块二:
extension XFJHomeViewController {
@objc private func presentPopGzViewClick () {
districtVC.popoverPresentationController?.barButtonItem = gzItem
districtVC.popoverPresentationController?.backgroundColor = UIColor.clearColor()
presentViewController(districtVC, animated: true , completion: nil)
setDisabled()
districtVC.popoverPresentationController?.delegate = self
}
}
—-> 4.1 弹出Popover代码块三 :
extension XFJHomeViewController {
@objc private func presentPopSortViewClick () {
sortsVC.popoverPresentationController?.barButtonItem = sortItem
sortsVC.popoverPresentationController?.backgroundColor = UIColor.whiteColor()
presentViewController(sortsVC, animated: true , completion: nil)
setDisabled()
sortsVC.popoverPresentationController?.delegate = self
}
}
八 处理弹出的Popover相关数据(全部由对应的模型来决定)
1 获取xib中的对象并且提供一个快速创建xib的类方法
@IBOutlet weak var leftTableView: UITableView!
@IBOutlet weak var rightTableView: UITableView!
var categories : [XFJCategories]?
var DistrictData : [XFJDistrict]?
var seletIndex : Int?
class func lrTableView () ->XFJLRTableView {
return NSBundle.mainBundle().loadNibNamed("XFJLRTableView" , owner: nil, options: nil).last as! XFJLRTableView
}
private var subData : [String]?
2 通过在xib中设置代理和数据源实现有关数据源方法
—-> 2.1 数据源方法一 : cell的个数
extension XFJLRTableView : UITableViewDataSource {
func tableView(tableView: UITableView , numberOfRowsInSection section: Int) -> Int {
if tableView == leftTableView {
return (delegateSource?.numberOfRowsInLeft (self ))!
}else {
return subData?.count ?? 0
}
}
—-> 2.2 数据源方法二 : cell的内容
func tableView(tableView: UITableView , cellForRowAtIndexPath indexPath: NSIndexPath ) -> UITableViewCell {
var cell = UITableViewCell ?()
if tableView == leftTableView {
cell = XFJLeftViewCell.leftViewCell (tableView)
cell?.textLabel ?.text = delegateSource?.lrTableView (titleDataSource: indexPath.row )
if delegate?.respondsToSelector ("lrTableViewWithNormalImageInLeft:" ) == true {
cell?.imageView ?.image = UIImage (named: (delegateSource?.lrTableView !(normalImageInLeft: indexPath.row ))!)
}
if delegate?.respondsToSelector ("lrTableViewWithHighlightImageLeft:" ) == true {
cell?.imageView ?.highlightedImage = UIImage (named: (delegateSource?.lrTableView !(highlightImageLeft: indexPath.row ))!)
}
}else {
cell = XFJRightViewCell.righViewCell (tableView)
cell?.textLabel ?.text = subData![indexPath.row ]
}
return cell!
}
—-> 2.3 数据源方法三 : 点击cell做出的相应数据改变
extension XFJLRTableView : UITableViewDelegate {
func tableView(tableView: UITableView , didSelectRowAtIndexPath indexPath: NSIndexPath ) {
if tableView == leftTableView {
subData = delegateSource?.lrTableView (subDataSource: indexPath.row )
delegate?.lrTableView (seletLeftButton: indexPath.row )
seletIndex = indexPath.row
rightTableView.reloadData ()
}else {
delegate?.lrTableView (seletRightButton: indexPath.row , seletLeftButton: seletIndex!)
}
}
}
九 创建模型
1 导入plist文件
2 创建继承NSObject的类,用来设置需要用到的模型属性
—-> 2.1 模型属性一 :(分类中所需要的模型属性)
var highlighted_icon = String ?()
var icon = String ?()
var name = String ?()
var small_highlighted_icon = String ?()
var small_icon = String ?()
var map_icon = String ?()
var subcategories = [String ]?()
—-> 2.2 模型属性二 : (全部模块中所需要的模型属性)
var name = String ?()
var subregions = [String ]?()
—-> 2.3 模型属性三 : (排序模块中所需要的模型属性)
var label = String ?()
var value = Int?()
3 在各自管理的类中懒加载模型(采用MJ框架加载模型)
—-> 3.1 分类模块中懒加载模型
private lazy var categories : [XFJCategories] = {
let categoriesData = XFJCategories.objectArrayWithFilename("categories.plist" ) as NSArray
return categoriesData as ! [XFJCategories]
}()
—-> 3.2 地区模块中懒加载模型
private lazy var DistrictView :[XFJDistrict] = {
let DistrictData = XFJDistrict.objectArrayWithFilename("gz.plist" ) as NSArray
return DistrictData as ! [XFJDistrict]
}()
—-> 3.3 排序模块中懒加载模型
var previousButton = UIButton()
private lazy var sortsData : [XFJSorts] = {
let sortsDatas = XFJSorts.objectArrayWithFilename("sorts.plist" ) as NSArray
return sortsDatas as ! [XFJSorts]
}()
十 自定义cell
1 通过功能图知道Popover出来的控制器中cell中既展示图片又展示文字,所以我们通过自定义cell来设置
2 自定义左边的tableViewCell
class XFJLeftViewCell : UITableViewCell {
class func leftViewCell (tableView : UITableView ) ->XFJLeftViewCell {
let leftCell = "leftCell"
var cell = tableView.dequeueReusableCellWithIdentifier(leftCell)
if cell == nil {
cell = XFJLeftViewCell(style: .Default, reuseIdentifier: leftCell)
}
return cell as ! XFJLeftViewCell
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super .init(style: style, reuseIdentifier: reuseIdentifier)
backgroundView = UIImageView(image: UIImage(named:"bg_dropdown_leftpart" ))
selectedBackgroundView = UIImageView(image: UIImage(named:"bg_dropdown_left_selected" ))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented" )
}
}
—-> 2.1 该方法是在数据源方法中调用的,用来加载cell
3 自定义右边的tableViewCell
class XFJRightViewCell : UITableViewCell {
class func righViewCell (tableView : UITableView ) ->XFJRightViewCell {
let rightCell = "rightCell"
var cell = tableView.dequeueReusableCellWithIdentifier(rightCell)
if cell == nil {
cell = XFJRightViewCell(style: .Default, reuseIdentifier: rightCell)
}
return cell as ! XFJRightViewCell
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super .init(style: style, reuseIdentifier: reuseIdentifier)
backgroundView = UIImageView(image: UIImage(named: "bg_dropdown_rightpart" ))
selectedBackgroundView = UIImageView(image: UIImage(named:"bg_dropdown_right_selected" ))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented" )
}
}
十一 上半部分总结
1 用上面这些方法确实可以达到用户点击item,弹出对应的控制器.但是上面的代码只是写了对其中一个点击item弹出的业逻辑,还有中间的item并没与处理,如果采用这样的方法处理,那么代码量太多了,并且看起来也显得没什么技术含量,我们最终将不会采用这种方法实现.
十二 代理 协议(最终实现的方案)
1 需要实现的功能 : 通过代理协议的方式,实现用户点击弹出控制器的左边部分,显示出右边部分,并且将对应的头像和主标题,子标题显示到item中
2 定义协议方法 : (包括可实现和可不实现)–> 因为 : 当点击左边的tableView中的cell的时候,有右边有些内容是空的,所以如果都定义为必须实现的,会出现问题
3 定义在XFJLRTableView中的协议方法第一部分
@objc protocol XFJTableViewDataSource : NSObjectProtocol {
func numberOfRowsInLeft (leftTableView : XFJLRTableView) ->Int
func lrTableView(titleDataSource leftRow: Int) ->String?
func lrTableView(subDataSource leftRow : Int) ->[String]
optional func lrTableView(normalImageInLeft leftRow : Int) ->String
optional func lrTableView(highlightImageLeft leftRow : Int) ->String
}
4 设置代理 :(分类模块代理)
weak var delegateSource : XFJTableViewDataSource?
5 注意 : 在对协议实现的部分方法中,已经在XFJLRTableView类中的数据源方法这种实现了或者是做出了判断.(上面数据源方法中有介绍)
6 对分类用户点击后实现协议相关的方法
—-> 6.1 设置对应的控制器为代理
override func viewDidLoad() {
super.viewDidLoad()
let lrTableView = XFJLRTableView.lrTableView()
lrTableView.frame = view.bounds
view.addSubview(lrTableView)
lrTableView.delegateSource = self
lrTableView.delegate = self
}
7 实现协议方法
extension XFJCategoryViewController : XFJTableViewDataSource {
func numberOfRowsInLeft(leftTableView: XFJLRTableView) -> Int {
return categories.count
}
func lrTableView(titleDataSource leftRow: Int) -> String? {
let categorie = categories[leftRow]
return categorie.name
}
func lrTableView(subDataSource leftRow: Int) -> [String] {
let categorie = categories[leftRow]
return categorie.subcategories ?? []
}
func lrTableView(normalImageInLeft leftRow: Int) -> String {
let categorie = categories[leftRow]
return categorie.small_icon!
}
func lrTableView(highlightImageLeft leftRow: Int) -> String {
let categorie = categories[leftRow]
return categorie.small_highlighted_icon!
}
}
8 处理用户点击item中的某行cell,将cell中显示的图片和主标题,子标题显示在item中
—-> 8.1 定义协议 :
@objc protocol XFJTableViewDelegate : NSObjectProtocol {
func lrTableView(seletLeftButton leftRow : Int)
func lrTableView(seletRightButton rightRow : Int,seletLeftButton leftRow : Int)
}
—-> 8.2 设置代理 :
weak var delegate : XFJTableViewDelegate?
—-> 8.3 实现协议中的方法
extension XFJCategoryViewController : XFJTableViewDelegate {
func lrTableView(seletLeftButton leftRow: Int) {
let catrgoryData = categories[leftRow]
let subCatroyData = catrgoryData.subcategories?.count
if subCatroyData == 0 {
NSNotificationCenter.defaultCenter().postNotificationName(XFJCategoryNotification, object : nil, userInfo: [XFJCategoryNotificationKey : catrgoryData])
}
}
func lrTableView(seletRightButton rightRow: Int, seletLeftButton leftRow: Int) {
let catrgoriesData = categories[leftRow]
let subCatrgoriesData = catrgoriesData.subcategories![rightRow]
NSNotificationCenter.defaultCenter().postNotificationName(XFJCategoryNotification, object : nil, userInfo: [XFJCategoryNotificationKey : catrgoriesData, XFJSubCategoryNotificationKey : subCatrgoriesData])
}
}
十三 通知
1 我们是如何将cell中对应的文字和图片显示到item中?
—-> 1.1 我们采用发送通知的方法将相关数据传递到item中
2 创建一个文件用来保存通知需要的参数
let XFJCategoryNotification = "XFJCategoryNotification"
let XFJCategoryNotificationKey = "XFJCategoryNotificationKey"
let XFJSubCategoryNotificationKey = "XFJSubCategoryNotificationKey"
let XFJDistrictNotification = "XFJDistrictNotification"
let XFJDistrictNotificationKey = "XFJDistrictNotificationKey"
let XFJSubDistrictNotificationKey = "XFJSubDistrictNotificationKey"
let XFJSortsNotification = "XFJSortsNotification"
let XFJSortsNotificationKey = "XFJSortsNotificationKey"
3 发送通知(分类模块)—-> 通知书写位置: 协议方法中
—-> 3.1 代码块一 :
func lrTableView(seletLeftButton leftRow: Int) {
let catrgoryData = categories[leftRow]
let subCatroyData = catrgoryData.subcategories?.count
if subCatroyData == 0 {
NSNotificationCenter.defaultCenter().postNotificationName(XFJCategoryNotification, object : nil, userInfo: [XFJCategoryNotificationKey : catrgoryData])
}
}
—-> 3.2 代码块二 :
func lrTableView(seletRightButton rightRow: Int, seletLeftButton leftRow: Int) {
let catrgoriesData = categories[leftRow]
let subCatrgoriesData = catrgoriesData.subcategories![rightRow]
NSNotificationCenter.defaultCenter().postNotificationName(XFJCategoryNotification, object : nil, userInfo: [XFJCategoryNotificationKey : catrgoriesData, XFJSubCategoryNotificationKey : subCatrgoriesData])
}
4 接收通知 : 虽然发送的通知是匿名通知,但是最好让能将数据提供给谁的一方接收通知,这样也方便设置相关数据
—-> 4.1 item是属于XFJHomeViewController类的,就让该类来接收通知,并实现通知中的方法
—-> 4.2 接收通知代码 :
NSNotificationCenter .defaultCenter ().addObserver (self , selector: "categoriesNotic:" , name: XFJCategoryNotification, object: nil )
NSNotificationCenter .defaultCenter ().addObserver (self , selector: "districtNotic:" , name: XFJDistrictNotification, object: nil )
NSNotificationCenter .defaultCenter ().addObserver (self , selector: "sortsNotic:" , name: XFJSortsNotification, object: nil )
—-> 4.3 移除通知 (重要点)
deinit {
NSNotificationCenter .defaultCenter ().removeObserver (self )
}
5 实现接收通知中的方法
—-> 5.2 分类
extension XFJHomeViewController {
@objc private func categoriesNotic (nic : NSNotification) {
let catrgoryData = nic.userInfo![XFJCategoryNotificationKey] as ! XFJCategories
let subCatroyData = nic.userInfo![XFJSubCategoryNotificationKey]
let categoryTopView = topItem?.customView as ! XFJTopView
let count = catrgoryData.subcategories?.count
if count == 0 {
categoryTopView.title = "美团"
categoryTopView.subtitle = catrgoryData.name
}else {
categoryTopView.title = catrgoryData.name
categoryTopView.subtitle = subCatroyData as ! String?
}
categoryTopView.normalName = catrgoryData.icon
categoryTopView.heightName = catrgoryData.highlighted_icon
categoryVC.dismissViewControllerAnimated(true ) { () -> Void in
self.setEnabled()
}
}
}
—-> 5.2 地区
extension XFJHomeViewController {
@objc private func districtNotic (disNic : NSNotification){
let districtData = disNic.userInfo![XFJDistrictNotificationKey] as ! XFJDistrict
let subDistrictData = disNic.userInfo![XFJSubDistrictNotificationKey]
let districtTopView = gzItem?.customView as ! XFJTopView
let count = districtData.subregions?.count
if count == 0 {
districtTopView.title = "美团"
districtTopView.subtitle = districtData.name
}else {
districtTopView.title = districtData.name
districtTopView.subtitle = subDistrictData as ! String?
}
districtVC.dismissViewControllerAnimated(true ) { () -> Void in
self.setEnabled()
}
}
}
—-> 5.3 排序
extension XFJHomeViewController {
@objc private func sortsNotic (sortsNic :NSNotification){
let sortsData = sortsNic.userInfo![XFJSortsNotificationKey] as ! XFJSorts
let sortsView = sortItem?.customView as ! XFJTopView
sortsView.subtitle = sortsData.label
sortsVC.dismissViewControllerAnimated(true ) { () -> Void in
self.setEnabled()
}
}
}
十四 细节处理
1 我们发现当运行在横屏的时候,没有问题,但是当在运行的之后转换为竖屏,导航条中item间的距离会被拉伸,这怎么解决呢?
—-> 1.1 产生这种现象的原因 : autoresizing导致屏幕旋转的时候,子控件跟随父控件的拉伸而拉伸
—-> 1.2 解决 :(如下图)—> 将正方形中间的红线去除就可以
2 当点击某个item的时候,发现再点击其他的item的时候,前一个item并没有退出,这样给用户的体验不好.我们通过代码来设置.
—-> 2.1 在监听用户点击的按钮中让所有的item都取消交互调用下面代码
@objc private func setDisabled () {
topItem?.enabled = false
gzItem?.enabled = false
sortItem?.enabled = false
}
—-> 2.2 在实现接收通知中调用的方法中,在poper被dismiss的时候,允许用户交互,调用下面代码来允许交互
@objc private func setEnabled () {
topItem?.enabled = true
gzItem?.enabled = true
sortItem?.enabled = true
}
—-> 2.3 在实现对item按钮的监听方法中,我们设置poper的代理为当前控制器(这里只说明一段代码,其它模块也是一样的)
categoryVC.popoverPresentationController?.delegate = self
—-> 2.4 实现代理方法
extension XFJHomeViewController : UIPopoverPresentationControllerDelegate {
func popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
setEnabled()
}
}
十五 总结
1 这篇博客我写的可能有点乱,代码太多,我也没办法具体到某一点,只是说了大概,介绍了协议可以实现这种情况的方法,同时对通知的运用也是捎带了,没有怎么细说.希望你们尽量看吧,看不懂的话,在给我私信吧.能帮到大家的,我一定帮忙.
2 最后还是那句话,大家如果觉得我写的博客还写的话,麻烦大家多多关注我的官方博客,谢谢!!!!