前言
最近在看以前的代碼的時候秩彤,發(fā)現(xiàn)自增高的實現(xiàn)有點(diǎn)復(fù)雜叔扼。在計算高度的時候有些數(shù)值是自己估摸著實現(xiàn)的,反正代碼看著很不友好漫雷,就想著重構(gòu)一下瓜富。完整測試代碼在文章最下方!
那么要實現(xiàn)UITextView輸入框有兩個要點(diǎn):
- 占位符
- 自增高
首先我們來看一下UITextView
這個東西降盹,乍一看与柑,它跟UITextFiled
好像是一家人,其實他們的父類都不是同一個蓄坏。UITextView
是繼承自UIScrollView
的仅胞,而UITextField
是繼承自UIControl
的。
Placeholder
做輸入框最基本的就是placeholder
(占位符)剑辫,當(dāng)然也有些輸入框是沒有占位符提示的干旧,比如微信,我們不管它妹蔽,我就是要搞個占位符椎眯。那么問題來了挠将,UITextView
是沒有placeholder
這個屬性的。這就是最蛋疼的地方编整,你一個輸入框的類舔稀,竟然連placeholder
都沒有,就想UITextFiled
沒有textFieldDidChange:
這個方法一樣??掌测!
好吧内贮,那我們就來手動實現(xiàn)一下placeholder
吧。要想添加一個placeholder
其實有很多方法汞斧,其中最常用的方法就是給UITextView
上加一個UILabel
夜郁,然后在textViewDidChange:
方法里面來控制他的顯示和隱藏。那有沒有更方便簡潔粘勒,看起來又比較牛逼的方法呢竞端?有的!
首先庙睡,我們來遍歷一下UITextView
這個類里的成員變量事富,用到的是runtime
里的入門小知識
var count: UInt32 = 0
let ivars = class_copyIvarList(UITextView.self, &count)
for i in 0..<count {
let ivar = ivars![Int(i)];
let name = ivar_getName(ivar);
let objcName = String(utf8String: name!)
print(objcName as Any);
}
下面是打印出來的結(jié)果
Optional("_private")
Optional("_textStorage")
Optional("_textContainer")
Optional("_layoutManager")
### Optional("_containerView") ###
Optional("_inputDelegate")
Optional("_tokenizer")
Optional("_inputController")
Optional("_interactionAssistant")
Optional("_textInputTraits")
Optional("_autoscroll")
Optional("_tvFlags")
Optional("_contentSizeUpdateSeqNo")
Optional("_scrollTarget")
Optional("_scrollPositionDontRecordCount")
Optional("_scrollPosition")
Optional("_offsetFromScrollPosition")
Optional("_linkInteractionItem")
Optional("_dataDetectorTypes")
Optional("_preferredMaxLayoutWidth")
### Optional("_placeholderLabel") ###
Optional("_inputAccessoryView")
Optional("_linkTextAttributes")
Optional("_streamingManager")
Optional("_characterStreamingManager")
Optional("_siriAnimationStyle")
Optional("_siriAlignment")
Optional("_siriParameters")
Optional("_firstBaselineOffsetFromTop")
Optional("_lastBaselineOffsetFromBottom")
Optional("_intrinsicSizeCache")
Optional("_cuiCatalog")
Optional("_beforeFreezingTextContainerInset")
Optional("_duringFreezingTextContainerInset")
Optional("_beforeFreezingFrameSize")
Optional("_unfreezingTextContainerSize")
Optional("_animatingPaste")
Optional("_frameOfTrailingWhitespace")
Optional("_textDragDropSupport")
Optional("_topContentPadding")
Optional("_bottomContentPadding")
Optional("_scrollEndDraggingVelocity")
Optional("_adjustsFontForContentSizeCategory")
Optional("_clearsOnInsertion")
Optional("_pasteDelegate")
Optional("_multilineContextWidth")
Optional("_textDragOptions")
Optional("_textDragDelegate")
Optional("_textDropDelegate")
Optional("_inputView")
Optional("_visualStyle")
是不是一大堆不知所云的東西,其他的不用管乘陪,只要看其實用###
標(biāo)出來的最關(guān)鍵的兩個成員變量_placeholderLabel
和_containerView
统台。先不管_containerView
,先來看_placeholderLabel
啡邑,這不就是占位符嗎贱勃。不過蘋果沒有把這個暴露出這個屬性給開發(fā)者使用,那么我們怎么使用者個私有的成員變量呢谣拣?當(dāng)然使用KVC
了!這個時候發(fā)現(xiàn)KVC
是個神器了吧族展!
setValue(placeHolderLabel, forKey: "_placeholderLabel")
這個系統(tǒng)自帶的placeholderLabel
和UITextField
的placeholder
一樣森缠。你只要自己寫個label
賦值給他就可以了,他的顯示和消失有系統(tǒng)控制仪缸!
那么placeholder
就搞定了贵涵,非常簡單吧,一個KVC
輕松解決恰画。
自增高
那么如何來實現(xiàn)自增高呢宾茂?這個時候大家應(yīng)該已經(jīng)想到了上面打印出來的那個被###
出來的兩個成員變量之一_containerView
。顧名思義拴还,這個東西就是個內(nèi)容視圖跨晴。你用View UI Hierarchy
查看一下就會驚奇的發(fā)現(xiàn),他的frame是根據(jù)文字高度變化的片林,也就是說_containerView
是自增高的端盆!那么問題就解決了怀骤,我們用KVC把這個_containerView
取出來。
let containerView = setValue(placeHolderLabel, forKey: "_containerView")
然后再用KVO
監(jiān)聽containerView
// 注冊監(jiān)聽
containerView.addObserver(self, forKeyPath: "frame", options: .new, context: nil)
// 實現(xiàn)監(jiān)聽
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {}
這樣我們就完美的實現(xiàn)了UITextView
的自增高焕妙,代碼又少又簡單蒋伦!
Tips
這里有個關(guān)于KVO
的小貼士,蘋果在官方文檔里已經(jīng)說明了iOS9.0
以后已經(jīng)不需要手動removeObserver:
了焚鹊,除了addObserverForName:object:queue:usingBlock:
方法痕届,因為這個方法在通知中心注冊的時候還是強(qiáng)引用的,所以要手動移除末患。
為什么iOS9.0
以后不需要手動移除Observer
了呢研叫?
因為在iOS9.0
以前,注冊Observer
時阻塑,通知中心對Observer
做unsafe_unretained
引用蓝撇,而iOS9.0
以后,通知中心對Observer
實現(xiàn)了weak
引用陈莽,這兩個引用的區(qū)別在與渤昌,weak
在對象釋放掉之后會置nil
,而unsafe_unretained
在對象釋放掉之后會變成野指針走搁,所以需要在對象釋放掉之前將Observer
移除独柑,防止野指針通信,造成Crash
私植。
Discussion
這段代碼有一個致命的缺陷是不支持設(shè)置contentInset
屬性忌栅,若是設(shè)置了contentInset
文字會上下跳,有興趣的同學(xué)可以在Demo里面測試一下曲稼,要是能解決這個問題就再好不過了索绪,謝謝大家啦!
本文Demo僅供交流使用贫悄,切勿直接扔進(jìn)項目里瑞驱!