13-發(fā)布微博

發(fā)布微博

課程目標

  1. 界面搭建
  2. 自定義文字輸入框
  3. 自定義顯示照片的View
  4. 底部 toolBar 自定義(UIStackView)
  5. 表情鍵盤(一個復雜的自定義 View 如何一步一步實現(xiàn)出來的)

界面搭建

導航欄內容

  • 標題視圖懶加載
// MARK: - 懶加載

/// 頂部標題視圖
private lazy var titleView: UILabel = {
    let label = UILabel()
    // 設置多行
    label.numberOfLines = 0
    // 字體大小
    label.font = UIFont.systemFontOfSize(14)
    // 文字居中
    label.textAlignment = NSTextAlignment.Center
    // 如果有用戶昵稱
    if let name = HMUserAccountViewModel.sharedInstance.userAccount?.name {
        // 初始化一個帶有屬性的文字
        var attr = NSMutableAttributedString(string: "發(fā)微博\n\(name)")
        // 獲取到要添加的屬性的范圍
        let range = (attr.string as NSString).rangeOfString(name)
        // 添加屬性
        attr.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(12), range: range)
        attr.addAttribute(NSForegroundColorAttributeName, value: UIColor.lightGrayColor() ,range: range)
        label.attributedText = attr
    }else{
        label.text = "發(fā)微博"
    }
    label.sizeToFit()
    return label
}()
  • 右邊按鈕懶加載
/// 右邊按鈕
private lazy var rightButton: UIButton = {
    let button = UIButton()

    // 添加點擊事件
    button.addTarget(self, action: "send", forControlEvents: UIControlEvents.TouchUpInside)

    // 設置文字屬性
    button.titleLabel?.font = UIFont.systemFontOfSize(13)
    button.setTitle("發(fā)送", forState: UIControlState.Normal)

    // 設置不同狀態(tài)的文字
    button.setTitleColor(UIColor.grayColor(), forState: UIControlState.Disabled)
    button.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)

    // 設置不同狀態(tài)的背景圖片
    button.setBackgroundImage(UIImage(named: "common_button_white_disable"), forState: UIControlState.Disabled)
    button.setBackgroundImage(UIImage(named: "common_button_orange"), forState: UIControlState.Normal)
    button.setBackgroundImage(UIImage(named: "common_button_orange_highlighted"), forState: UIControlState.Highlighted)

    // 設置寬高
    button.height = 30
    button.width = 44

    return button
}()
  • 實現(xiàn) send 方法
@objc private func send(){
    printLog("發(fā)送")
}
  • 設置導航欄內容
// 設置導航欄內容
private func setupNav(){
    // 設置左邊 Item
    navigationItem.leftBarButtonItem = UIBarButtonItem.item(title: "返回", target: self, action: "back")
    // 設置中間 titleView
    navigationItem.titleView = titleView
    // 設置右邊 Item
    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightButton)
    // 默認為不可用狀態(tài)
    navigationItem.rightBarButtonItem?.enabled = false
}

運行測試

文字輸入框

  1. 帶有占位文字
  2. 可以像 UITextView 一樣輸入多行
  3. 自定義一個輸入框繼承于 UITextView亩冬,向里面添加一個 label
  • 代碼實現(xiàn)
class HMTextView: UITextView {

    /// 重寫的是指定構造函數(shù)
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)

        // 添加占位控件
        addSubview(placeholderLabel)

        // 添加約束
        placeholderLabel.snp_makeConstraints { (make) -> Void in
            make.width.lessThanOrEqualTo(self.snp_width).offset(-10)
            make.leading.equalTo(self.snp_leading).offset(5)
            make.top.equalTo(self.snp_top).offset(8)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // 占位文字控件
    private lazy var placeholderLabel: UILabel = {
        let label = UILabel()
        // 設置文字顏色以及大小
        label.font = UIFont.systemFontOfSize(12)
        label.textColor = UIColor.lightGrayColor()
        label.text = "請輸入文字"

        // 多行
        label.numberOfLines = 0
        return label
    }()
}
  • 添加到 controller 中使用
// 懶加載控件
private lazy var textView: HMTextView = {
    let textView = HMTextView()
    return textView
}()

// setupUI 方法中添加子控件并設置約束

view.addSubview(textView)
textView.snp_makeConstraints { (make) -> Void in
    make.edges.equalTo(self.view.snp_edges)
}

運行測試

  • HMTextView 中提供給外界設置占位文字的屬性
// 添加 placeholder 屬性,代外界設置值
var placeholder: String? {
    didSet{
        placeholderLabel.text = placeholder
    }
}
  • 重寫 font 屬性茉盏,以讓占位文字與輸入的文字字體大小一樣
override var font: UIFont? {
    didSet{
        placeholderLabel.font = font
    }
}
  • 外界設置文字大小
textView.font = UIFont.systemFontOfSize(16)

運行測試:占位文字與輸入的文字一樣大

  • 監(jiān)聽文字改變的時候鉴未,去執(zhí)行占位控件的隱藏與顯示邏輯
// 監(jiān)聽文字改變的通知
NSNotificationCenter.defaultCenter().addObserver(self, selector: "textDidChange", name: UITextViewTextDidChangeNotification, object: self)
  • 文字改變之后調用的方法
/// 文字改變的時候會調用這個方法,當前如果有文字的話就隱藏占位 label
@objc private func textDidChange(){
    placeholderLabel.hidden = hasText()
}

運行測試鸠姨。注:監(jiān)聽文字改變在這個地方不要使用代理铜秆,因為自己一般不成為自己的代理。

底部 ToolBar 初始化

//設置約束
        toolBar.snp_makeConstraints { (make) -> Void in
            make.left.right.bottom.equalTo(self.view)
        }
        var items = [UIBarButtonItem]()
        //添加 UIBarButtonItem類型的對象到數(shù)據(jù)源數(shù)組中
        let itemSettings = [["imageName": "compose_toolbar_picture","actionName": "selectPicture"],
            ["imageName": "compose_mentionbutton_background"],
            ["imageName": "compose_trendbutton_background"],
            ["imageName": "compose_emoticonbutton_background", "actionName": "selectEmoticon"],
            ["imageName": "compose_add_background"]]

        for item in itemSettings {
            let imageName = item["imageName"]
            let btn = UIButton()
            btn.setImage(UIImage(named: imageName!), forState: .Normal)
            btn.setImage(UIImage(named: imageName! + "_highlighted"), forState: .Highlighted)
            btn.sizeToFit()
            if let actionName = item["actionName"] {
                btn.addTarget(self, action: Selector(actionName), forControlEvents: .TouchUpInside)
            }

            let barItem = UIBarButtonItem(customView: btn)
            //添加到數(shù)組中
            items.append(barItem)
            //添加彈簧類型的item  FlexibleSpace: 可伸縮的彈簧
            let space = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target: nil, action: nil)
            items.append(space)
        }

        items.removeLast()
        toolBar.items = items
  • HMComposeViewController 中懶加載控件
/// composeToolBar
private lazy var composeToolBar: HMComposeToolBar = HMComposeToolBar(frame: CGRectZero)
  • HMComposeViewControllersetupUI 方法中添加控件與約束
