swift 自定義鍵盤+錄音

老規(guī)矩,一圖勝千言担败。Demo 傳送門 點(diǎn)我就行

鍵盤動態(tài)圖.gif

運(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吨瞎。

看圖知意

鍵盤思維導(dǎo)圖.png

這個(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)提供好了接口給我們直接使用罢艾,UITextViewUITextField都有的inputViewinputAccessoryView就是用來實(shí)現(xiàn)自定義鍵盤的”。但是有一種情況是:如果表情鍵盤的高度低于系統(tǒng)字體鍵盤的高度尽纽,那么在切換表情鍵盤與文字鍵盤的時(shí)候是有落差的咐蚯,這個(gè)落差導(dǎo)致textView在回落的過程中,字體鍵盤瞬間切換表情鍵盤會有一個(gè)間隙把當(dāng)前頁面的內(nèi)容暴露出來個(gè)零點(diǎn)幾秒弄贿,非常影響美觀春锋,而系統(tǒng)的文字鍵盤高度和 emoji 鍵盤高度時(shí)一致的所以沒有這個(gè)問題。解決辦法我暫時(shí)就想起來兩種:

  1. 當(dāng)textViewkeyboardWillShow通知執(zhí)行時(shí)差凹,將textViewsuperView的高度等于 textView.height + emojiView.height 這樣supeView的高度就會很大期奔,這樣在回落的過程中就不會顯示位移縫隙,還可以為 emoji 視圖加向上滾動的動畫危尿,這樣切換就會更加銜接呐萌。
  2. 不用textViewinputView屬性,做一個(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>

  1. 每點(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
    
  1. 錄制時(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文件路徑码泞。
  2. 錄制成功后,拿到相對應(yīng)的.mp3文件路徑上傳到服務(wù)器狼犯,因?yàn)樵谏蟼鬟^程必為異步上傳(如果為主線程那不就卡了)余寥,有可能當(dāng)前文件未上傳成功后續(xù)又有文件要上傳,所以要記得加鎖悯森,加鎖宋舷,加鎖保證數(shù)據(jù)的安全。demo 中這一部分并沒有實(shí)現(xiàn)瓢姻。
  3. 取消發(fā)送祝蝠,則刪除兩個(gè)對應(yīng)的文件,結(jié)束轉(zhuǎn)換
  4. 錄制時(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ò)展

像上面的例子中UITableviewCellUICollectionCell中他們所實(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)目里有很多地方是對UIViewUIColor等常用類進(jìn)行extension

  1. 在進(jìn)行多人開發(fā)的時(shí)候?qū)ν活愋妥鱿嗤僮魇呛艹S械氖滤钐蓿矊懥艘粋€(gè)和你命名方式一樣的方法,但是他也新建了一個(gè)文件隔躲,然后你們兩個(gè)方法就沖突了,然后再進(jìn)行一頓排查物延。
  2. 隨著需求的增多宣旱,你擴(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/

http://www.reibang.com/p/c06ebd6de2e8

https://www.cnblogs.com/muzijie/p/6596164.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市傀蓉,隨后出現(xiàn)的幾起案子欧漱,更是在濱河造成了極大的恐慌,老刑警劉巖僚害,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件硫椰,死亡現(xiàn)場離奇詭異繁调,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)靶草,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門蹄胰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人奕翔,你說我怎么就攤上這事裕寨。” “怎么了派继?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵宾袜,是天一觀的道長。 經(jīng)常有香客問我驾窟,道長庆猫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任绅络,我火速辦了婚禮月培,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恩急。我一直安慰自己杉畜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布衷恭。 她就那樣靜靜地躺著此叠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪随珠。 梳的紋絲不亂的頭發(fā)上灭袁,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機(jī)與錄音牙丽,去河邊找鬼简卧。 笑死兔魂,一個(gè)胖子當(dāng)著我的面吹牛烤芦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播析校,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼构罗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了智玻?” 一聲冷哼從身側(cè)響起遂唧,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吊奢,沒想到半個(gè)月后盖彭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年召边,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铺呵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡隧熙,死狀恐怖片挂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贞盯,我是刑警寧澤音念,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站躏敢,受9級特大地震影響闷愤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜件余,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一肝谭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛾扇,春花似錦攘烛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至更哄,卻和暖如春芋齿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背成翩。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工觅捆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人麻敌。 一個(gè)月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓栅炒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親术羔。 傳聞我的和親對象是個(gè)殘疾皇子赢赊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 1、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,988評論 3 119
  • 依舊很喜歡去懷念兒時(shí)的各種趣事级历。 今天又回到了家释移,又去到了農(nóng)忙時(shí)要割稻谷的地方,想當(dāng)年小的時(shí)候寥殖,每一年都有兩次常規(guī)...
    lin秀閱讀 215評論 0 0
  • 導(dǎo)讀: 很多親密關(guān)系雙方玩讳,分別是回避型依戀者涩蜘、焦慮型依戀者。在對待親密關(guān)系伴侶中熏纯,兩者思維行為截然相反皱坛。格格不入的...
    穆先生4526閱讀 6,952評論 0 14
  • 好久不見 不好說甚是想念 卻談的上恍如三秋 樹上有生新芽 添了幾抹新意 鳥兒來了又去 只剩下陽光在與影子竊竊私語 ...
    云之北閱讀 251評論 0 0