開源專案-WaterMark

軒轅小羽發表於2016-08-27

前言

這裡引用應用描述裡的一句話:

妻子做微商很辛苦所以想做個軟體來緩解她的壓力,於是誕生了這個軟體.在她的鼓勵下把這個軟體上線到Appstroe上,希望能幫助到你們

本專案程式碼全部開源,只刪除了LeanCloud相關的id和key,如果我以後不小心把key和id push上去了各位小夥伴一定要提醒我哈...

這個專案是業餘時間瞎敲出來的...很多程式碼都是自己一邊嘗試一邊敲出來的...所以大家可以挑重點看

我準備把WaterMark 和以後其他的開源專案寫成一個系列的部落格,每次更新版本都會把遇到的難點和坑點總結出來..喜歡的話大家可以收藏,關注支援一下

希望本篇部落格能幫助菜鳥瞭解iOS專案開發中的常識

歡迎大家一起討論正確的開發姿勢

跪求大神帶我飛!

(在下英文渣,請各位老爺觀看時利用腦內runtime 把不對的單詞替換成對的單詞...哈哈哈,我會好好背單詞的!)

專案地址:github.com/Lafree317/W…

AppStroe:itunes.apple.com/WebObjects/…

迴歸正題

目前功能:

  • 原尺寸/縮圖 新增水印
  • 水印快取
  • 水印編輯

架構

專案檔案結構

專案中現在整合的第三方開源庫有:

  • Pod
    • LeanCloud Swift-alpha版(坑): 用於反饋資料的雲端儲存
    • MBProgressHUD 1.0 :提示控制元件 每個專案必備
    • RxSwift和RxCocoa: 現在程式碼中還沒有用到,到時候寫登陸的時候準備這個寫響應式
    • SnapKit: Swift版的Messary 炒雞好用
  • Vendors
    • ShowString 我司小哥改裝的一個提示框,優點在於提示的時候還可以對頁面進行UI互動
    • TZImagePickerController 一個1000+star圖片選擇庫,這個開發者很熱心發issues很快就會回覆你並解決問題
    • IGLDropDownMenu 一個下拉抽屜式動畫,使用起來非常方便
    • ZEViewKit 我自己封裝的一些小控制元件,準備再贊一些一起上傳到Pod中
    • Scenes裡有一個ZEVC是準備以後所有開源專案中公用的反饋和登陸介面

專案架構採用最基本的MVC

因為不是公司專案喜歡自己瞎搞一些東西所以一些被我改畸形了 這裡詳細說一下:

我發現Swfit的Extension太過強大於是將Model層和View層都用extension來代替了,如feedBackController的程式碼就被我改成:

  • Controller

    class ZEFeedBackContoller: UIViewController,UITextFieldDelegate,UIScrollViewDelegate {
      // 各種屬性
    
      override func viewDidLoad() {
          super.viewDidLoad()
    
          setUI()
      }複製程式碼
  • Model

    extension ZEFeedBackContoller {
      // model方法
      func sendFeedBack(){
      }
    }複製程式碼
  • View

    extension ZEFeedBackContoller {
      // 新增UI
      func setUI(){
      }複製程式碼

首先強調一點...這是我自己胡亂嘗試敲出來的...沒有看過類似程式碼..所以不推薦在正常專案中使用,只是講一下自己的思路希望能幫助到你

這樣做的好處我覺得有幾點

  • 程式碼都為C的程式碼,不存在跨類呼叫方法和取值(高內聚?)
  • 上下兩個控制器傳值呼叫的時候也更方便一些(低耦合?)
  • C程式碼很少,只把主要的方法C中,如果想擴充套件相應功能或修改bug可以直接找到位置去進行操作

我覺得不好的地方

  • 程式碼較為混亂,不適合多人開發?
  • 具體功能重用性差,不利於其他專案使用

我一直自己摸索學習Swift,還沒有找到它正確的開發姿勢,希望大家能教我正確的開發姿勢

專案中還有一小部分面向協議

如給UIView新增一個滑動手勢,這裡應該如果優化一下應該 extenion到具體類 而不是直接給UIView新增..

protocol ViewGestureRecognizer{


}
extension UIView:ViewGestureRecognizer{
    func addPan(){
        self.userInteractionEnabled = true
        let pan = UIPanGestureRecognizer(target: self, action: #selector(pan(_:)))
        self.addGestureRecognizer(pan)
    }
    func pan(pan:UIPanGestureRecognizer){
        let point = pan.translationInView(self)
        self.transform = CGAffineTransformTranslate(self.transform, point.x, point.y)
        pan.setTranslation(.zero, inView: self)
    }
}複製程式碼

一些細節

水印的繪製是由EditModel這個類實現的

原理就是利用UIGraphics 的 UIImage context 先按照圖片尺寸繪製出背景圖片,然後把label一個一個的繪製到圖片上 最後匯出一張繪製好的圖片儲存到相簿中

    /**
     新增水印
     */
    func save(){
        guard let image = imageView.image else{
            return
        }
        weak var weakSelf = self
        guard let wself = weakSelf else{
            return
        }
        ZEHud.sharedInstance.showHud()
        dispatch_async(dispatch_queue_create("addLabel",nil)) {
            UIGraphicsBeginImageContext(image.size)// 開始繪製
            image.drawInRect(CGRect(origin: CGPoint.zero, size: image.size))
            for label in wself.labelArr { // 新增多個水印
                let rect = wself.imageView.convertRect(label.frame, fromView: nil)
                let reScale = 1/wself.imageView.scale
                let labelRect = CGRectMake((rect.origin.x)*reScale, (rect.origin.y*reScale), rect.size.width*reScale, rect.height*reScale)
                label.model.text.drawInRect(labelRect, withAttributes:label.model.getAttributes(1/wself.imageView.scale))
            }
            let imageA = UIGraphicsGetImageFromCurrentImageContext()// 獲取圖片
            UIGraphicsEndImageContext()// 結束繪製
            UIImageWriteToSavedPhotosAlbum(imageA, self, nil, nil)// 儲存
            dispatch_async(dispatch_get_main_queue(), {
                ZEHud.sharedInstance.hideHud()
                ShowString.sharedManager().showStringView("儲存成功")
                wself.imageView.image = imageA
                wself.assets.removeAtIndex(wself.index)
                wself.changeImage(wself.index)
                if weakSelf!.assets.count == 0 {
                    weakSelf?.performSelector(#selector(weakSelf?.nodataPop), withObject: nil, afterDelay: 0.75)
                    }
            })
        }
    }複製程式碼

專案中水印是由WaterMark這個類來實現的,因為偷懶所以直接繼承自UILabel(獲取尺寸的時候回方便一些),然後在Label下面新增了一個TextField,來進行文字編輯.

    // 通過傳入的bool進行label的文字變換
    func changeEidtType(type:Bool){
        if type == true {
            self.textField.font = self.font
            self.textField.text = self.text
            self.textField.becomeFirstResponder()
        }else{
            textField.endEditing(true)
            let dic = model.getAttributes(1)
            let att = NSAttributedString(string: self.textField.text!, attributes: dic)
            self.attributedText = att
        }
        textField.hidden = !type
        viewChange()
    }複製程式碼

每次編輯水印的步驟我設計成 長按Label -> 彈出EditView -> 每一次操作都做相應的處理(快取,改變Label的狀態)

label的樣式是由NSMutableAttributedString這個類來實現的,這個類可以直接用在繪製裡

難點

Label和ImageView的比例計算

我給ImageView的class宣告瞭一個scale屬性,呼叫這個屬性就會計算出比例的變數

繪製Label的時候就按照1/scale的比例逆推回原大小進行繪製

class EditImageView: UIImageView {
    var scale:CGFloat {
        get{
            return frame.width / image!.size.width
        }
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    func setNewImage(newImage:UIImage){
        image = newImage
        self.frame.size.width = screenWidth
        frame = CGRectMake(0, 0, screenWidth, newImage.size.height * scale)
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}複製程式碼

水印的本地快取

快取格式:

顏色快取是一個坑...如果用系統自帶的顏色直接快取的話會有問題,因為width black gary 和其他顏色的屬性是不一樣的,這三類顏色只有黑色和透明度而其他顏色都是RGB 不能進行統一處理

於是我就自己歸檔了一個顏色陣列(查系統的色值還挺麻煩的..)

let titleColorArr:Array<[String:[String:CGFloat]]> = [
    ["黑色":["red":0,"green":0,"blue":0,"alpha":1]],
    ["灰色":["red":102,"green":102,"blue":102,"alpha":1]],
    ["白色":["red":255,"green":255,"blue":255,"alpha":1]],
    ["透明":["red":255,"green":255,"blue":255,"alpha":0]],
    ...
]複製程式碼

然後宣告瞭兩個方法,一個是字典轉顏色,一個是顏色轉字典

class ColorFile {
    static func colorToDic(color:UIColor) -> [String:CGFloat] {
        let components = CGColorGetComponents(color.CGColor)
        let r = components[0]
        let g = components[1]
        let b = components[2]
        let a = components[3]
        return ["red":r,"green":g,"blue":b,"alpha":a]
    }
    static func dicToColor(dic:[String:CGFloat]) -> UIColor {
        let r = dic["red"]!
        let g = dic["green"]!
        let b = dic["blue"]!
        let a = dic["alpha"]!
        return UIColor(red: r, green: g, blue: b, alpha:a)
    }
}複製程式碼

寫入快取的時機是LabelModel每一個屬性改變的時候

    var italic:Bool = false{
        willSet{
            self.italic = newValue
            setUD(self.italic, key:italicUDK)
        }
    }
    var underLine:Bool = false{
        willSet{
            self.underLine = newValue
            setUD(self.underLine, key:underLineUDK)
        }
    }

    func setUD(value:AnyObject?,key:String){
        NSUserDefaults.standardUserDefaults().setObject(value, forKey: key)
        NSUserDefaults.standardUserDefaults().synchronize()// 同步資料
    }
    func getUD(key:String) -> AnyObject? {
        return NSUserDefaults.standardUserDefaults().objectForKey(key)
    }複製程式碼

labelModel初始化的時候就會取快取,如果沒取到就會給一個預設值

init(){
        if  let text =  getUD(textUDK) as? String {
            self.text = text
        }else{
            text = "這是第一個水印"
        }
}複製程式碼

當ImageView被拖拽放大時 相對 Label的筆記變化

這個還未解決,在上線前已經禁止掉了ImageView的手勢

每次imageView大小變化的時候用Label按照scale逆推回去就會發現比例不對,不能按照原比例繪製,求大神幫忙..

歡迎來到吐槽時間...

LeanCloud-Swift-SDK就是一個坑啊...程式碼難懂不說居然只支援iOS9.1以上版本...相信這一條就讓99%的專案不準備引用了吧...

然後他們居然還在info.plist裡面的short boundle version裡面寫英文!握草我頭一次遇見打包時候這個Error型別,為此我還特意記了一條筆記....

推薦

推薦一個AppIcon快速生成外掛,可以用Package Manager直接下載或者直接去git->github.com/kaphacius/I…

使用方法:開啟Assets->右鍵選中AppIcon->選中Make An App Icon -> 選擇圖片 -> 成功!

現在稽核時各個型號的手機可以共用一套簡介圖片了(可能不是最近才改的,一直是另外一個小哥負責打包的 @辛勤的另外一個小哥)

結尾語

現在專案剛剛提交了稽核,稽核通過了再把appstore連結貼上...

我都是想起哪裡說哪裡...可能有些難懂,如果有不懂的地方可以在評論裡和我說我會及時修改文章的

睡覺明早起來再改錯別字...