view.addSubview(composeToolBar)

// 添加約束
composeToolBar.snp_makeConstraints { (make) -> Void in
    make.bottom.equalTo(self.view.snp_bottom)
    make.width.equalTo(self.view.snp_width)
    make.height.equalTo(44)
}

底部 ToolBar 跟隨鍵盤移動

  • 監(jiān)聽鍵盤 frame 改變通知
// 監(jiān)聽鍵盤 frame 改變通知
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillChangeFrame:", name: UIKeyboardWillChangeFrameNotification, object: nil)
  • 注銷通知
deinit{
    NSNotificationCenter.defaultCenter().removeObserver(self)
}
  • 在鍵盤 frame 改變做更新約束的邏輯
/// 鍵盤 frame 改變通知調用的方法
@objc private func keyboardWillChangeFrame(noti: NSNotification){

    let endFrame = (noti.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()

    // 更新約束
    composeToolBar.snp_updateConstraints { (make) -> Void in
        make.bottom.equalTo(self.view.snp_bottom).offset(endFrame.origin.y - self.view.height)
    }

    UIView.animateWithDuration(0.25) { () -> Void in
        self.composeToolBar.layoutIfNeeded()
    }
}
  • 拖動 textView 的時候退下鍵盤:打開 textView 垂直方向彈簧效果讶迁,并設置代理
textView.alwaysBounceVertical = true
textView.delegate = self
  • 實現(xiàn)協(xié)議连茧,并實現(xiàn)協(xié)議方法
func scrollViewDidScroll(scrollView: UIScrollView) {
    self.view.endEditing(true)
}
  • 實現(xiàn) textViewDidChange 的方法,當textView有文字輸入的時候右邊按鈕可用
func textViewDidChange(textView: UITextView) {
    //設置占位的文本的隱藏或者顯示
        placeholderLabel.hidden = textView.hasText()
        //設置 發(fā)布按鈕的 交互 和不可交互狀態(tài)
        //有文本就允許交互
        navigationItem.rightBarButtonItem?.enabled = textView.hasText()
}

選擇照片

目標

  • 在獨立的項目中開發(fā)獨立的功能,或者直接切換根控制器
  • 開發(fā)完畢后再整合到現(xiàn)有項目中
  • 提高工作效率巍糯,專注開發(fā)品質 ??
  • 選擇照片
  • 重建控件布局

項目準備

  • 新建文件夾 PictureSelector
  • 新建 PictureSelectorViewController 繼承自 UICollectionViewController
  • AppDelegate 添加以下代碼
window?.rootViewController = PictureSelectorViewController()

運行測試

代碼實現(xiàn)

設置布局

  • 添加控制器構造函數(shù)啸驯,簡化外部調用
/// 可重用標識符號
private let WBPictureSelectorViewCellID = "WBPictureSelectorViewCellID"

/// 照片選擇控制器
class PictureSelectorViewController: UICollectionViewController {

    // MARK: - 構造函數(shù)
    init() {
        let layout = UICollectionViewFlowLayout()

        super.init(collectionViewLayout: layout)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // 注冊可重用 Cell
        self.collectionView!.registerClass(UICollectionViewCell.self,
            forCellWithReuseIdentifier: WBPictureSelectorViewCellID)
    }
}
  • 設置背景顏色
collectionView?.backgroundColor = UIColor.lightGrayColor()

注意在 CollectionViewController 中,collectionView 不是 view

  • 修改數(shù)據(jù)源方法
// MARK: 數(shù)據(jù)源
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 10
}

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(WBPictureSelectorViewCellID, forIndexPath: indexPath)

    // Configure the cell
    cell.backgroundColor = UIColor.redColor()

    return cell
}
  • 設置 cell 尺寸
init() {
    let layout = UICollectionViewFlowLayout()
    // 屏幕越大祟峦,顯示的內容應該越多
    layout.itemSize = CGSize(width: 80, height: 80)
    layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 0, right: 20)

    super.init(collectionViewLayout: layout)
}

從 iPhone 6 開始罚斗,就需要考慮越大的屏幕顯示越多的內容

自定義 Cell

  • 添加素材

  • 自定義 Cell

/// 照片選擇單元格
private class PictureSelectorViewCell: UICollectionViewCell {

    // MARK: - 構造函數(shù)
    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    /// 設置界面
    private func setupUI() {
        // 添加控件
        contentView.addSubview(addButton)
        contentView.addSubview(removeButton)

        // 自動布局
        addButton.snp_makeConstraints { (make) -> Void in
            make.edges.equalTo(contentView.snp_edges)
        }
        removeButton.snp_makeConstraints { (make) -> Void in
            make.top.equalTo(contentView.snp_top)
            make.right.equalTo(contentView.snp_right)
        }
    }

    // MARK: - 懶加載控件
    /// 添加按鈕
    private var addButton = UIButton(imageName: "compose_pic_add", backImageName: nil)
    /// 刪除按鈕
    private var removeButton = UIButton(imageName: "compose_photo_close", backImageName: nil)
}
  • 修改注冊的 Cell
// 注冊可重用 Cell
collectionView!.registerClass(PictureSelectorViewCell.self,
    forCellWithReuseIdentifier: WBPictureSelectorViewCellID)
  • 修改數(shù)據(jù)源
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(WBPictureSelectorViewCellID, forIndexPath: indexPath) as! PictureSelectorViewCell
  • 按鈕監(jiān)聽方法
// MARK: - 監(jiān)聽方法
/// 添加照片
@objc private func addPicture() {
    print("添加照片")
}

/// 刪除照片
@objc private func removePicture() {
    print("刪除照片")
}
  • 添加監(jiān)聽方法
// 監(jiān)聽方法
addButton.addTarget(self, action: "addPicture", forControlEvents: .TouchUpInside)
removeButton.addTarget(self, action: "removePicture", forControlEvents: .TouchUpInside)

利用代理傳遞按鈕點擊事件

  • 定義協(xié)議傳遞消息
/// 照片選擇單元格代理
private protocol PictureSelectorViewCellDelegate: NSObjectProtocol {
    /// 添加照片
    func pictureSelectorViewCellDidAdd(cell: PictureSelectorViewCell)
    /// 刪除照片
    func pictureSelectorViewCellDidRemove(cell: PictureSelectorViewCell)
}
  • 設置代理
/// 照片選擇代理
weak var pictureDelegate: PictureSelectorViewCellDelegate?
  • 修改監(jiān)聽方法
// MARK: - 監(jiān)聽方法
/// 添加照片
@objc private func addPicture() {
    pictureDelegate?.pictureSelectorViewCellDidAdd(self)
}

/// 刪除照片
@objc private func removePicture() {
    pictureDelegate?.pictureSelectorViewCellDidRemove(self)
}
  • extension 中實現(xiàn)協(xié)議方法
// MARK: - PictureSelectorViewCellDelegate
extension PictureSelectorViewController: PictureSelectorViewCellDelegate {
    private func pictureSelectorViewCellDidAdd(cell: PictureSelectorViewCell) {
        print("添加照片")
    }

