深挖UITableViewCell-編輯多選模式下,引發的深思

前言

在移動端日常開發中,列表UITableView的使用頻率非常高,而TableView里主要用的就是UITableViewCell,不管是自定義cell,還是系統的cell,還是后期為了優化列表,都離不開操作cell,那么UITableViewCell就有必要研究一下了;至于為什么要整理一篇關于Cell的文章,因為,搜了一下關于cell的文章,基本都是把頭文件里面的方法簡單介紹一下,沒有介紹編輯模式下,cell的一些問題;

說明規定: 一些簡單語法,便于說明問題:

  • A-->B 意思是: A繼承B,B是A的父類;

另外,不要覺得自己平時常用,然后想當然以為,自己對于這個"出鏡率"極高的控件極為熟悉,曾經我也那么以為,然而,畢竟我們:

太年輕

問題引導

遇到如下問題:猜猜看,可能是什么原因造成的?我在平時比較活躍的QQ交流群問了一下,然后石沉大海了......

cell編輯多選bug

這只是其中在cell編輯多選中遇到一個問題;

還有一個就是,當用xib布局cell的時候,tableView進入編輯模式,多選不能全選cell,都是一些奇葩的問題;

這篇文章主要說明什么問題

  • Cell內部結構;
  • 回答文章一開始的問題;
  • Cell的重用機制;
  • 能不能自己仿照系統Cell,實現一個繼承自UIView并且重用的cell;

一.cell內部結構

我們知道,cell可以根據不同的style顯示不同的類型;
比如下面的幾種:

  • 右邊顯示感嘆號或者對勾;
  • 編輯模式,左邊顯示勾選框;

Cell內部有什么

  • cell的繼承關系

首先,OC中,所有的類都是對象,都是繼承自NSObject,這個元類;每個對象都有一個isa指針指向它的父類,實例對象的isa指針指向創建他的本類,類對象isa指向它的父類,元類指向它本身;這一塊不做討論;

不知你是否好奇,為什么對象都有一個isa指針?其實,OC對象中isa指針,并不是OC獨有的,在Python中,也有類似的,isa分開來就是:

is a ,后面是省略的 kind of ,合起來就是 is a kind of ...翻譯過來就是:是....的一類或者跟...是同一類的;

好理解,子類繼承父類,便是 Son -> Father,B is A,就是B繼承A;

當然以UI開頭的屬于UIKIt框架范疇,但是,所有的對象都是繼承自NSObject;

UITableViewCell --> UIView --> UIResponder --> NSObject

  • 工具:Runtime動態獲取cell內部控件;

通過獲取cell內部私有的子類,我們知道,除了TextLable,ContentView,ImageView等,還有重用標識符reuseIdentifier

cell內部的控件結構圖如下:


cell內部的控件結構

