Swift3 QQ联系人列表

最终效果:

Swift3 QQ联系人列表_第1张图片

第一步:搭建一个基础的界面

  1. 首先在main.storyboard中拖拽一个tableViewController,并且身份设置检查器中的Class设置为已经继承UITableViewController的ViewController
  2. Swift3 QQ联系人列表_第2张图片
  3. 分析plist文件中的内容,并且根据内容创建模型

    创建一个Friend类、一个FriendGroup类,并且让它们都继承NSObject(可以利用KVC快速设置值)。
    在Friend类中有四个个属性 :icon、intro、name的三个String属性和一个vip的Int属性。
    利用       setValuesForKeys( _  keyedValues: [ String  :  Any ])   快速设置属性
    在Friend类中赋值:
    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]   数组,类型不匹配 ,故会出错。
    由于setValuesForKeys的原理是通过遍历键值 利用      func setValue ( _ value : Any?, forKey key : String ) 这个方法为每个key赋值,所以我们可以重写这个方法来保证通过KVC快速赋值不出错。

    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)
        }

  4. 在ViewController中通过懒加载来读取文件
    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
        }()
  5. 根据最终效果图定义一个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")
        }

     
     
     
     
     
     
  6. 实现数据源协议 与 代理协议(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中注册一个可重用标志符一样的Cell
    override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            tableView.register(FriendCell.self, forCellReuseIdentifier: "FriendCell")
    }

     
     
     
     
     
     
  7. 观察最终效果图不难看出具有悬停功能显示分组名称和在线人数的视图应该为表头:即  UItableViewHeaderFooterView ,但是效果图中表头具有点击功能,
    则此表头具有一个一个按钮,因此需要自定一个表头视图,表头具有一个按钮,一个Label显示标签
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)

    }

ContactHeaderViewgroupBtnDidOnClick中发出通知

NotificationCenter.default.post(name: reloadGroupNotificationName, object: self)
















































你可能感兴趣的:(swift学习历程)