    private func pictureSelectorViewCellDidRemove(cell: PictureSelectorViewCell) {
        print("刪除照片")
    }
}
  • 在數(shù)據(jù)源方法中設置代理
cell.pictureDelegate = self

注意:如果協(xié)議是私有的,那么協(xié)議方法也必須是私有的

選擇照片

  • 判斷是否支持訪問相冊
// 添加照片
private func pictureSelectorViewCellDidAdd(cell: PictureSelectorViewCell) {
    // 判斷是否支持照片選擇
    if !UIImagePickerController.isSourceTypeAvailable(.PhotoLibrary) {
        print("無法訪問照片庫")
        return
    }
}
  • 訪問相冊
// 訪問相冊
let picker = UIImagePickerController()
presentViewController(picker, animated: true, completion: nil)
  • 設置代理
// 設置代理
picker.delegate = self
  • 遵守協(xié)議并實現(xiàn)方法
// MARK: - UIImagePickerControllerDelegate, UINavigationControllerDelegate
extension PictureSelectorViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    /// 選中媒體代理方法
    ///
    /// - parameter picker: 照片選擇器
    /// - parameter info:   信息字典 allowsEditing = true 適合選擇頭像
    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
        print(info)

        dismissViewControllerAnimated(true, completion: nil)
    }
}

注意:一旦實現(xiàn)了代理方法宅楞,則需要用代碼 dismiss 控制器

設置圖片數(shù)據(jù)源

  • 定義照片數(shù)組
/// 照片數(shù)組
private lazy var pictures = [UIImage]()
  • 在代理方法中插入照片
let image = info[UIImagePickerControllerOriginalImage] as! UIImage

pictures.append(image)
collectionView?.reloadData()

dismissViewControllerAnimated(true, completion: nil)
  • 在 cell 中添加 image 屬性
/// 照片
private var image: UIImage? {
    didSet {
        addButton.setImage(image, forState: .Normal)
    }
}
  • 修改數(shù)據(jù)源中的圖像數(shù)量函數(shù)
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return pictures.count + 1
}

保證末尾有一個加號按鈕添加照片

  • 在數(shù)據(jù)源方法中設置圖像
cell.image = (indexPath.item == pictures.count) ? nil : pictures[indexPath.item]
  • 擴展 image 屬性的 didSet 函數(shù)
/// 照片
private var image: UIImage? {
    didSet {
        addButton.setImage(image ?? UIImage(named: "compose_pic_add"), forState: .Normal)
        addButton.setImage(image ?? UIImage(named: "compose_pic_add_highlighted"), forState: .Highlighted)
    }
}

細節(jié)處理

記錄用戶點擊按鈕的索引

  • 定義當前選中照片索引
/// 當前選中照片索引
private var currentIndex = 0
  • 在代理方法中記錄當前用戶點擊 cell 的索引
// 記錄當前用戶選中索引
currentIndex = collectionView!.indexPathForCell(cell)!.item
  • 在照片選擇控制器的代理方法中設置對應的圖像
if currentIndex < pictures.count {
    pictures[currentIndex] = image
} else {
    pictures.append(image)
}
collectionView?.reloadData()

設置照片填充模式

// 設置照片填充模式
addButton.imageView?.contentMode = .ScaleAspectFill

刪除照片

  • 刪除照片操作
// 刪除照片
private func pictureSelectorViewCellDidRemove(cell: PictureSelectorViewCell) {

    guard let indexPath = collectionView?.indexPathForCell(cell) else {
        return
    }

    pictures.removeAtIndex(indexPath.item)
    collectionView?.deleteItemsAtIndexPaths([indexPath])
}
  • 默認隱藏刪除按鈕
/// 照片
private var image: UIImage? {
    didSet {
        addButton.setImage(image ?? UIImage(named: "compose_pic_add"), forState: .Normal)
        addButton.setImage(image ?? UIImage(named: "compose_pic_add_highlighted"), forState: .Highlighted)

        removeButton.hidden = (image == nil)
    }
}

設置最多選擇照片數(shù)量

  • 定義最多照片常量
/// 最大照片數(shù)量
private let WBPictureSelectorViewMaxPictureCount = 9
  • 修改數(shù)據(jù)源方法
// MARK: 數(shù)據(jù)源
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return pictures.count + (pictures.count < WBPictureSelectorViewMaxPictureCount ? 1 : 0)
}

內存處理

  • 縮放圖片
extension UIImage {

    /// 將圖像縮放到指定寬度
    ///
    /// - parameter width: 指定寬度针姿,如果圖片尺寸比指定寬度小袱吆,直接返回
    ///
    /// - returns: 等比例縮放后的圖像
    func scaleImage(width: CGFloat) -> UIImage {

        // 1. 判斷圖像尺寸
        if size.width < width {
            return self
        }

        // 2. 計算比例
        let height = size.height * width / size.width
        let rect = CGRect(x: 0, y: 0, width: width, height: height)

        // 3. 核心繪圖
        // 1> 開啟上下文
        UIGraphicsBeginImageContext(rect.size)

        // 2> 繪圖
        drawInRect(rect)

        // 3> 獲得結果
        let result = UIGraphicsGetImageFromCurrentImageContext()

        // 4> 關閉上下文
        UIGraphicsEndImageContext()

        // 5> 返回結果
        return result
    }
}

  • 修改照片選擇控制器代理方法
/// 選中媒體代理方法
///
/// - parameter picker: 照片選擇器
/// - parameter info:   信息字典 allowsEditing = true 適合選擇頭像
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {

    let image = info[UIImagePickerControllerOriginalImage] as! UIImage
    let scaleImage = image.scaleImage(300)

    if currentIndex < pictures.count {
        pictures[currentIndex] = scaleImage
    } else {
        pictures.append(scaleImage)
    }
    collectionView?.reloadData()

    dismissViewControllerAnimated(true, completion: nil)
}

整合照片選擇控制器

準備 文件

  • PhotoSelector 拖拽至項目
  • UIImage+Extension.swift 拖拽至項目

整合照片選擇控制器

  • 定義控制器屬性
/// 照片選擇控制器
private lazy var pictureSelectorViewController = PictureSelectorViewController()
  • 準備照片視圖
/// 準備照片視圖
private func preparePictureView() {
    // 添加視圖
    view.addSubview(pictureSelectorViewController.view)

    // 自動布局
    pictureSelectorViewController.view.snp_makeConstraints { (make) -> Void in
        make.bottom.equalTo(view.snp_bottom)
        make.left.equalTo(view.snp_left)
        make.right.equalTo(view.snp_right)
        make.height.equalTo(view.snp_height).multipliedBy(0.6)
    }
}

運行測試,發(fā)現(xiàn)選中照片結束后距淫,提示錯誤:

Presenting view controllers on detached view controllers is discouraged

  • 添加子控制器
// 添加子控制器
addChildViewController(pictureSelectorViewController)
  • 修改照片選擇視圖層次
// 添加視圖
view.insertSubview(pictureSelectorViewController.view, belowSubview: toolbar)

運行會發(fā)現(xiàn)照片選擇視圖跑到了 textView 和 toolBar 的后面

重建控件布局

  • 修改照片選擇視圖的高度
