swift 自定義鍵盤+錄音

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

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

運(yùn)行環(huán)境

  • Xcode10
  • swift 4.0

前言

這里沒(méi)有干貨,也沒(méi)有教程,請(qǐng)各位大神手下留情。這個(gè) demo 是平時(shí)自己在工作之余學(xué)習(xí) swift 寫的,因?yàn)槊刻鞂W(xué)習(xí)時(shí)間有限所以這個(gè) demo 前后寫了一個(gè)月左右,里面的語(yǔ)法和命名都不是很規(guī)范,也沒(méi)有做大量的機(jī)型和版本測(cè)試,整體語(yǔ)法偏向于OC。在寫的期間也查詢了許多資料以及API 的用法,其中有一部分邏輯和 emoji 表情資源是來(lái)自于VernonVan的這篇博客,我也并無(wú)抄襲之意,只是單純的去練習(xí)和使用swift語(yǔ)法僅此而已。其他素材均來(lái)自于iconfont

看圖知意

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

這個(gè)思維導(dǎo)圖展示的是各個(gè)子控件的層級(jí)關(guān)系,也包含了部分邏輯。demo中頁(yè)面聯(lián)動(dòng)和旋轉(zhuǎn)適配未做。

花開(kāi)兩朵各表一枝

從 emoji 說(shuō)起

demo 整體業(yè)務(wù)邏輯占很大內(nèi)容,其他都是子控件的堆疊并沒(méi)有很高的難度系數(shù),只要處理好控件之間的邏輯關(guān)系就能很好的實(shí)現(xiàn)動(dòng)畫效果。

在做 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屬性來(lái)處理然后給相應(yīng)的表情打Tag。來(lái)看一下具體代碼:

//點(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!)]"
    // 通過(guò)字體大小設(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ì)算文字高度,來(lái)做自適應(yīng)
    self.textViewDidChange(self.textView)
}

因?yàn)樵?emoji 被點(diǎn)擊的時(shí)候就被打上相應(yīng)的tag,value 是對(duì)應(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 對(duì)應(yīng)的描述信息
            let tagString = value as! String
            result = result + tagString
            
        }else{
            let rangString = string.substring(with: range)
            result = result + rangString
        }
}
復(fù)制粘貼的實(shí)現(xiàn)

通過(guò)上面的代碼就已經(jīng)實(shí)現(xiàn)文字<=>富文本的相互轉(zhuǎn)換了,因?yàn)?code>textView自帶復(fù)制粘貼功能,而UIPasteboard粘貼板是沒(méi)有attributedText屬性的,當(dāng)復(fù)制或剪切時(shí)只能將textView.attributedText轉(zhuǎn)為文字,當(dāng)粘貼的時(shí)候只能將文字轉(zhuǎn)為富文本。因?yàn)樵趀moji 鍵盤被點(diǎn)擊的時(shí)候你已經(jīng)知道 emoji 相對(duì)應(yīng)的文字描述,而如果粘貼為純文字,那如何知道相對(duì)應(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á)式不是很會(huì)寫,只是嘗試著想了這幾種規(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))
輸入框與表情頁(yè)切換動(dòng)畫

引用別人的話:“真正的鍵盤也就是說(shuō)調(diào)起表情鍵盤時(shí)輸入框是有光標(biāo)的,能進(jìn)行拖拽光標(biāo)、選中區(qū)域等的操作,這樣的體驗(yàn)才是與系統(tǒng)鍵盤一致的。其實(shí)系統(tǒng)已經(jīng)提供好了接口給我們直接使用,UITextViewUITextField都有的inputViewinputAccessoryView就是用來(lái)實(shí)現(xiàn)自定義鍵盤的”。但是有一種情況是:如果表情鍵盤的高度低于系統(tǒng)字體鍵盤的高度,那么在切換表情鍵盤與文字鍵盤的時(shí)候是有落差的,這個(gè)落差導(dǎo)致textView在回落的過(guò)程中,字體鍵盤瞬間切換表情鍵盤會(huì)有一個(gè)間隙把當(dāng)前頁(yè)面的內(nèi)容暴露出來(lái)個(gè)零點(diǎn)幾秒,非常影響美觀,而系統(tǒng)的文字鍵盤高度和 emoji 鍵盤高度時(shí)一致的所以沒(méi)有這個(gè)問(wèn)題。解決辦法我暫時(shí)就想起來(lái)兩種:

  1. 當(dāng)textViewkeyboardWillShow通知執(zhí)行時(shí),將textViewsuperView的高度等于 textView.height + emojiView.height 這樣supeView的高度就會(huì)很大,這樣在回落的過(guò)程中就不會(huì)顯示位移縫隙,還可以為 emoji 視圖加向上滾動(dòng)的動(dòng)畫,這樣切換就會(huì)更加銜接。
  2. 不用textViewinputView屬性,做一個(gè)假的 emoji 表情頁(yè),微信的鍵盤就是一個(gè)假的,因?yàn)楫?dāng)切換到表情頁(yè)時(shí),textView就失去了響應(yīng),光標(biāo)就消失了,這樣就造成了鍵盤回落而 emoji 鍵盤向上滾動(dòng)的效果,我在工程中就是用的這種方式。

