老規(guī)矩,一圖勝千言担败。Demo 傳送門 點(diǎn)我就行
運(yùn)行環(huán)境
- Xcode10
- swift 4.0
前言
這里沒有干貨,也沒有教程拣挪,請各位大神手下留情克蚂。這個(gè) demo 是平時(shí)自己在工作之余學(xué)習(xí) swift 寫的,因?yàn)槊刻鞂W(xué)習(xí)時(shí)間有限所以這個(gè) demo 前后寫了一個(gè)月左右,里面的語法和命名都不是很規(guī)范,也沒有做大量的機(jī)型和版本測試,整體語法偏向于OC瞻赶。在寫的期間也查詢了許多資料以及API 的用法赛糟,其中有一部分邏輯和 emoji 表情資源是來自于VernonVan的這篇博客派任,我也并無抄襲之意,只是單純的去練習(xí)和使用swift語法僅此而已璧南。其他素材均來自于iconfont吨瞎。
看圖知意
這個(gè)思維導(dǎo)圖展示的是各個(gè)子控件的層級關(guān)系,也包含了部分邏輯穆咐。demo中頁面聯(lián)動和旋轉(zhuǎn)適配未做。
花開兩朵各表一枝
從 emoji 說起
demo 整體業(yè)務(wù)邏輯占很大內(nèi)容字旭,其他都是子控件的堆疊并沒有很高的難度系數(shù)对湃,只要處理好控件之間的邏輯關(guān)系就能很好的實(shí)現(xiàn)動畫效果。
在做 emoji 表情的時(shí)候還在想怎么實(shí)現(xiàn)表情與文字的轉(zhuǎn)換遗淳,如:?? -> [笑哭] 這種形式拍柒,因?yàn)榕c服務(wù)器進(jìn)行數(shù)據(jù)交互將表情作為圖片做數(shù)據(jù)傳遞是非常不合理的嚷闭,并且還要考慮到表情與文字之間的相互轉(zhuǎn)化關(guān)系史隆,所以demo 中用到的是將 emoji 當(dāng)中富文本的Attachment
屬性來處理然后給相應(yīng)的表情打Tag。來看一下具體代碼:
//點(diǎn)擊 emoji 事件
func didClickEmoji(with model: MYEmojiModel) {
guard let image = UIImage.image(name: model.imageName!, path: "emoji") else {
print("圖片找不到")
return
}
// 記錄textView光標(biāo)當(dāng)前位置
let selectedRange = self.textView.selectedRange
// 將 emoji 標(biāo)記為[name] 這種形式
let emojiString = "[\(model.emojiDescription!)]"
// 通過字體大小設(shè)置 emoji 大小
let font = UIFont.systemFont(ofSize: MYTextViewTextFont)
let emojiHeight = font.lineHeight
// emoji 圖片附件
let attachment = NSTextAttachment()
attachment.image = image
attachment.bounds = .init(x: 0, y: font.descender, width: emojiHeight, height: emojiHeight)
let attachString = NSAttributedString(attachment: attachment)
// 將圖片附件轉(zhuǎn)為 NSMutableAttributedString
let emojiAttributedString = NSMutableAttributedString(attributedString: attachString)
// 將這段文字打上標(biāo)記,key 自己定義晴竞,value 為[name]养叛,這樣做方便遍歷和表情與文字替換
emojiAttributedString.addAttribute(NSAttributedString.Key(rawValue: MYAddEmojiTag), value: emojiString, range: .init(location: 0, length: attachString.length))
// 獲取輸入框中的富文本
let attributedText = NSMutableAttributedString(attributedString: self.textView.attributedText)
// 將打好標(biāo)記的富文本替換到光標(biāo)位置
attributedText.replaceCharacters(in: selectedRange, with: emojiAttributedString)
self.textView.attributedText = attributedText
self.textView.selectedRange = .init(location: selectedRange.location + emojiAttributedString.length, length: 0)
// 重新設(shè)置 font 是為了避免 emoji 在文字末尾導(dǎo)致光標(biāo)變小
self.textView.font = font
//重新計(jì)算文字高度种呐,來做自適應(yīng)
self.textViewDidChange(self.textView)
}
因?yàn)樵?emoji 被點(diǎn)擊的時(shí)候就被打上相應(yīng)的tag,value 是對應(yīng)的文字描述弃甥,所以在富文本轉(zhuǎn)字符串時(shí)就比較方便了爽室。
//將 string 轉(zhuǎn)為 NSString為了方便做字符串截取
let string = attribute.string as NSString
//遍歷富文本,篩選出被打標(biāo)記的富文本
attribute.enumerateAttribute(NSAttributedString.Key(rawValue: MYAddEmojiTag),
in: range, options: NSAttributedString.EnumerationOptions.longestEffectiveRangeNotRequired) { (value, range, stop) in
if value != nil {
// value即 emoji 對應(yīng)的描述信息
let tagString = value as! String
result = result + tagString
}else{
let rangString = string.substring(with: range)
result = result + rangString
}
}
復(fù)制粘貼的實(shí)現(xiàn)
通過上面的代碼就已經(jīng)實(shí)現(xiàn)文字<=>富文本的相互轉(zhuǎn)換了淆攻,因?yàn)?code>textView自帶復(fù)制粘貼功能阔墩,而UIPasteboard
粘貼板是沒有attributedText
屬性的,當(dāng)復(fù)制或剪切時(shí)只能將textView.attributedText
轉(zhuǎn)為文字瓶珊,當(dāng)粘貼的時(shí)候只能將文字轉(zhuǎn)為富文本啸箫。因?yàn)樵趀moji 鍵盤被點(diǎn)擊的時(shí)候你已經(jīng)知道 emoji 相對應(yīng)的文字描述,而如果粘貼為純文字伞芹,那如何知道相對應(yīng)的 emoji 呢忘苛?是的,用的是正則匹配唱较,也是盜用別人的邏輯柑土,但是VernonVan他的工程中的正則表達(dá)式是有點(diǎn)瑕疵的。在正則表達(dá)式上我做了改進(jìn)绊汹,匹配規(guī)則如下:
- 你好[smile] -> [smile]
- 你好[smile.png] -> [smile.png]
- 你好[smile_] -> [smile_]
- 你好[a[smile]] -> [smile]
- 你好[][[[smile]] -> []稽屏、[smile]
只做了 a-z 下劃線和.的匹配,如果想匹配更多內(nèi)容自己添加規(guī)則即可西乖。正則表達(dá)式不是很會寫狐榔,只是嘗試著想了這幾種規(guī)則坛增,想要驗(yàn)證和學(xué)習(xí)的可以去正則驗(yàn)證網(wǎng)站學(xué)習(xí)。具體代碼實(shí)現(xiàn)在工程:Targets->Utils->Keyboard->Resources->MYMatchingEmojiManager
文件中
//正則驗(yàn)證網(wǎng)站:https://c.runoob.com/front-end/854
//表達(dá)式: \[([a-z_.])+?\]
let regex = try! NSRegularExpression.init(pattern: "\\[([a-z_.])+?\\]")
//用表達(dá)式匹配結(jié)果
let results = regex.matches(in: string
, options: NSRegularExpression.MatchingOptions.reportProgress, range: .init(location: 0, length: string.count))
輸入框與表情頁切換動畫
引用別人的話:“真正的鍵盤也就是說調(diào)起表情鍵盤時(shí)輸入框是有光標(biāo)的薄腻,能進(jìn)行拖拽光標(biāo)收捣、選中區(qū)域等的操作,這樣的體驗(yàn)才是與系統(tǒng)鍵盤一致的庵楷。其實(shí)系統(tǒng)已經(jīng)提供好了接口給我們直接使用罢艾,UITextView
和UITextField
都有的inputView
和inputAccessoryView
就是用來實(shí)現(xiàn)自定義鍵盤的”。但是有一種情況是:如果表情鍵盤的高度低于系統(tǒng)字體鍵盤的高度尽纽,那么在切換表情鍵盤與文字鍵盤的時(shí)候是有落差的咐蚯,這個(gè)落差導(dǎo)致textView
在回落的過程中,字體鍵盤瞬間切換表情鍵盤會有一個(gè)間隙把當(dāng)前頁面的內(nèi)容暴露出來個(gè)零點(diǎn)幾秒弄贿,非常影響美觀春锋,而系統(tǒng)的文字鍵盤高度和 emoji 鍵盤高度時(shí)一致的所以沒有這個(gè)問題。解決辦法我暫時(shí)就想起來兩種:
- 當(dāng)
textView
的keyboardWillShow
通知執(zhí)行時(shí)差凹,將textView
的superView
的高度等于textView.height + emojiView.height
這樣supeView
的高度就會很大期奔,這樣在回落的過程中就不會顯示位移縫隙,還可以為 emoji 視圖加向上滾動的動畫危尿,這樣切換就會更加銜接呐萌。 - 不用
textView
的inputView
屬性,做一個(gè)假的 emoji 表情頁谊娇,微信的鍵盤就是一個(gè)假的搁胆,因?yàn)楫?dāng)切換到表情頁時(shí),textView
就失去了響應(yīng)邮绿,光標(biāo)就消失了渠旁,這樣就造成了鍵盤回落而 emoji 鍵盤向上滾動的效果,我在工程中就是用的這種方式船逮。
無論是文字切換語音顾腊、文字切表情、語音切表情或者其他功能的任意切換挖胃,都是經(jīng)過以下方法(具體實(shí)現(xiàn)見 demo):
private var keyboardType : MYKeyboardInputViewEnum.KeyboardType = .None {
//默認(rèn)沒有任何屬性杂靶,為.None
//相當(dāng)于OC中的重寫 set 方法
willSet{
if keyboardType == newValue {
//如果將要改變的值與當(dāng)前值一樣,則不做任何操作酱鸭,即同一種模式
return
}
//不相同則重新賦值
self.keyboardType = newValue;
switch newValue {
//判斷哪種模式吗垮,處理相應(yīng)的邏輯,具體實(shí)現(xiàn)見工程代碼
case .Emoji:
break
case .System:
break
case .Funcs:
break
case .Record:
break
default:
break
}
}
}
語音邊錄邊轉(zhuǎn)的實(shí)現(xiàn)
語音錄制邏輯是這樣?jì)鸬摹?/p>
-
每點(diǎn)擊一次錄音按鈕便創(chuàng)建一個(gè)錄音機(jī)凹髓,創(chuàng)建錄音機(jī)的同時(shí)會創(chuàng)建兩個(gè)路徑:
.caf
路徑和.mp3
路徑烁登,.caf
路徑是錄音機(jī)錄制的文件存放路徑,.mp3
路徑則為轉(zhuǎn)換后的文件路徑蔚舀。以及錄音機(jī)的一些必要參數(shù):/// 設(shè)置錄音格式 默認(rèn)kAudioFormatLinearPCM var formatIDKey : AudioFormatID = kAudioFormatLinearPCM /// 設(shè)置錄音采樣率(Hz) 8000/44100/96000(影響音頻的質(zhì)量) 默認(rèn) 44100 var sampleRateKey : NSInteger = 44100 /// 錄音通道數(shù) 1 或 2 默認(rèn)為2 var channelsKey : NSInteger = 2 /// 線性采樣位數(shù) 8饵沧、16锨络、24、32 默認(rèn) 16 var bitDepthKey : NSInteger = 16 /// 錄音的質(zhì)量 默認(rèn)QualityMin var qualityKey : AVAudioQuality = .min
- 錄制時(shí)間為60秒狼牺,前1S內(nèi)為初始化錄音機(jī)時(shí)間羡儿,如果1S內(nèi)取消錄制則提示"錄音時(shí)間太短",執(zhí)行取消錄制方法,刪除兩個(gè)文件是钥;如果沒有取消則繼續(xù)錄制掠归,展示錄制動畫,增加手勢滑動效果悄泥,增加語音消息呼吸燈動畫虏冻,當(dāng)錄制完畢后在轉(zhuǎn)換回調(diào)中刪除錄制相對應(yīng)的
.caf
文件,拋出轉(zhuǎn)換成功的.mp3
文件路徑码泞。 - 錄制成功后,拿到相對應(yīng)的
.mp3
文件路徑上傳到服務(wù)器狼犯,因?yàn)樵谏蟼鬟^程必為異步上傳(如果為主線程那不就卡了)余寥,有可能當(dāng)前文件未上傳成功后續(xù)又有文件要上傳,所以要記得加鎖悯森,加鎖宋舷,加鎖保證數(shù)據(jù)的安全。demo 中這一部分并沒有實(shí)現(xiàn)瓢姻。 - 取消發(fā)送祝蝠,則刪除兩個(gè)對應(yīng)的文件,結(jié)束轉(zhuǎn)換
- 錄制時(shí)間到直接發(fā)送幻碱,上滑取消绎狭,聲波監(jiān)測等等。褥傍。儡嘶。
具體實(shí)現(xiàn)見demo內(nèi)Utils->Keyboard->Tool->Recorder
文件,邊錄邊轉(zhuǎn)的實(shí)現(xiàn)見ConverAudioFile
文件恍风,轉(zhuǎn)換是用的lame.framework
的三方庫蹦狂。
swift基本語法
常用屬性
只讀屬性(readonly)
在OC語法中因?yàn)榇嬖?code>.h和.m
兩個(gè)文件,所以想暴露給外部使用的接口和方法是全部定義在.h
文件中的而 swift 則是全部寫在同一個(gè)文件中的朋贬。如果你想定義一個(gè)屬性為只讀屬性:
OC寫法
.h文件定義
@property (nonatomic, assign,readonly) BOOL isHidden;
.m文件實(shí)現(xiàn)
- (BOOL)isHidden{}
swift寫法
//只實(shí)現(xiàn) get 方法
var isHidden : BOOL {
get{
return true
}
}
有時(shí)候你需要定義一個(gè)屬性凯楔,外部為只讀而內(nèi)部可以讀寫,OC是非常好實(shí)現(xiàn)的
.h文件定義
@property (nonatomic, assign,readonly) BOOL isHidden;
.m文件實(shí)現(xiàn)
@property (nonatomic, assign) BOOL isHidden;
這樣就可實(shí)現(xiàn)一個(gè)外部只讀內(nèi)部讀寫功能
而 swift 實(shí)現(xiàn)方法有很多種锦募,你可以定義一個(gè)方法摆屯,內(nèi)部定義一個(gè)為private
的屬性,將這個(gè)屬性返回出去糠亩。還有更簡便的寫法
//意思是內(nèi)部實(shí)現(xiàn) set 方法鸥拧,外部只可調(diào)用 get 方法
private(set) var isHidden = true
設(shè)置代理
OC 中是這樣寫的
@protocol MYEmojiProtocolDelegate <NSObject>
//必須實(shí)現(xiàn)
- (void) didClickDelete;
@optional 可選實(shí)現(xiàn)
- (void) didClickSend;
@end
設(shè)置代理屬性:
@property (nonatomic, weak) id<MYEmojiProtocolDelegate> delegate;
swift 寫法
protocol MYEmojiProtocolDelegate : NSObjectProtocol {
func didClickDelete()
}
設(shè)置代理屬性:
weak var pageDelegate : MYEmojiProtocolDelegate?
如果 swift 代理方法想設(shè)置成option
可選方法党远,則方法需要加@objc
前綴,protocol
前也是需要加@objc
的富弦,被標(biāo)識為@objc屬性沟娱,使得它兼容OC代碼,擁有可選方法的協(xié)議只能被類遵守而枚舉和結(jié)構(gòu)體是不能遵守協(xié)議的腕柜。還有一種做法就是對協(xié)議進(jìn)行方法擴(kuò)展:
extension MYEmojiProtocolDelegate {
//擴(kuò)展代理的方法是必須實(shí)現(xiàn)的
func didClickSend() {
}
}
在學(xué)習(xí) swift 的時(shí)候發(fā)現(xiàn)OC中的代理與 swift 中的協(xié)議济似,這是兩種不同的概念,我們也知道 swift 是一門面向協(xié)議的編程盏缤,因?yàn)槭浅鯇W(xué) swift 對其理解還是比較淺的砰蠢,下面談?wù)勎覍γ嫦騾f(xié)議的理解。
protocol
是一些方法或?qū)傩缘拿Q唉铜,自我理解像是方法和屬性(或者屬性)的集合台舱。只定義接口或者屬性而不實(shí)現(xiàn)任何功能,如果某個(gè)類型
(不是類潭流;類型包括:類竞惋,枚舉,結(jié)構(gòu)體)想要遵守一個(gè)協(xié)議灰嫉,那它需要實(shí)現(xiàn)這個(gè)協(xié)議所定義的所有這些內(nèi)容拆宛。swift 里的protocol
不僅可以定義方法還可定義屬性,這與OC
里的有所不同讼撒。
從實(shí)現(xiàn)方法說起
舉個(gè)栗子:為UITableViewcell
實(shí)現(xiàn)點(diǎn)擊事件即:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//增加個(gè)點(diǎn)擊調(diào)用方法
didClick()
}
因?yàn)橛胁煌?code>UITableViewcell的子類都需要實(shí)現(xiàn)這個(gè)方法浑厚,那我們應(yīng)該怎么做呢?
繼承
可以很好的解決這個(gè)問題根盒,但是缺點(diǎn)是帶來耦合性钳幅。如果再實(shí)現(xiàn)一個(gè)呼吸效果呢,就又在Base
類中實(shí)現(xiàn)相應(yīng)的代碼炎滞,很快Base
類就變得臃腫贡这,且任何代碼都可以寫進(jìn)去,而子類也完全不知道實(shí)現(xiàn)了父類的哪些方法厂榛。
Extension/Category
大家肯定在項(xiàng)目中用到的比較多盖矫,也很實(shí)用。直接為UITableViewcell
寫一個(gè)擴(kuò)展击奶,那意味著項(xiàng)目里所有的UITableViewcell
對象都可以訪問這個(gè)方法辈双,如果UICollectionCell
也需要上面的方法呢?也寫擴(kuò)展柜砾,粘貼復(fù)制同樣的代碼湃望,我們都知道這兩個(gè)類都繼承自UIView
,那直接給UIView
添加擴(kuò)展,這樣項(xiàng)目中所有繼承自UIView
的對象都可以訪問這個(gè)方法证芭,為了一個(gè)類就污染了其他對象瞳浦,因?yàn)檫@些對象根本不需要這些方法。
使用協(xié)議解決問題
定義一個(gè)protocol
protocol MYCompatible {
// 定義屬性
//必須明確指定該屬性支持的操作:只讀(get)或者是可讀寫(get set)
// 要用 var 定義屬性废士,即使只有 get 方法
var name: String {get set}
var birthday : String {get}
// 定義方法
//protocol中的約定方法叫潦,當(dāng)方法中有參數(shù)時(shí)是不能有默認(rèn)值的
func eat(food: String)
//如果需要改變自身的值,需要在方法前面加mutating關(guān)鍵字
mutating func changeName(name: String)
}
定一個(gè)類或者結(jié)構(gòu)體實(shí)現(xiàn)該協(xié)議
//遵守協(xié)議官硝,實(shí)現(xiàn)協(xié)議的方法 就上面例子而言矗蕊,只需要將`UITableviewCell`類遵守協(xié)議即可
class MYExtension: MYCompatible {
var name: String = "xiaoma"
let birthday: String = "1994"
func eat(food: String = "KFC") {
if food == "KFC" {
print("好吃")
} else {
print("想吃KFC")
}
}
//如果協(xié)議中方法有mutating關(guān)鍵字,如果結(jié)構(gòu)體來遵守協(xié)議則需要mutating
func changeName(name: String) {
self.name = name
}
}
如果只希望協(xié)議只被類class遵守氢架,只需要在定義協(xié)議的時(shí)候在后面加上AnyObject
即可
protocol MYCompatible : AnyObject {
var name: String {get set}
...
}
如果協(xié)議中定義了構(gòu)造函數(shù)(init)傻咖,則實(shí)現(xiàn)協(xié)議的類必須實(shí)現(xiàn)這個(gè)構(gòu)造函數(shù)
protocol MYCompatible {
var name: String {get set}
var birthday: String {get}
// 定義構(gòu)造函數(shù)
init(name: String)
}
class MYExtension: MYCompatible {
var name: String = "xiaoma"
let birthday: String = "1994"
//如果該類被定義為final 則 required 不寫
required init(name: String) {
self.name = name
}
}
協(xié)議擴(kuò)展
像上面的例子中UITableviewCell
和UICollectionCell
中他們所實(shí)現(xiàn)的方法都是一樣的,只是兩者的類型不同岖研,則沒必要定義兩個(gè)協(xié)議卿操,只需要寫一個(gè)協(xié)議即可,這時(shí)就可以在協(xié)議中使用關(guān)聯(lián)類型associatedtype
public protocol MYCompatible {
associatedtype MYCompatibleType
var my : MYCompatibleType { get }
}
final class MYExtension: MYCompatible {
typealias MYCompatibleType = Bool
var my: MYCompatibleType {
return true
}
}
我們知道協(xié)議中定義的屬性或者方法是不提供實(shí)現(xiàn)方式的孙援,我們可以通過協(xié)議擴(kuò)展的形式害淤,在擴(kuò)展中實(shí)現(xiàn)相應(yīng)的代碼:
//定一個(gè)協(xié)議
public protocol MYCompatible {
//使用關(guān)聯(lián)類型
associatedtype MYCompatibleType
//創(chuàng)建屬性 屬性類型為關(guān)聯(lián)的協(xié)議
var my : MYCompatibleType { get }
}
//構(gòu)建一個(gè)類,實(shí)現(xiàn)協(xié)議
public final class MYExtension<Base>: MYCompatible {
// Base 為泛型
public let my: Base
// 構(gòu)造方法
public init(_ my:Base) {
self.my = my
}
}
給協(xié)議添加默認(rèn)實(shí)現(xiàn)赃磨,用where
關(guān)鍵字對協(xié)議做條件限定(where 類型限定)
這里 MYCompatibleType 關(guān)聯(lián)類型筝家,可以是類或者是結(jié)構(gòu)體洼裤,如果是結(jié)構(gòu)體可以用 MYCompatibleType == Data
如果是類則可以 MYCompatibleType: UIView
extension MYCompatible where MYCompatibleType : UIView {
public var width: CGFloat {
get {
return my.frame.size.width
}
set {
my.frame.size.width = newValue
}
}
}
在想要擴(kuò)展的類中添加MYExtension 類或者結(jié)構(gòu)體邻辉,這個(gè)類是繼承MYCompatible的協(xié)議的,所以就擁有了MYCompatible協(xié)議里面默認(rèn)的實(shí)現(xiàn)方法腮鞍,即剛才那個(gè)用 `where` 限定的類型
extension UIView {
var my: MYExtension <UIView> {
return MYExtension(my: self)
}
}
//調(diào)用則是
let view = UIView()
view.my.width = 20
我們現(xiàn)在回過頭來看看這個(gè)擴(kuò)展協(xié)議值骇,首先定義一個(gè)名為MYCompatible
的協(xié)議,然后關(guān)聯(lián)類型associatedtype MYCompatibleType
移国,定義屬性為var my : MYCompatibleType { get }
返回的類型為關(guān)聯(lián)的類型吱瘩,再定義一個(gè)類MYExtension <Base>
Base為泛型,實(shí)現(xiàn)協(xié)議迹缀,則實(shí)現(xiàn)my
屬性使碾,再構(gòu)造MYExtension
類的init
方法,現(xiàn)在對UIView
進(jìn)行擴(kuò)展
extension UIView {
var my: MYExtension <UIView> {
return MYExtension(my: self)
}
}
現(xiàn)在 UIView
的對象里的屬性my
就實(shí)現(xiàn)了MYCompatible
協(xié)議祝懂,即擁有該協(xié)議的方法票摇,因?yàn)閰f(xié)議默認(rèn)是不提供方法的實(shí)現(xiàn)的,所以要對協(xié)議進(jìn)行擴(kuò)展砚蓬,在擴(kuò)展的時(shí)候使用了where
做類型限定矢门,即方法擁有者只能是限定的類型。
為什么使用協(xié)議擴(kuò)展
因?yàn)槲覀冺?xiàng)目里有很多地方是對UIView
、UIColor
等常用類進(jìn)行extension
- 在進(jìn)行多人開發(fā)的時(shí)候?qū)ν活愋妥鱿嗤僮魇呛艹S械氖滤钐蓿矊懥艘粋€(gè)和你命名方式一樣的方法,但是他也新建了一個(gè)文件隔躲,然后你們兩個(gè)方法就沖突了,然后再進(jìn)行一頓排查物延。
- 隨著需求的增多宣旱,你擴(kuò)展的方法也就更多,然后將這些方法寫成工具類教届,當(dāng)進(jìn)行下次開發(fā)時(shí)可以直接拖進(jìn)工程中快速使用响鹃,但是卻與其他人的方法沖突了,很尷尬啊案训。
上面的協(xié)議擴(kuò)展可以很好的解決這個(gè)問題买置,而且在寫法上可以帶一個(gè)自己的標(biāo)志,逼格很高强霎。像一些三方庫都有這種操作的:view.snp.makeConstraints()
忿项、imageView.kf.setImage(with: <>)
因?yàn)轭愋秃芏啵獢U(kuò)展出來的方法也很多城舞,總不能每個(gè)類或者結(jié)構(gòu)體都寫一個(gè)協(xié)議吧轩触,其實(shí),寫一個(gè)就夠了家夺,將這些協(xié)議抽離出一個(gè)通用的即可脱柱。demo 中就是這樣做的,將協(xié)議抽離出一個(gè)通用的來拉馋。
總結(jié)
在寫的過程中并沒有按照別人的代碼照抄照搬而是吸取精華榨为,棄去糟粕。寫 demo 不是目的煌茴,更多的是為了提高自己的知識面随闺,而且 swift 語言版本也日漸穩(wěn)定,swift 作為 iOS 的新語言潛力還是比較大的蔓腐。因?yàn)閷?swift 學(xué)習(xí)的比較少矩乐,理解的也比較淺,文中或 demo 里肯定有不妥當(dāng)?shù)牡胤交芈郏允墙邮芘u和教育的散罕。
參考資料
https://juejin.im/post/5a6b3f016fb9a01ca267b6db
http://www.reibang.com/p/971fff236881
https://onevcat.com/2016/11/pop-cocoa-1/
https://onevcat.com/2016/12/pop-cocoa-2/