make.height.equalTo(0)
  • 在選擇照片監(jiān)聽方法中重建控件索引
// 選擇照片
@objc private func selectPhoto() {

    if pictureSelectorViewController.view.bounds.height == 0 {
        // 修改布局高度
        pictureSelectorViewController.view.snp_remakeConstraints { (make) -> Void in
            make.bottom.equalTo(view.snp_bottom)
            make.left.equalTo(view.snp_left)
            make.right.equalTo(view.snp_right)
            make.height.equalTo(view.snp_height).multipliedBy(0.6)
        }
        // 修改文本視圖的底部約束
        textView.snp_remakeConstraints { (make) -> Void in
            make.top.equalTo(view.snp_top)
            make.left.equalTo(view.snp_left)
            make.right.equalTo(view.snp_right)
            make.bottom.equalTo(pictureSelectorViewController.view.snp_top)
        }

        UIView.animateWithDuration(0.25, animations: { () -> Void in
            self.view.layoutIfNeeded()
        })
    }
}
  • 關閉鍵盤
textView.resignFirstResponder()

運行測試

  • 調整 viewDidAppear 如果已經(jīng)顯示照片選擇視圖绞绒,則不再激活鍵盤
override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    if pictureSelectorViewController.imageList.count == 0 {
        textView.becomeFirstResponder()
    }
}

發(fā)布微博

發(fā)布文字微博

接口定義

參數(shù) 說明
access_token 采用OAuth授權方式為必填參數(shù),其他授權方式不需要此參數(shù)榕暇,OAuth授權后獲得
status 要發(fā)布的微博文本內容蓬衡,必須做URLencode,內容不超過140個漢字

連續(xù)兩次發(fā)布的微博不可以重復

  • HMNetworkTools 中添加 update 方法
/// 發(fā)布文字微博
func update(accessToken: String, text: String, finished: HMRequestCallBack){
    // 請求地址
    let urlString = "https://api.weibo.com/2/statuses/update.json"
    // 請求參數(shù)
    let params = [
        "access_token": accessToken,
        "status": text
    ]
    request(.POST, url: urlString, params: params, finished: finished)
}
  • HMComposeViewController 中調用
/// 發(fā)送文字微博
private func update(){
    HMNetworkTools.shareTools.update(HMUserAccountViewModel.sharedUserAccount.accessToken!, text: textView.emoticonText) { (result, error) -> () in
        if error != nil {
            print(error)
            SVProgressHUD.showErrorWithStatus("發(fā)表失敗")
            return
        }
        print(result)
        SVProgressHUD.showSuccessWithStatus("發(fā)表成功")
    }
}

發(fā)布圖片微博

接口定義

文檔地址

http://open.weibo.com/wiki/2/statuses/upload

接口地址

https://upload.api.weibo.com/2/statuses/upload.json

HTTP 請求方式

  • POST

請求參數(shù)

參數(shù) 說明
access_token 采用OAuth授權方式為必填參數(shù)彤枢,其他授權方式不需要此參數(shù)狰晚,OAuth授權后獲得
status 要發(fā)布的微博文本內容,必須做URLencode堂污,內容不超過140個漢字
pic 要上傳的圖片家肯,僅支持JPEG龄砰、GIF狠角、PNG格式鼻吮,圖片大小小于5M

請求必須用POST方式提交,并且注意采用multipart/form-data編碼方式

代碼實現(xiàn)

  • HMNetworkTools 中添加上傳圖片的方法
func upload(accessToken: String, text: String, image: UIImage, finished: HMRequestCallBack){
    // url
    let url = "https://upload.api.weibo.com/2/statuses/upload.json"

    let params = [
        "access_token": accessToken,
        "status": text
    ]

    POST(url, parameters: params, constructingBodyWithBlock: { (formData) -> Void in
        let data = UIImagePNGRepresentation(image)!
        /**
            1. data: 二進制數(shù)據(jù)
            2. name: 服務器定義的字段名稱
            3. fileName: 保存在服務器的文件名,通炒移可以亂寫,服務器自己會做處理
            4. mimeType: 告訴服務器文件類型
                - 大類型 / 小類型
                    image/jepg, image/png
                    text/plain, text/html
                - 如果不想告訴服務器準確類型:
                    application/octet-stream

        */
        formData.appendPartWithFileData(data, name: "pic", fileName: "xxaaa", mimeType: "image/jpeg")
        }, success: { (_, response) -> Void in
            guard let dict = response as? [String: AnyObject] else {
                // 如果不是字典她混,返回錯誤
                let error = NSError(domain: "com.itheima.error", code: -1001, userInfo: ["message": "The response data type isn't a [String: AnyObject]"])
                finished(result: nil, error: error)
                return
            }
            finished(result: dict, error: nil)
        }) { (_, error) -> Void in
            finished(result: nil, error: error)
    }
}

表情鍵盤

在實際開發(fā)中對于比較獨立的模塊,可以直接新建一個項目,在新項目上演練,測試,等待模塊開發(fā)完畢之后再移植到項目中,方便項目的測試

實現(xiàn)效果

表情鍵盤效果圖.png.jpeg

實現(xiàn)思路

  1. 從最簡單的 View 開始做起
  2. 底部切換表情類型的 View 可以使用 UIStackView 來實現(xiàn)
  3. 表情顯示的 View 可以使用 UICollectionView 實現(xiàn)
  4. 每一頁表情對應一個 Cell 來表示
  5. 每一種表情對應 UICollectionView 中的一組

自定義 HMEmoticonKeyboard

  • 自定義 HMEmoticonKeyboard 繼承于 UIView
class HMEmoticonKeyboard: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    private func setupUI(){
        // 設置背景顏色
        backgroundColor = UIColor(patternImage: UIImage(named: "emoticon_keyboard_background")!)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
  • HMComposeViewController 中添加切換鍵盤的方法 switchKeyboard
/// 切換鍵盤
private func switchKeyboard(){
}
  • 在點擊 HMComposeToolBar 上的表情按鈕的時候調用方法
// MARK: - HMComposeToolBarDelegate
func composeToolBarButtonDidSelected(type: ComposeToolBarButtonType) {
    switch type {
    case ...
    case .Emoticon:
        switchKeyboard()
    }
}
  • 懶加載鍵盤
/// 鍵盤
private lazy var emoticonKeyboard: HMEmoticonKeyboard = {
    let keyboard = HMEmoticonKeyboard()
    keyboard.size = CGSizeMake(SCREENW, 216)
    return keyboard
}()

表情類型切換視圖

  • 自定義 HMEmoticonToolBar
class HMEmoticonToolBar: UIStackView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        // 設置布局方向
        axis = UILayoutConstraintAxis.Horizontal
        // 設置子控件的分布方式 -> 填充拳话,大小相等
        distribution = UIStackViewDistribution.FillEqually