Runtime獲取一個類的中所有的私有屬性:
備注:需要導入Runtime相關的頭文件
#import <objc/runtime.h> #import <objc/message.h>

 unsigned int count;
    Ivar *ivarList = class_copyIvarList([cell class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivarList[i];
        NSLog(@"%s", ivar_getName(ivar));
    }
    free(ivarList);

2017-11-30 22:10:42.095 UITtableViewCell[66184:9589083] _tableView
2017-11-30 22:10:44.336 UITtableViewCell[66184:9589083] _layoutManager
2017-11-30 22:10:46.753 UITtableViewCell[66184:9589083] _target
2017-11-30 22:10:48.696 UITtableViewCell[66184:9589083] _editAction
2017-11-30 22:10:50.070 UITtableViewCell[66184:9589083] _accessoryAction
2017-11-30 22:10:51.967 UITtableViewCell[66184:9589083] _oldEditingData
2017-11-30 22:10:51.967 UITtableViewCell[66184:9589083] _editingData
2017-11-30 22:10:51.967 UITtableViewCell[66184:9589083] _rightMargin
2017-11-30 22:10:51.967 UITtableViewCell[66184:9589083] _indentationLevel
2017-11-30 22:10:51.967 UITtableViewCell[66184:9589083] _indentationWidth
2017-11-30 22:10:51.968 UITtableViewCell[66184:9589083] _reuseIdentifier
2017-11-30 22:10:51.968 UITtableViewCell[66184:9589083] _floatingContentView
2017-11-30 22:10:51.968 UITtableViewCell[66184:9589083] _lineBreakModeBeforeFocus
2017-11-30 22:10:51.968 UITtableViewCell[66184:9589083] _contentView
2017-11-30 22:10:51.968 UITtableViewCell[66184:9589083] _imageView
2017-11-30 22:10:51.968 UITtableViewCell[66184:9589083] _textLabel
2017-11-30 22:10:51.968 UITtableViewCell[66184:9589083] _detailTextLabel
2017-11-30 22:10:51.969 UITtableViewCell[66184:9589083] _backgroundView
2017-11-30 22:10:51.969 UITtableViewCell[66184:9589083] _selectedBackgroundView
2017-11-30 22:10:51.969 UITtableViewCell[66184:9589083] _multipleSelectionBackgroundView
2017-11-30 22:10:51.974 UITtableViewCell[66184:9589083] _selectedOverlayView
2017-11-30 22:10:51.974 UITtableViewCell[66184:9589083] _selectionFadeDuration
2017-11-30 22:10:51.974 UITtableViewCell[66184:9589083] _backgroundColor
2017-11-30 22:10:51.974 UITtableViewCell[66184:9589083] _separatorColor
2017-11-30 22:10:51.975 UITtableViewCell[66184:9589083] _separatorEffect
2017-11-30 22:10:51.975 UITtableViewCell[66184:9589083] _topShadowColor
2017-11-30 22:10:51.975 UITtableViewCell[66184:9589083] _bottomShadowColor
2017-11-30 22:10:51.975 UITtableViewCell[66184:9589083] _sectionBorderColor
2017-11-30 22:10:51.975 UITtableViewCell[66184:9589083] _floatingSeparatorView
2017-11-30 22:10:51.975 UITtableViewCell[66184:9589083] _topShadowAnimationView
2017-11-30 22:10:51.976 UITtableViewCell[66184:9589083] _bottomShadowAnimationView
2017-11-30 22:10:51.976 UITtableViewCell[66184:9589083] _badge
2017-11-30 22:10:51.976 UITtableViewCell[66184:9589083] _unhighlightedStates
2017-11-30 22:10:51.976 UITtableViewCell[66184:9589083] _highlightingSupport
2017-11-30 22:10:51.976 UITtableViewCell[66184:9589083] _selectionSegueTemplate
2017-11-30 22:10:51.976 UITtableViewCell[66184:9589083] _accessoryActionSegueTemplate
2017-11-30 22:10:51.977 UITtableViewCell[66184:9589083] _accessoryActionPreviewingSegueTemplateStorage
2017-11-30 22:10:51.977 UITtableViewCell[66184:9589083] _accessoryView
2017-11-30 22:10:52.008 UITtableViewCell[66184:9589083] _editingAccessoryView
2017-11-30 22:10:52.008 UITtableViewCell[66184:9589083] _customAccessoryView
2017-11-30 22:10:52.009 UITtableViewCell[66184:9589083] _customEditingAccessoryView
2017-11-30 22:10:52.009 UITtableViewCell[66184:9589083] _separatorView
2017-11-30 22:10:52.009 UITtableViewCell[66184:9589083] _topSeparatorView
2017-11-30 22:10:52.009 UITtableViewCell[66184:9589083] _topShadowView
2017-11-30 22:10:52.010 UITtableViewCell[66184:9589083] _editableTextField
2017-11-30 22:10:52.010 UITtableViewCell[66184:9589083] _lastSelectionTime
2017-11-30 22:10:52.010 UITtableViewCell[66184:9589083] _deselectTimer
2017-11-30 22:10:52.010 UITtableViewCell[66184:9589083] _textFieldOffset
2017-11-30 22:10:52.010 UITtableViewCell[66184:9589083] _indexBarWidth
2017-11-30 22:10:52.010 UITtableViewCell[66184:9589083] _separatorInset
2017-11-30 22:10:52.011 UITtableViewCell[66184:9589083] _backgroundInset
2017-11-30 22:10:52.011 UITtableViewCell[66184:9589083] _returnAction
2017-11-30 22:10:52.011 UITtableViewCell[66184:9589083] _selectionTintColor
2017-11-30 22:10:52.011 UITtableViewCell[66184:9589083] _accessoryTintColor
2017-11-30 22:10:52.011 UITtableViewCell[66184:9589083] _reorderControlImage
2017-11-30 22:10:52.012 UITtableViewCell[66184:9589083] _menuGesture
2017-11-30 22:10:52.012 UITtableViewCell[66184:9589083] _representedIndexPath
2017-11-30 22:10:52.012 UITtableViewCell[66184:9589083] _focusable
2017-11-30 22:10:52.013 UITtableViewCell[66184:9589083] _swipeToDeleteConfirmationView
2017-11-30 22:10:52.013 UITtableViewCell[66184:9589083] _swipeToDeleteCancelationGesture
2017-11-30 22:10:52.013 UITtableViewCell[66184:9589083] _clearBlendingView
2017-11-30 22:10:52.013 UITtableViewCell[66184:9589083] _swipeToDeleteDistancePulled
2017-11-30 22:10:52.013 UITtableViewCell[66184:9589083] _sectionCornerRadius
2017-11-30 22:10:52.014 UITtableViewCell[66184:9589083] _sectionBorderWidth
2017-11-30 22:10:52.014 UITtableViewCell[66184:9589083] _defaultMarginWidth
2017-11-30 22:10:52.014 UITtableViewCell[66184:9589083] _editControlFocusGuide
2017-11-30 22:10:52.014 UITtableViewCell[66184:9589083] _reorderControlFocusGuide
2017-11-30 22:10:52.014 UITtableViewCell[66184:9589083] _constants
2017-11-30 22:10:52.014 UITtableViewCell[66184:9589083] _tableCellFlags
2017-11-30 22:10:52.015 UITtableViewCell[66184:9589083] _isLayoutEngineSuspended

二 文章一開始的問題

2.1 為什么會有這個問題?

項目中,基本上都是多人協作開發,有些UI是復用的,團隊中,又沒有codeReview,每個人對自己要求又不一樣,在日常開發中,由于我們偷懶,能少寫一點就少寫一點,而文章出現的自定義cell正好是copy過來的,也沒檢查,當在非編輯模式下,顯示正常;當在編輯模式下,就出問題了?

  • 解決辦法

    首先,在編輯模式下,正常的話,cell整體發生位移了,那么,只要涉及到控件位移的,第一就是打印frame,我們看下,編輯時和非編輯情況下,cell的frame變化情況:

    • tableView非編輯下,cell的frame:

      非編輯cell Frame:<UITableViewCell: 0x7f9bd2020600; frame = (0 0; 320 44)

    • tableView編輯下,cell的frame

    編輯下cell Frame:<UITableViewCell: 0x7f9bd401ea00; frame = (0 0; 320 44);

    我們驚奇的發現,兩種模式下,cell的frame居然是一致的,那也就是說:
    編輯狀態下,cell的整體位移不是通過修改cell本身的frame實現的;

接下來,cell內部還有一個contentView,我們打印它的frame:

  • tableView非編輯下,cell.contentView的frame:

    非編輯cell Frame:<UITableViewCellContentView: 0x7f9fc31374a0; frame = (0 0; 320 44);

    • tableView編輯下,cell.contentView的frame

    編輯下cell Frame:<UITableViewCellContentView: 0x7fb522c02070; frame = (0 0; 320 44);

我靠,contentView的frame也是一致的,奇怪,那tableView,編輯下,怎么實現cell的整體位移呢?

既然,cell的本身的frame和cell內部contentView的frame,在編輯和非編輯模式下,frame在打印出來,結果并沒有改變,而實際上,在編輯模式下,cell明顯發生位移了,接下來,我們看一下cell的的視圖層級關系:

  • cell 的視圖層級關系
    進入xcode,視圖層級關系,如下:
cell視圖層級關系

通過視圖層級,我們可以看出,在編輯模式下,每一個cell都動態添加了
UITableViewCellEditControl的控件,而這個editControl的并沒有add到contentView中,而是add在cell本身中,因此,如果自定義cell,布局相對cell本身,編輯模式,cell內部子控件就不會整體移動,必須相對于contentView;

其實,cell本身就是一個View,其內部又內置了一個容器view---ContentView,而ContView的文檔說明如下:

If you want to customize cells by simply adding additional views, you should add them to the content view so they will be positioned appropriately as the cell transitions into and out of editing mode.

就是說,如果想自定義cell,創建的子控件,如lable,imgView等,建議最好把子控件添加到ContentView中,這時候,在cell動畫的時候,子控件也會隨著ContView
一塊動畫;

2.2 自定義cell中,子控件不放在cell.contentView中,行不行?

image

行啊,UI顯示是沒問題,但是,如果cell涉及動畫,或者編輯模式下,多選,整體cell往右移動,這時候,如果子控件沒有添加到contentView中,就會出現文章一開始的問題

三 建議及后續

這篇文章,本來想通過cell的frame來探究編輯模式下,系統如何實現cell的整體移動,但是,frame居然是一致的,但是,通過層級關系,我們也知道,contentView在編輯狀態下,frame改變了;

建議

因此,建議:我們在日常開發中,尤其是團隊開發中,在自定義tableVIewcell的時候,布局相對contentView布局,不要像如下這樣布局:以下代碼就是罪魁禍首!

 [self.videoCoverImgView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(weakSelf).offset(15*X_WidthScale);
        make.top.equalTo(weakSelf).offset(15*Y_HeightScale);
        make.width.offset(130*X_WidthScale);
        make.height.offset(74*Y_HeightScale);
    }];
    

點擊獲取文章涉及代碼

后續

cell的重用機理及嘗試模仿寫一個cell,打算從新開始一個文章論述,如果有問題的話,歡迎評論區留言,我們一起討論!

如果您喜歡我的這篇文章,歡迎您給我點個贊,謝謝!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容