IOS 聊天界面(自適應(yīng)文字)的實(shí)現(xiàn)
該篇文章主要介紹一個(gè)實(shí)現(xiàn)聊天界面的思路過程,源碼可以在 源碼鏈接 獲得,該工程實(shí)現(xiàn)聊天的基本功能,功能還不夠完善,歡迎大家提PR,效果圖如下所示
我希望通過相對簡單的方式實(shí)現(xiàn)界面的布局,沒有復(fù)雜的計(jì)算達(dá)到自適應(yīng)的效果。
iOS8新功能介紹
雖然self size cell最終沒有在我的工程中用到,但是這是我曾經(jīng)挖過的坑,所以在此做了簡單的介紹。
在iOS 8 中,UITableView新增一項(xiàng)功能 self size cells,這是一項(xiàng)通過 UITableViewCell 的約束自動自動計(jì)算UITableView contentSize 的技術(shù)。這個(gè)新特性給我們帶來兩個(gè)個(gè)好處。
- 我們不再需要為了自適應(yīng)文字,去計(jì)算每個(gè)cell 中文字所需要的高度。
- 而且有更高的性能。(因?yàn)閁ITableView每次reloadData的時(shí)候都會重新計(jì)算 cell 的高度,意味著如果有1萬個(gè) cell 要展示,需要 調(diào)用heightForRowAtIndexPath一萬次,這效率是特別低的)
我們通過一個(gè)簡單的demo來介紹一下 Self Size Cells 的用法,demo 源碼
效果如圖
使用步驟是
- 為UITableViewCell添加約束
- 設(shè)置UITableView 的estimatedRowHeight屬性
- 設(shè)置 rowHeight為 UITableViewAutomaticDimension
- 有一點(diǎn)需要注意,代碼中不能實(shí)現(xiàn)heightForRowAtIndexPath 這個(gè)方法
添加約束,有一個(gè)原則是,除了自適應(yīng)text的高度不需要約束外,需要確定所有必要約束
用代碼來說就是(這里用到第三方庫 SnapKit 做代碼約束 SnapKit 傳送門)
textview.snp_makeConstraints{ (make)in make.top.equalTo(self.contentView).offset(15) make.width.equalTo(100) make.left.equalTo(self.contentView).offset(15) make.bottom.equalTo(self.contentView).offset(-15) }
對于UILabel 來說還需要把numberOfLines置為0
然后設(shè)置UITableView的必要屬性
messageTable.estimatedRowHeight=44 messageTable.rowHeight=UITableViewAutomaticDimension
以上便是,使用self size cell 的所有步驟。
實(shí)戰(zhàn)篇
接下來便是實(shí)戰(zhàn)部分,我希望在聊天頁面中使用self size cell 這個(gè)功能,聊天頁面的效果圖
以下下是我為messageCell 制作約束圖,事實(shí)上用的是代碼約束,詳情可以查看我的源碼
問題
如果我想實(shí)現(xiàn) 一個(gè)功能像微信一樣下拉刷新,而且消息停留在原來的消息頁面上,如下圖所示
分析:在刷新數(shù)據(jù)后 調(diào)用tableview.reloadData 方法,可以刷新tableview 顯示的數(shù)據(jù),不過tableview 會滾動到最頂部。幸運(yùn)的是tableView是UIScrollView的子類,如果我們改變了內(nèi)容,contentSize 這個(gè)屬性一定會改變,也就是說系統(tǒng)一定會掉用contentSize的set方法。如果我們重新這個(gè)set 方法,在每次掉用setContentSize 的時(shí)候計(jì)算出之前視窗所在的位置,并且在設(shè)置完contentSize后移動到計(jì)算號的位置,就能平滑的上拉加載更多的歷史消息了。
下面是實(shí)現(xiàn)平滑滾動的關(guān)鍵代碼
@objc(JChatMessageTable) class JChatMessageTable: UITableView { var isFlashToLoad:Bool! = false override var contentSize: CGSize { didSet { if self.isFlashToLoad != false { if !CGSizeEqualToSize(self.contentSize, CGSizeZero) { if oldValue.height < self.contentSize.height { var offset = self.contentOffset offset.y = self.contentSize.height - oldValue.height self.contentOffset = offset } } } self.isFlashToLoad = false } } override init(frame: CGRect, style: UITableViewStyle) { super.init(frame: frame, style: style) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func loadMoreMessage() { self.isFlashToLoad = true self.reloadData() } }
想法是好的,但是現(xiàn)實(shí)總是殘酷的,在具體實(shí)行的時(shí)候出現(xiàn)了一個(gè)新的問題,因?yàn)槲矣玫氖荢elf size cell 來自適應(yīng)UITableView的contentSize的, Self size cell 在處理UITableView 的contentSize時(shí)候并不是一次性賦值成功的,它是每5個(gè)點(diǎn)的增加height(contentSize.height),直到合適的高度位置, 也就是說 contentSize的 Set 方法會被掉用很多次,而且上面的代碼完全沒有作用(可以想象結(jié)果是只有最后一次掉用contentSize的Set方法起到了作用小于5個(gè)點(diǎn)的偏移量,由于這個(gè)變化真的很小,我也是在這個(gè)地方被坑了一次),由于 Self size cells 的這個(gè)特性,似乎很難實(shí)現(xiàn)這個(gè)功能。
此時(shí)我果斷放棄了使用Self Size Cells ,雖然比較心痛,不過我們大致了解了Self size cell 是如何自適應(yīng)高度的。
self size cell 主要給我們帶來兩個(gè)好處
- 能夠得到更高的執(zhí)行效率
- 我們不需要為文字的frame進(jìn)行復(fù)雜的計(jì)算 ,只需要設(shè)置width的約束和位置,就能夠得到自適應(yīng)的Size
由于messageTable 消息的展示是通過分頁加載消息的方式,第一次只會添加20條的消息,也就不會出現(xiàn)調(diào)用heightForRowAtIndexPath時(shí)間過長的卡頓問題。但是如果用戶不斷的下拉刷新heightForRowAtIndexPath的執(zhí)行時(shí)間也會出現(xiàn)線性的增加,所以為了減少這方面的時(shí)間開支,我們在每次成功加載一個(gè)新的cell 的時(shí)候把高度緩存起來,這樣就可以減少計(jì)算的時(shí)間,每次只需要計(jì)算新展現(xiàn)的歷史消息高度就可以了。
- 第一個(gè)問題經(jīng)過分析我們可以通過緩存高度的方式提高性能.
- 對于第二點(diǎn),也是最重要的一點(diǎn),如何不通過手動計(jì)算獲得UITableViewCell所需要的高度,經(jīng)過分析發(fā)現(xiàn)UITableViewCell 有一個(gè)方法systemLayoutSizeFittingSize可以計(jì)算返回自身的高度,所以我接下來生成一個(gè)全局的UITableViewCell 用于計(jì)算UITableViewCell 的高度這樣我們就不需要手動計(jì)算Cell的高度了。通過
messageCell.systemLayoutSizeFittingSize(CGSizeZero).height + 1//這里的 +1 是為了彌補(bǔ)分隔線的高度
返回的高度就是UITableView所需要的高度。
自適應(yīng)輸入框
接下來,我們需要實(shí)現(xiàn)如下圖的效果,輸入框能夠自適應(yīng)輸入文字的大小JChatInputView
我們需要輸入框能夠自適應(yīng)文本的大小,我們
需要給TextView 添加如下約束
inputTextView?.snp_makeConstraints(closure: { (make) ->Voidin make.right.equalTo(self.showMoreBtn.snp_left).offset(-5) make.left.equalTo(self.switchBtn.snp_right).offset(5) make.top.equalTo(inputWrapView).offset(5) make.bottom.equalTo(inputWrapView).offset(-5) make.height.greaterThanOrEqualTo(30) make.height.lessThanOrEqualTo(100) })
inputWarpView 添加如下約束。不需要添加高度約束, 因?yàn)閕nputWarpView的高度由TextView 的高度和其他約束計(jì)算得出
inputTextView?.snp_makeConstraints(closure: { (make) ->Voidin make.right.bottom.left.equalTo(superView) })
到此介紹了一個(gè)聊天界面的自適應(yīng)UI部分,靈活的使用約束可以減少大量的代碼。
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
iOS 條碼及二維碼掃描(從相冊中讀取條形碼/二維碼)及掃碼過程中遇到的坑
本文主要給大家介紹ios中從手機(jī)相冊中讀取條形碼和二維碼的問題及解決辦法,需要的朋友參考下2017-01-01Xcode 9下適配iPhoneX導(dǎo)致iOS 10不兼容問題的解決方法
這篇文章主要給大家介紹了關(guān)于Xcode 9下適配iPhoneX導(dǎo)致iOS 10不兼容問題的解決方法,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04iOS將時(shí)間NSDate轉(zhuǎn)化為毫秒時(shí)間戳的方法示例
這篇文章主要給大家介紹了關(guān)于iOS將時(shí)間NSDate轉(zhuǎn)化為毫秒時(shí)間戳的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08ios UITableView實(shí)現(xiàn)無數(shù)據(jù)加載占位圖片
這篇文章主要介紹了ios UITableView實(shí)現(xiàn)無數(shù)據(jù)占位圖片,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08