        setupUI()
    }

    private func setupUI(){
        // 添加 4 個按鈕
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
  • 提供添加 3 個按鈕的方法
private func addChildItem(title: String, bgImageName: String) {
    let button = UIButton()

    // 設置文字以及字體大小
    button.titleLabel?.font = UIFont.systemFontOfSize(14)
    button.setTitle(title, forState: UIControlState.Normal)

    // 設置不同狀態(tài)的背景圖片
    button.setBackgroundImage(UIImage(named: "\(bgImageName)_normal"), forState: UIControlState.Normal)
    button.setBackgroundImage(UIImage(named: "\(bgImageName)_selected"), forState: UIControlState.Selected)

    // 設置不同狀態(tài)的文字顏色
    button.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
    button.setTitleColor(UIColor.grayColor(), forState: UIControlState.Selected)

    addArrangedSubview(button)
}
  • setupUI 中添加按鈕
private func setupUI(){
    // 添加 3 個按鈕
    addChildItem("默認", bgImageName: "compose_emotion_table_right")
    addChildItem("Emoji", bgImageName: "compose_emotion_table_mid")
    addChildItem("浪小花", bgImageName: "compose_emotion_table_right")
}
  • HMEmoiticonKeyboard 中添加 HMEmoticonToolBar
// 懶加載控件
/// 底部切換表情類型的toolBar
private lazy var emoticonToolBar: HMEmoticonToolBar = HMEmoticonToolBar(frame: CGRectZero)
  • setupUI 方法中添加控件以及約束
// 添加子控件
addSubview(emoticonToolBar)

// 添加約束
emoticonToolBar.snp_makeConstraints { (make) -> Void in
    make.bottom.equalTo(self.snp_bottom)
    make.leading.equalTo(self.snp_leading)
    make.right.equalTo(self.snp_right)
    make.height.equalTo(37)
}

運行測試:按鈕背景圖片沒有拉伸方式有問題

  • 更改拉伸方式:點擊Assets.xcassets --> 選中對應的背景圖片 --> 查看右邊屬性面板 --> 在 Slicing 區(qū)設置 SlicesHorizontal,設置 centerStretches

    • 有些情況下 Xcode 會出 Bug夕玩,需要設置 SlicesHorizontal And Vertical
  • 監(jiān)聽子按鈕點擊

private func addChildItem(title: String, bgImageName: String) {
    let button = UIButton()
    // 添加點擊事件
    button.addTarget(self, action: "childButtonClick:", forControlEvents: UIControlEvents.TouchUpInside)
    ...
}
  • 實現(xiàn)響應方法
/// 子控件點擊
///
/// - parameter button: 當前點擊的 button
@objc private func childButtonClick(button: UIButton){
    // 按鈕點擊方法
}
  • 實現(xiàn)選中一個按鈕的時候取消選中之前的按鈕

    • 記錄當前選中的按鈕
    • 當點擊下一個按鈕的時候取消選中記錄的按鈕你弦,選中當前按鈕
    • 再次記錄當前選中的按鈕
  • 定義 currentSelectedButton 屬性記錄當前選中的按鈕

/// 當前選中的按鈕
var currentSelectedButton: UIButton?
  • childButtonClick 實現(xiàn)按鈕點擊邏輯
/// 子按鈕點擊
///
/// - parameter button: 當前點擊的 button
@objc private func childButtonClick(button: UIButton){

    // 如果當前選中的 button 與即將要選中的button相同,則直接返回
    if button == currentSelectedButton {
        return
    }
    // 取消選中之前的
    currentSelectedButton?.selected = false
    // 選中現(xiàn)在點擊的
    button.selected = true
    // 再次記錄現(xiàn)在選的按鈕
    currentSelectedButton = button
}

運行測試

  • 按鈕點擊的時候需要讓 HMEmoticonKeyboard 知道哪一個按鈕點擊了
    • 給按鈕添加tag
    • 添加協(xié)議燎孟,在按鈕點擊的時候調用協(xié)議方法
  • 更新 setupUI 方法中調用方式
private func setupUI(){
    // 添加 3 個按鈕
    addChildItem("默認", bgImageName: "compose_emotion_table_left", index: 0)
    addChildItem("Emoji", bgImageName: "compose_emotion_table_mid", index: 1)
    addChildItem("浪小花", bgImageName: "compose_emotion_table_right", index: 2)
}
  • 定義協(xié)議
protocol HMEmoticonToolBarDelegate: NSObjectProtocol {
    func emoticonToolBarButtonDidSelected(index: Int)
}
  • 添加代理屬性
/// 代理
weak var delegate: HMEmoticonToolBarDelegate?
  • 在按鈕點擊的時候調用代理身上的方法
/// 子按鈕點擊
///
/// - parameter button: 當前點擊的 button
@objc private func childButtonClick(button: UIButton){
    ...
    // 調用代理方法
    delegate?.emoticonToolBarButtonDidSelected(button.tag)
}
  • HMEmoticonKeyboard 繼承 HMEmoticonToolBarDelegate 協(xié)議
class HMEmoticonKeyboard: UIView, HMEmoticonToolBarDelegate {
    ...
}
  • HMEmoticonKeyboard 中設置 HMEmoticonToolBar 的代理為自己
/// 底部切換表情類型的toolBar
private lazy var emoticonToolBar: HMEmoticonToolBar = {
    let toolBar = HMEmoticonToolBar(frame: CGRectZero)
    toolBar.delegate = self
    return toolBar
}()
  • 實現(xiàn)代理方法
// MARK: - HMEmoticonToolBarDelegate
func emoticonToolBarButtonDidSelected(index: Int) {
    print(index)
}

運行測試

表情顯示視圖

  • HMEmoticonKeyboard 中添加 UICollectionView
/// 懶加載控件
/// 顯示表情的視圖
private lazy var emoticonCollectionView: UICollectionView = {
    let collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: UICollectionViewFlowLayout())
    collectionView.backgroundColor = RandomColor()
    return collectionView
}()
  • 添加控件與約束
// 添加子控件
addSubview(emoticonCollectionView)
// 添加約束
emoticonCollectionView.snp_makeConstraints { (make) -> Void in
    make.width.equalTo(self.snp_width)
    make.top.equalTo(self.snp_top)
    make.bottom.equalTo(self.emoticonToolBar.snp_top)
    make.leading.equalTo(self)
}

運行測試

  • 設置 emoticonCollectionView 的數(shù)據(jù)源以及注冊 cell
/// 顯示表情的視圖
private lazy var emoticonCollectionView: UICollectionView = {
    let collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: UICollectionViewFlowLayout())
    collectionView.backgroundColor = RandomColor()
    // 設置數(shù)據(jù)源
    collectionView.dataSource = self
    // 注冊 cell
    collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: HMEmoticonKeyboardCellId)
    return collectionView
}()
  • 繼承協(xié)議
class HMEmoticonKeyboard: UIView, HMEmoticonToolBarDelegate, UICollectionViewDataSource {
    ...
}
  • 實現(xiàn)協(xié)議方法
extension HMEmoticonKeyboard {

    /// 返回表情一共有多少頁
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // 為了測試禽作,先默認返回10個
        return 10
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(HMEmoticonKeyboardCellId, forIndexPath: indexPath)
        // 測試返回隨機顏色
        cell.backgroundColor = RandomColor()
        return cell
    }
}

運行測試:

  • 調整每一個 cell 的大小
    • 因為每一個 cell 的大小與 collectionView 一樣大
    • 而調整完 collectionView 大小要調用的方法就是 layoutSubviews
    • 所以在 layoutSubviews 調整每一個 cell 的大小