無(wú)論是文字切換語(yǔ)音、文字切表情、語(yǔ)音切表情或者其他功能的任意切換,都是經(jīng)過(guò)以下方法(具體實(shí)現(xiàn)見(jiàn) demo):

private var keyboardType : MYKeyboardInputViewEnum.KeyboardType = .None {
        //默認(rèn)沒(méi)有任何屬性,為.None
        //相當(dāng)于OC中的重寫 set 方法
        willSet{
        if keyboardType == newValue {
        //如果將要改變的值與當(dāng)前值一樣,則不做任何操作,即同一種模式
            return
        }
        //不相同則重新賦值
        self.keyboardType = newValue;
        switch newValue {
        //判斷哪種模式,處理相應(yīng)的邏輯,具體實(shí)現(xiàn)見(jiàn)工程代碼
        case .Emoji:
            break
        case .System:
            break
        case .Funcs:
            break
        case .Record:
            break
        default:
            break
        }
        
    }
}
語(yǔ)音邊錄邊轉(zhuǎn)的實(shí)現(xiàn)

語(yǔ)音錄制邏輯是這樣?jì)鸬摹?/p>

  1. 每點(diǎn)擊一次錄音按鈕便創(chuàng)建一個(gè)錄音機(jī),創(chuàng)建錄音機(jī)的同時(shí)會(huì)創(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è)文件;如果沒(méi)有取消則繼續(xù)錄制,展示錄制動(dòng)畫,增加手勢(shì)滑動(dòng)效果,增加語(yǔ)音消息呼吸燈動(dòng)畫,當(dāng)錄制完畢后在轉(zhuǎn)換回調(diào)中刪除錄制相對(duì)應(yīng)的.caf文件,拋出轉(zhuǎn)換成功的.mp3文件路徑。
  2. 錄制成功后,拿到相對(duì)應(yīng)的.mp3文件路徑上傳到服務(wù)器,因?yàn)樵谏蟼鬟^(guò)程必為異步上傳(如果為主線程那不就卡了),有可能當(dāng)前文件未上傳成功后續(xù)又有文件要上傳,所以要記得加鎖,加鎖,加鎖保證數(shù)據(jù)的安全。demo 中這一部分并沒(méi)有實(shí)現(xiàn)。
  3. 取消發(fā)送,則刪除兩個(gè)對(duì)應(yīng)的文件,結(jié)束轉(zhuǎn)換
  4. 錄制時(shí)間到直接發(fā)送,上滑取消,聲波監(jiān)測(cè)等等。。。

具體實(shí)現(xiàn)見(jiàn)demo內(nèi)Utils->Keyboard->Tool->Recorder文件,邊錄邊轉(zhuǎn)的實(shí)現(xiàn)見(jiàn)ConverAudioFile文件,轉(zhuǎn)換是用的lame.framework的三方庫(kù)。

swift基本語(yǔ)法

常用屬性

只讀屬性(readonly)在OC語(yǔ)法中因?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è)屬性返回出去。還有更簡(jiǎn)便的寫法

