init(dict : [String : NSObject]) {
super.init()
setValuesForKeys(dict)
}
//forUnderfineKey重写这个方法是为了防止以后出现一个未定义key并为其赋值是出现错误
override func setValue(_ value: Any?, forUndefinedKey key: String) {
}
在FriendGroup中,由于FriendGroup本身就是一个模型,而这个模型里面需要添加一个Friends即是一个Friend的数组,是模型里面嵌套模型,不能通过直接用setValuesForKeys的方法来直接为friends属性赋值,因为在在plist中friends是一个数组其类型是 [[String : NSObject]] 这样的数组,而在我们FriendGroup中的friends属性的类型是 [friend] 是一个friend类型的数组 , 若是直接用 setValuesForKeys 则是直接将 [[String : NSObject]] 数组赋值给 [friend] 数组,类型不匹配 ,故会出错。
override func setValue(_ value: Any?, forKey key: String) {
//当遍历到的键为。friends 时 ,利用friend构造方法将其转化为 [friend] 数组
if key == "friends"{
let array = value as! [[String : NSObject]]
var friendItems = [Friend]()
for item in array{
let friend = Friend(dict: item)
friendItems.append(friend)
}
self.friends = friendItems
return
}
super.setValue(value, forKey: key)
}
lazy var itemList : [FriendGroup] = {
let plistPath = Bundle.main.path(forResource: "friends", ofType: "plist")!
let arrays = NSArray(contentsOfFile: plistPath)!
var dataList = [FriendGroup]()
for array in arrays{
let item = FriendGroup(dict: array as! [String : NSObject])
dataList.append(item)
}
return dataList
}()
根据最终效果图定义一个Cell类的模型(ViewController)
var friend : Friend?{
willSet{
self.imageView?.image = UIImage(named: (newValue?.icon ?? ""))
self.detailTextLabel?.text = newValue?.intro
self.textLabel?.text = newValue?.name
if newValue?.vip == 1 {
self.textLabel?.textColor = UIColor.red
}else{
self.textLabel?.textColor = UIColor.black
}
}
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
实现数据源协议 与 代理协议(ViewController)
//MARK: UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return itemList.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let group = self.itemList[section]
return group.groupBtnIsOpened == true ? (group.friends?.count ?? 0) : 0
}//用 dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) 得到重用cell 并对每个Cell中内容进行赋值
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FriendCell", for: indexPath) as! FriendCell
let currentFriend = self.itemList[indexPath.section].friends?[indexPath.row]
cell.friend = currentFriend
return cell
}
注意:对于纯代码书写的Cell在使用重用方法要去得到一个可重用的Cell的时候,需要在ViewDidLoad方法中在tableView中注册一个可重用标志符一样的Celloverride func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.register(FriendCell.self, forCellReuseIdentifier: "FriendCell")
}
观察最终效果图不难看出具有悬停功能显示分组名称和在线人数的视图应该为表头:即 UItableViewHeaderFooterView ,但是效果图中表头具有点击功能,
class ContactHeaderView: UITableViewHeaderFooterView {
var groupBtn : UIButton?
var onlineUsers : UILabel?
var friendGroup : FriendGroup?{
willSet{
groupBtn?.setTitle(newValue?.name, for: .normal)
onlineUsers?.text = "\(newValue?.online ?? 0)/\(newValue?.friends?.count ?? 0)"
//由于可重用表头视图的特殊性,必须判断每个视图的三角形形状
let imageView = self.groupBtn?.imageView
if !(newValue?.groupBtnIsOpened)! {
imageView?.transform = CGAffineTransform.identity
}else{
imageView?.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
}
}
}
override init(reuseIdentifier: String?) {
//表头初始化时并不会给自己设置frame,设置frame是在tableView中进行的
super.init(reuseIdentifier: reuseIdentifier)
self.setUpUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: -- layOutSubviews()方法的调用情况
/*
1.视图控件将要显示的时候会调用这个方法,并给这个视图控件的子控件设置frame
2.当视图控件的尺寸发生了变化的时候,会重新调用这个方法,来布局子视图的位置
3.当屏幕发生了选装的时候,也会调用这个方法,布局子控件
4.在这个方法中最好只是设置子控件的frame,而不进行有关值的设置
*/
override func layoutSubviews() {
super.layoutSubviews()
//MARK: -- 此处无法设置btn的frame为self.frame 因为由于self.frame的orgin的x,y是以父视图即tableview为参照的,不为零!
groupBtn?.frame = self.bounds
onlineUsers?.frame = CGRect(x: self.bounds.width - 80, y: 0, width: 72, height: self.bounds.height)
}
func setUpUI(){
self.groupBtn = UIButton()
self.onlineUsers = UILabel()
groupBtn?.setImage(UIImage(named: "buddy_header_arrow"), for: .normal)
groupBtn?.contentHorizontalAlignment = .left
groupBtn?.setBackgroundImage(UIImage(named:"buddy_header_bg"), for: .normal)
groupBtn?.setBackgroundImage(UIImage(named:"buddy_header_bg_highlighted"), for: .highlighted)
self.groupBtn?.setTitleColor(UIColor.black, for: .normal)
groupBtn?.contentEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 8)
groupBtn?.titleEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 0)
onlineUsers?.textAlignment = .right
self.contentView.addSubview(groupBtn!)
self.contentView.addSubview(onlineUsers!)
self.groupBtn?.addTarget(self, action: #selector(self.groupBtnDidOnClick), for: .touchUpInside)
//MARK: -- 设置图片的填充样式,避免图片在形变以后图片的大小比例被改变 , 并且设置超出部分不被父视图裁剪
self.groupBtn?.imageView?.contentMode = .center //居中显示会显示原有的尺寸
self.groupBtn?.imageView?.clipsToBounds = false
}
func groupBtnDidOnClick(){
//MARK: 三角形产生形变
let imageView = self.groupBtn?.imageView
if (self.friendGroup?.groupBtnIsOpened)! {
imageView?.transform = CGAffineTransform.identity //恢复形变
}else{
imageView?.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
}
self.friendGroup?.groupBtnIsOpened = !((self.friendGroup?.groupBtnIsOpened)!)
}
}
//在这个方法中获得一个自定一的表头视图(ViewController)
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let contactHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "ContactHeaderView") as? ContactHeaderView
//MARK : --必须实现代理
contactHeaderView?.contactHeaderViewDelegate = self
contactHeaderView?.friendGroup = self.itemList[section]
// print(contactHeaderView?.groupBtn?.frame)
return
//使用可重用的表头视图需要在ViewController中的ViewDidLoad()注册一个可重用的表头视图。 tableView.register(ContactHeaderView.self, forHeaderFooterViewReuseIdentifier: "ContactHeaderView")
7.由于点击事件是在 ContactHeaderView 中定义的 因此在点击事件之后要“回传” ViewController 让 ViewController 知道已经发生了点击事件并且让tableView重新实现数据源方法,此时可以使用两种方法来达成目的。
一、使用代理
代理设计模式本质是将本身一部分的功能让另外一个代理类去实现
设计一个表头类的代理 ContactHeaderViewDelegate
并且在ContactHeaderView类中定义一个属性
var contactHeaderViewDelegate :ContactHeaderViewDelegate?
并在按键点击事件groupBtnDidOnClick方法中添加self.contactHeaderViewDelegate?.headerViewDidOnClick(header:self)
在ViewController中遵守contactHeaderViewDelegate代理协议 ,并且设置表头视图的代理为VIewController
然后实现代理协议中的方法,在方法中使用tableView。reloadData()方法刷新表数据
二、使用通知
首先要在ViewController中定义个全局的通知名称ontactHeaderView
let reloadGroupNotificationName = NSNotification.Name(rawValue: "reloadGroupNotification")
然后在ViewController中监听一个通知。 //通知时现监听后注册的
NotificationCenter.default.addObserver(self, selector: #selector(self.reloadGroup(_:)), name: reloadGroupNotificationName, object: nil)
添加一个收到通知后执行的方法
func reloadGroup(_ notification : Notification){
let header = notification.object as! ContactHeaderView
let indexSet = NSIndexSet(index: header.tag)
self.tableView.reloadSections(indexSet as IndexSet, with: .automatic)
}
在ContactHeaderView的groupBtnDidOnClick中发出通知
NotificationCenter.default.post(name: reloadGroupNotificationName, object: self)