override func layoutSubviews() {
    super.layoutSubviews()

    // 設置每一個 cell 的大小
    let layout = emoticonCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
    layout.itemSize = emoticonCollectionView.size
}

運行測試:每一行之間有間距,而且滾動方向不對

  • 在初始化 emoticonCollectionView 的時候設置滾動方向以及 cell 間距 (UICollectionViewFlowLayout 身上的屬性)
/// 顯示表情的視圖
private lazy var emoticonCollectionView: UICollectionView = {
    let layout = UICollectionViewFlowLayout()
    // 設置滾動方向:水平滾動
    layout.scrollDirection = UICollectionViewScrollDirection.Horizontal
    // 設置每一個 cell 之間的間距
    layout.minimumLineSpacing = 0

    let collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: layout)
    collectionView.backgroundColor = RandomColor()
    // 設置數(shù)據(jù)源
    collectionView.dataSource = self
    // 注冊 cell
    collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: HMEmoticonKeyboardCellId)
    return collectionView
}()
  • 開啟分頁 & 隱藏水平滾動條 & 關閉彈簧效果 (UIScrollView 身上的屬性)
// 開啟分頁 & 隱藏水平滾動條
collectionView.pagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
// 關閉彈簧效果
collectionView.bounces = false
  • 自定義 HMEmoticonPageCell 為表情鍵盤的 Cell
class HMEmoticonPageCell: UICollectionViewCell {

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupUI(){
        backgroundColor = RandomColor()
    }
}
  • 替換注冊的 cell
// 注冊 cell
collectionView.registerClass(HMEmoticonPageCell.self, forCellWithReuseIdentifier: HMEmoticonKeyboardCellId)
  • 為了測試效果揩页,在 HMEmoticonPageCell中添加一個測試的 label
class HMEmoticonPageCell: UICollectionViewCell {

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    private func setupUI(){

        contentView.addSubview(label)

        label.snp_makeConstraints { (make) -> Void in
            make.center.equalTo(contentView.snp_center)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    /// 測試用的 label
    private lazy var label: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFontOfSize(35)
        return label
    }()
}
  • 提供 indexPath: NSIndexPath 屬性旷偿,顯示當前滾動到哪個位置
var indexPath: NSIndexPath? {
    didSet{
        label.text = "第\(indexPath!.section)組,第\(indexPath!.item)頁"
    }
}
  • 在返回 cell 的時候設置 indexPath
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(HMEmoticonKeyboardCellId, forIndexPath: indexPath) as! HMEmoticonPageCell
    // 測試返回隨機顏色
    cell.backgroundColor = RandomColor()
    cell.indexPath = indexPath
    return cell
}

運行測試

讀取表情數(shù)據(jù)

  • 在iTunesStore中下載最新版本的新浪微博安裝包,獲取素材文件
  • 支持iOS6.0的項目的素材是可以直接獲取的但是如果不支持iOS6.0的設置是無法獲取素材的,建議保存一些有些App的素材,大部分是沒有版權的

三種文件夾的區(qū)別

  • 黃色文件夾: 編譯后爆侣,資源文件在 mainBundle 中萍程,源代碼程序需要通過這種方式拖拽添加, 效率高
  • 藍色文件夾:編譯后,資源文件在 mainBundle 中的對應文件夾中兔仰,游戲文件的素材一般通過這種方式拖拽添加,用于換膚應用,游戲場景, 不同路徑下的相同文件名

*白色 Bundle:編譯后茫负,資源文件在 mainBundle 中仍然以包的形式存在,可以路徑形式訪問,拖拽文件更簡單,主要用于第三方框架包裝資源素材

  • 新鍵 HMEmoticonManager 類乎赴,里面加載表情數(shù)據(jù),對外提供表情數(shù)據(jù),和一些配置信息
class HMEmoticonManager: NSObject {
static let shareEmoticonManager: EmoticonManager = EmoticonManager()

    lazy var packages: [EmoticonPackages] = [EmoticonPackages]()

    private override init() {
        super.init()
        loadEmoticons()
    }

    func loadEmoticons() {
        let path = NSBundle.mainBundle().pathForResource("emoticons.plist", ofType: nil, inDirectory: "Emoticons.bundle")!
        let dict = NSDictionary(contentsOfFile: path) as! [String : AnyObject]

        let array = dict["packages"] as! [[String : AnyObject]]

        for item in array {
            //獲取id
            let id = item["id"] as! String
            loadPackages(id)
        }
    }

    private func loadPackages(id: String) {
        //通過id獲取 分組名稱
        let path = NSBundle.mainBundle().pathForResource("info.plist", ofType: nil, inDirectory: "Emoticons.bundle/" + id)!
        //通過分組名稱加載分組中的 info.plist 文件
        let dict = NSDictionary(contentsOfFile: path)!
        let group_name_cn = dict["group_name_cn"] as! String
        //獲取表情數(shù)據(jù)
        let array = dict["emoticons"] as! [[String : String]]

        let p = EmoticonPackages(id: id, title: group_name_cn,array: array)
        packages.append(p)
    }
}
  • 定義表情模型
class HMEmoticon: NSObject {

    /// 表情文字描述
    var chs: String?
    /// 表情圖片名字 (僅對圖片表情有效)
    var png: String?


    /// Emoji表情的 code
    var code: String?
    /// 是否是Emoji表情
    var isEmoji: Bool = false

    init(dictionary: [String: AnyObject]) {
        super.init()
        setValuesForKeysWithDictionary(dictionary)
    }

    override func setValue(value: AnyObject?, forUndefinedKey key: String) {}
}
  • 與界面對應需要向上抽取一個package對應的模型
  • title toolBar中顯示的title
  • sectionEmoticon toolBar每個按鈕對應的大數(shù)組
class EmoticonPackages: NSObject {
    var title: String?
    lazy var sectionEmoticon = [[Emoticon]]()
}
  • EmoticonPackages添加構造方法
init(id: String, title: String, array: [[String : String]]) {
        super.init()
        self.title = title

        //遍歷數(shù)組 轉換為模型 再將模型轉換為

        var emoticonArray: [Emoticon] = [Emoticon]()
        for item in array {
            let e = Emoticon(id: id, dict: item)
            emoticonArray.append(e)
        }
    }
  • 處理數(shù)據(jù), 將模型數(shù)組[HMEmoticon]類型處理為 [[HMEmoticon]]
private func sectionEmoticonArray(array: [Emoticon]) -> [[Emoticon]]{
        //獲取表情數(shù)量 這些數(shù)組每頁21個 能裝多少組
        let pageCount = (array.count - 1 ) / 21 + 1

        var sectionEm = [[Emoticon]]()
        for i in 0..<pageCount {
            //每頁截取從大數(shù)組中截取21個表情   不足21個的會造成數(shù)組索引越界
            let loc = i * SectionEmoticonCount
            var length = SectionEmoticonCount
            if loc + length > array.count {
                length = array.count - loc
            }
            let subArray = (array as NSArray).subarrayWithRange(NSRange(location: loc, length: length))
            sectionEm.append(subArray as! [Emoticon])
        }
        return sectionEm
    }
  • 返回 HMEmoticonKeyboardcollectionView 所需要的數(shù)據(jù)
    • 數(shù)據(jù)結構分析如下