//意思是內(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)識(shí)為@objc屬性,使得它兼容OC代碼,擁有可選方法的協(xié)議只能被類遵守而枚舉和結(jié)構(gòu)體是不能遵守協(xié)議的。還有一種做法就是對(duì)協(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 對(duì)其理解還是比較淺的,下面談?wù)勎覍?duì)面向協(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)方法說(shuō)起

舉個(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è)問(wèn)題,但是缺點(diǎn)是帶來(lái)耦合性。如果再實(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對(duì)象都可以訪問(wèn)這個(gè)方法,如果UICollectionCell也需要上面的方法呢?也寫擴(kuò)展,粘貼復(fù)制同樣的代碼,我們都知道這兩個(gè)類都繼承自UIView,那直接給UIView添加擴(kuò)展,這樣項(xiàng)目中所有繼承自UIView的對(duì)象都可以訪問(wèn)這個(gè)方法,為了一個(gè)類就污染了其他對(duì)象,因?yàn)檫@些對(duì)象根本不需要這些方法。

使用協(xié)議解決問(wèn)題

定義一個(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)體來(lái)遵守協(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)的方法都是一樣的,只是兩者的類型不同,則沒(méi)必要定義兩個(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)方式的,我們可以通過(guò)協(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)鍵字對(duì)協(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)在回過(guò)頭來(lái)看看這個(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)在對(duì)UIView進(jìn)行擴(kuò)展

extension UIView {
    var my: MYExtension <UIView> {
        return MYExtension(my: self)
    }
}

現(xiàn)在 UIView 的對(duì)象里的屬性my就實(shí)現(xiàn)了MYCompatible協(xié)議,即擁有該協(xié)議的方法,因?yàn)閰f(xié)議默認(rèn)是不提供方法的實(shí)現(xiàn)的,所以要對(duì)協(xié)議進(jìn)行擴(kuò)展,在擴(kuò)展的時(shí)候使用了where做類型限定,即方法擁有者只能是限定的類型。

為什么使用協(xié)議擴(kuò)展

因?yàn)槲覀冺?xiàng)目里有很多地方是對(duì)UIViewUIColor等常用類進(jìn)行extension

  1. 在進(jìn)行多人開(kāi)發(fā)的時(shí)候?qū)ν活愋妥鱿嗤僮魇呛艹S械氖拢矊懥艘粋€(gè)和你命名方式一樣的方法,但是他也新建了一個(gè)文件,然后你們兩個(gè)方法就沖突了,然后再進(jìn)行一頓排查。
  2. 隨著需求的增多,你擴(kuò)展的方法也就更多,然后將這些方法寫成工具類,當(dāng)進(jìn)行下次開(kāi)發(fā)時(shí)可以直接拖進(jìn)工程中快速使用,但是卻與其他人的方法沖突了,很尷尬啊。

上面的協(xié)議擴(kuò)展可以很好的解決這個(gè)問(wèn)題,而且在寫法上可以帶一個(gè)自己的標(biāo)志,逼格很高。像一些三方庫(kù)都有這種操作的:view.snp.makeConstraints()imageView.kf.setImage(with: <>)

因?yàn)轭愋秃芏啵獢U(kuò)展出來(lái)的方法也很多,總不能每個(gè)類或者結(jié)構(gòu)體都寫一個(gè)協(xié)議吧,其實(shí),寫一個(gè)就夠了,將這些協(xié)議抽離出一個(gè)通用的即可。demo 中就是這樣做的,將協(xié)議抽離出一個(gè)通用的來(lái)。

總結(jié)

在寫的過(guò)程中并沒(méi)有按照別人的代碼照抄照搬而是吸取精華,棄去糟粕。寫 demo 不是目的,更多的是為了提高自己的知識(shí)面,而且 swift 語(yǔ)言版本也日漸穩(wěn)定,swift 作為 iOS 的新語(yǔ)言潛力還是比較大的。因?yàn)閷?duì) swift 學(xué)習(xí)的比較少,理解的也比較淺,文中或 demo 里肯定有不妥當(dāng)?shù)牡胤剑允墙邮芘u(píng)和教育的。

參考資料

https://juejin.im/post/5a6b3f016fb9a01ca267b6db

http://www.lxweimin.com/p/971fff236881

https://onevcat.com/2016/11/pop-cocoa-1/

https://onevcat.com/2016/12/pop-cocoa-2/

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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