表情數(shù)據(jù)結構分析.png

運行測試

底部 HMEmoticonToolBar 與 顯示表情的 collectionView 聯(lián)動

點擊底部表情類型按鈕忍法,切換到對應表情

  • HMEmoticonToolBar 的代表方法中使用 collectionView 滾動到對應的組
// MARK: - HMEmoticonToolBarDelegate
func emoticonToolBarButtonDidSelected(index: Int) {
    print(index)
            let indexPath = NSIndexPath(forItem: 0, inSection: index)
            self.collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Left, animated: false)
}

運行測試

當滾動到某種表情頁時置吓,選中對應表情按鈕

實現(xiàn)思路

  • 實現(xiàn)監(jiān)聽 collectionView 滾動的位置
    • scrollView 的代理方法 scrollViewDidScroll
  • 獲取到 collectionView 的中心
  • 定位滾動到對應cell的中心點
  • 判斷當前屏幕中顯示的兩個cell 誰的frame包含了目標中心點
  • 循環(huán)當前屏幕中顯示的cell對應的item(最多兩個)
  • 當cell的frame 包含該了中心點的時候 就更新頁面信息

實現(xiàn)代碼

 func scrollViewDidScroll(scrollView: UIScrollView) {
        //確定cell的中心點
        var center = collectionView.center
        center.x = center.x + collectionView.contentOffset.x
        let indexPaths = collectionView.indexPathsForVisibleItems()

        for indexPath in indexPaths {
            //最多兩個  最少一個
            let cell = collectionView.cellForItemAtIndexPath(indexPath)!
            if cell.frame.contains(center) {
                toolBar.setBtnSelected(indexPath.section)
                updatePageControlData(indexPath)
            }
        }
    }

運行測試

  • HMEmoticonToolBar 中提示 selectButtonWithSection 方法供選中按鈕方法
/// 通過 section 選中某一個按鈕
func selectButtonWithSection(section: Int) {
    // 通過 section 獲取到對應的 button,讓其選中
    let button = viewWithTag(section)! as! UIButton
    childButtonClick(button)
}
  • HMEmoticonKeyboardscrollViewDidScroll 方法中調用此方法
func scrollViewDidScroll(scrollView: UIScrollView) {
    ...
    emoticonToolBar.selectButtonWithSection(section)
}

運行測試:崩潰 Could not cast value of type 'WeiBo.HMEmoticonToolBar' (0x10bad2dc0) to 'UIButton' (0x10dcc2320). 原因是當前 section 為 0缔赠,調用 viewWithTag 方法取到的是 toolBar 自己衍锚,強轉出錯,所以把每一個按鈕對應的枚舉值給定一個基數(shù)

  • 在調用 viewWithTag 方法的時候添加一個基數(shù)
/// 通過 section 選中某一個按鈕
func selectButtonWithSection(section: Int) {
    // 通過 section 獲取到對應的 button嗤堰,讓其選中
    let button = viewWithTag(section + 1000)! as! UIButton
    childButtonClick(button)
}

運行測試:在從第0組滑動過一半的時候戴质,很快速的切換到第1組表情去了,原因就是調用 childButtonClick 方法會執(zhí)行代理方法踢匣,代理方法會回調滾動 collectionView告匠,所以在這個地方只需要切換 button 選中狀態(tài)

  • 提取按鈕切換狀態(tài)的方法 changeButtonState
/// 改變按鈕狀態(tài),把當前選中的 button 取消選中离唬,把傳入的 button 設置選中
private func changeButtonState(button: UIButton){
    // 如果當前選中的 button 與即將要選中的button相同后专,則直接返回
    if button == currentSelectedButton {
        return
    }
    // 取消選中之前的
    currentSelectedButton?.selected = false
    // 選中現(xiàn)在點擊的
    button.selected = true
    // 再次記錄現(xiàn)在選的按鈕
    currentSelectedButton = button
}
  • selectButtonWithSection 調用 changeButtonState 方法
/// 通過 section 選中某一個按鈕
func selectButtonWithSection(section: Int) {
    // 通過 section 獲取到對應的 button,讓其選中
    let button = viewWithTag(section + 1000)! as! UIButton
    // 更改按鈕選中狀態(tài)
    changeButtonState(button)
}
  • 替換 childButtonClick 方法內實現(xiàn)
/// 子按鈕點擊
///
/// - parameter button: 當前點擊的 button
@objc private func childButtonClick(button: UIButton){
    // 如果當前選中的 button 與即將要選中的button相同输莺,則直接返回
    if button == currentSelectedButton {
        return
    }
    // 改變按鈕狀態(tài)
    changeButtonState(button)

    // 調用代理方法
    delegate?.emoticonToolBarButtonDidSelected(HMEmoticonType(rawValue: button.tag)!)
}

運行測試

表情顯示

設置子控件

  • HMEmoticonPageCell 中添加 20 個按鈕表情按鈕
/// 添加表情按鈕
private func addEmoticonButtons(){
    et leftMargin: CGFloat = 5
        let bottomMargin: CGFloat = 30
        let bW = (UIScreen.mainScreen().bounds.width - 2 * leftMargin) / CGFloat(EmoticonColCount)
        let bH = (bounds.height - bottomMargin) / CGFloat(EmoticonRowCount)
        for i in 0..<SectionEmoticonCount {
            let btn = EmoticonButton()
            btn.addTarget(self, action: "btnDidClick:", forControlEvents: .TouchUpInside)
            btn.titleLabel?.font = UIFont.systemFontOfSize(32)
            let row = i / EmoticonColCount
            let col = i % EmoticonColCount
            let x = leftMargin + CGFloat(col) * bW
            let y = bH * CGFloat(row)
            btn.frame = CGRect(x: x, y: y, width: bW, height: bH)
            contentView.addSubview(btn)
            buttonArray.append(btn)
        }
}
  • setupUI 方法中調用此方法
private func setupUI(){
    // 添加子控件
    addEmoticonButtons()
    ...
}


> 運行測試

### 顯示圖片表情數(shù)據(jù)

- 在 `HMEmoticonPageCell` 中提供 `emoticons` 屬性戚哎,供外界設置表情數(shù)據(jù)

```swift
var emoticons: [HMEmoticon]?
  • HMEmoticonKeyboard 中的 collectionView 數(shù)據(jù)源方法里面給 cell 設置數(shù)據(jù)
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(HMEmoticonKeyboardCellId, forIndexPath: indexPath) as! HMEmoticonPageCell
    cell.indexPath = indexPath
    // 設置表情數(shù)據(jù)
    cell.emoticons = HMEmoticonTools.allEmoticons()[indexPath.section][indexPath.row]
    return cell
}
  • emoticonsdidSet 方法中顯示表情
/// 當前頁顯示的表情數(shù)據(jù)
var emoticons: [HMEmoticon]? {
    didSet{
        // 遍歷當前設置的表情數(shù)據(jù)
        for (index,value) in emoticons!.enumerate() {
            let button = emoticonButtons[index]
            if !value.isEmoji {
                let image = UIImage(named: value.png!)
                button.setImage(image, forState: UIControlState.Normal)
            }
        }
    }
}

運行測試:表情沒有顯示出來,加載表情的圖片地址不正確嫂用,因為表情圖片是放在 Emoticons.bundle 中的型凳,所以需要拼接前面的路徑,而這前面的路徑就是表情所對應的 info.plist 文件所在的路徑

  • HMEmoticon 中添加 path 屬性
var png: String? {
        didSet {
            imagePath = Emoticon.bundlePath + "/Emoticons.bundle/" + (id ?? "") + "/\(png ?? "")"
        }
    }

    var imagePath: String?
  • 更新 HMEmoticonPageCellemoticonsdidSet 方法
/// 當前頁顯示的表情數(shù)據(jù)
var emoticons: [HMEmoticon]? {
    didSet{
        // 遍歷當前設置的表情數(shù)據(jù)
        for (index,value) in emoticons!.enumerate() {
            let button = emoticonButtons[index]
            if !value.isEmoji {
                let image = UIImage(named: "\(value.path!)/\(value.png!)")
                button.setImage(image, forState: UIControlState.Normal)
            }
        }
    }
}

運行測試:圖片表情顯示出來了嘱函,但是 cell 復用 導致沒有表情的頁面也顯示過表情甘畅,所以在遍歷設置表情之后需要先將所有的 顯示表情的button 隱藏掉

  • 先隱藏所有顯示表情的 button,遍歷幾個表情顯示幾個
/// 當前頁顯示的表情數(shù)據(jù)
var emoticons: [HMEmoticon]? {
    didSet{

        // 先隱藏所有的表情按鈕
        for value in emoticonButtons {
            value.hidden = true
        }

        // 遍歷當前設置的表情數(shù)據(jù)
        for (index,value) in emoticons!.enumerate() {
            let button = emoticonButtons[index]
            // 顯示當前遍歷到的表情按鈕
            button.hidden = false
            if !value.isEmoji {
                let image = UIImage(named: "\(value.path!)/\(value.png!)")
                button.setImage(image, forState: UIControlState.Normal)
            }
        }
    }
}

顯示 Emoji 表情數(shù)據(jù)

  1. 演練Emoji表情, 拖入 String+Emoji 分類到項目中往弓,
  2. Emoji 表情其實就是字符串
  • 設置 Emoji 表情數(shù)據(jù)

    button.setTitle(em.codeStr(), forState: UIControlState.Normal)
}
  • 運行測試:Emoji 表情顯示太小疏唾,調整 button 的文字大小即可解決
/// 添加表情按鈕
private func addEmoticonButtons(){
    for _ in 0..<HMEmoticonPageNum {
        let button = UIButton()
        button.titleLabel?.font = UIFont.systemFontOfSize(36)
        contentView.addSubview(button)
        emoticonButtons.append(button)
    }
}

運行測試

  • 更改 HMEmoticonKeyboard 中的 collectionView 的背景顏色為透明色
/// 顯示表情的視圖
private lazy var emoticonCollectionView: UICollectionView = {
    ...
    collectionView.backgroundColor = UIColor.clearColor()
    ...
    return collectionView
}()
  • 去掉 HMEmoticonPageCell 中顯示 section 的 label

運行測試

  • 提升表情數(shù)據(jù)
  • 每頁的最后一個添加一個刪除按鈕
  • 每頁不足21個表情需要補足空白表情
  • 空白表情的最后一個應該是刪除表情

在HMEmoticonPackages提升數(shù)據(jù)

init(id: String, title: String, array: [[String : String]]) {
        super.init()
        self.title = title

        //遍歷數(shù)組 轉換為模型 再將模型轉換為

        var emoticonArray: [Emoticon] = [Emoticon]()
        var index = 0
        for item in array {
            let e = Emoticon(id: id, dict: item)
            emoticonArray.append(e)
            index++
            if index == 20 {
                //每頁的最后一個添加一個刪除表情
                let delete = Emoticon(isDelete: true)
                emoticonArray.append(delete)
                index = 0
            }
        }


        //不足21個的補空白表情
        let delta = emoticonArray.count % 21
        if delta != 0 {
            for _ in delta..<20 {
                let empty = Emoticon(isEmpty: true)
                emoticonArray.append(empty)
            }
            emoticonArray.append(Emoticon(isDelete: true))
        }
        //將模型數(shù)組處理成 分組的模型數(shù)組
        //分組規(guī)則 21 個表情為一頁
        self.sectionEmoticon = sectionEmoticonArray(emoticonArray)
    }
  • 在HMEmoticon模型中添加空表情和刪除表情的構造方法和對應的標記

運行測試 解決頁面復用的問題

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市函似,隨后出現(xiàn)的幾起案子槐脏,更是在濱河造成了極大的恐慌,老刑警劉巖缴淋,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件准给,死亡現(xiàn)場離奇詭異,居然都是意外死亡重抖,警方通過查閱死者的電腦和手機露氮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钟沛,“玉大人畔规,你說我怎么就攤上這事『尥常” “怎么了叁扫?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵三妈,是天一觀的道長。 經(jīng)常有香客問我莫绣,道長畴蒲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任对室,我火速辦了婚禮模燥,結果婚禮上,老公的妹妹穿的比我還像新娘掩宜。我一直安慰自己蔫骂,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布牺汤。 她就那樣靜靜地躺著辽旋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪檐迟。 梳的紋絲不亂的頭發(fā)上补胚,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音锅减,去河邊找鬼糖儡。 笑死,一個胖子當著我的面吹牛怔匣,可吹牛的內容都是我干的。 我是一名探鬼主播桦沉,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼每瞒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了纯露?” 一聲冷哼從身側響起剿骨,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎埠褪,沒想到半個月后浓利,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡钞速,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年贷掖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渴语。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡苹威,死狀恐怖,靈堂內的尸體忽然破棺而出驾凶,到底是詐尸還是另有隱情牙甫,我是刑警寧澤掷酗,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站窟哺,受9級特大地震影響泻轰,放射性物質發(fā)生泄漏。R本人自食惡果不足惜且轨,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一糕殉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧殖告,春花似錦阿蝶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至爽丹,卻和暖如春筑煮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粤蝎。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工真仲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人初澎。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓秸应,卻偏偏與公主長得像,于是被迫代替她去往敵國和親碑宴。 傳聞我的和親對象是個殘疾皇子软啼,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容

  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件延柠、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,024評論 4 62
  • 2017.02.22 可以練習祸挪,每當這個時候,腦袋就犯困贞间,我這腦袋真是神奇呀贿条,一說讓你做事情,你就犯困增热,你可不要太...
    Carden閱讀 1,328評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理整以,服務發(fā)現(xiàn),斷路器钓葫,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 在宿舍住了一年多一點了悄蕾,養(yǎng)花也快一年了,規(guī)模逐漸擴大,由開始的花市上買的盆栽到后來自己用塑料瓶切開的養(yǎng)著的路邊的...
    嘎嘎222閱讀 296評論 2 2