
前言
這里引用應(yīng)用描述里的一句話:
妻子做微商很辛苦所以想做個軟件來緩解她的壓力,于是誕生了這個軟件.在她的鼓勵下把這個軟件上線到Appstroe上,希望能幫助到你們
本項目代碼全部開源,只刪除了LeanCloud相關(guān)的id和key,如果我以后不小心把key和id push上去了各位小伙伴一定要提醒我哈...
這個項目是業(yè)余時間瞎敲出來的...很多代碼都是自己一邊嘗試一邊敲出來的...所以大家可以挑重點看
我準(zhǔn)備把WaterMark 和以后其他的開源項目寫成一個系列的博客,每次更新版本都會把遇到的難點和坑點總結(jié)出來..喜歡的話大家可以收藏,關(guān)注支持一下
希望本篇博客能幫助菜鳥了解iOS項目開發(fā)中的常識
歡迎大家一起討論正確的開發(fā)姿勢
跪求大神帶我飛!
(在下英文渣,請各位老爺觀看時利用腦內(nèi)runtime 把不對的單詞替換成對的單詞...哈哈哈,我會好好背單詞的!)
回歸正題
項目地址:https://github.com/Lafree317/WaterLabel
AppStroe:https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1148289486&mt=8
架構(gòu)
項目文件結(jié)構(gòu)

項目中現(xiàn)在集成的第三方開源庫有:
- Pod
- LeanCloud Swift-alpha版(坑): 用于反饋數(shù)據(jù)的云存儲
- MBProgressHUD 1.0 :提示控件 每個項目必備
- RxSwift和RxCocoa: 現(xiàn)在代碼中還沒有用到,到時候?qū)懙顷懙臅r候準(zhǔn)備這個寫響應(yīng)式
- SnapKit: Swift版的Messary 炒雞好用
- Vendors
- ShowString 我司小哥改裝的一個提示框,優(yōu)點在于提示的時候還可以對頁面進(jìn)行UI交互
- TZImagePickerController 一個1000+star圖片選擇庫,這個開發(fā)者很熱心發(fā)issues很快就會回復(fù)你并解決問題
- IGLDropDownMenu 一個下拉抽屜式動畫,使用起來非常方便
- ZEViewKit 我自己封裝的一些小控件,準(zhǔn)備再贊一些一起上傳到Pod中
- Scenes里有一個ZEVC是準(zhǔn)備以后所有開源項目中公用的反饋和登陸界面
項目架構(gòu)采用最基本的MVC
因為不是公司項目喜歡自己瞎搞一些東西所以一些被我改畸形了
這里詳細(xì)說一下:
我發(fā)現(xiàn)Swfit的Extension太過強(qiáng)大于是將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(){
}
首先強(qiáng)調(diào)一點...這是我自己胡亂嘗試敲出來的...沒有看過類似代碼..所以不推薦在正常項目中使用,只是講一下自己的思路希望能幫助到你
這樣做的好處我覺得有幾點
- 代碼都為C的代碼,不存在跨類調(diào)用方法和取值(高內(nèi)聚?)
- 上下兩個控制器傳值調(diào)用的時候也更方便一些(低耦合?)
- C代碼很少,只把主要的方法C中,如果想擴(kuò)展相應(yīng)功能或修改bug可以直接找到位置去進(jìn)行操作
我覺得不好的地方
- 代碼較為混亂,不適合多人開發(fā)?
- 具體功能重用性差,不利于其他項目使用
我一直自己摸索學(xué)習(xí)Swift,還沒有找到它正確的開發(fā)姿勢,希望大家能教我正確的開發(fā)姿勢
項目中還有一小部分面向協(xié)議
如給UIView添加一個滑動手勢,這里應(yīng)該如果優(yōu)化一下應(yīng)該 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)
}
}
一些細(xì)節(jié)
水印的繪制是由EditModel這個類實現(xiàn)的
原理就是利用UIGraphics 的 UIImage context 先按照圖片尺寸繪制出背景圖片,然后把label一個一個的繪制到圖片上 最后導(dǎo)出一張繪制好的圖片保存到相冊中
/**
添加水印
*/
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()// 結(jié)束繪制
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這個類來實現(xiàn)的,因為偷懶所以直接繼承自UILabel(獲取尺寸的時候回方便一些),然后在Label下面添加了一個TextField,來進(jìn)行文字編輯.
// 通過傳入的bool進(jìn)行l(wèi)abel的文字變換
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()
}
每次編輯水印的步驟我設(shè)計成 長按Label -> 彈出EditView -> 每一次操作都做相應(yīng)的處理(緩存,改變Label的狀態(tài))
label的樣式是由NSMutableAttributedString這個類來實現(xiàn)的,這個類可以直接用在繪制里
難點
Label和ImageView的比例計算
我給ImageView的class聲明了一個scale屬性,調(diào)用這個屬性就會計算出比例的變量
繪制Label的時候就按照1/scale的比例逆推回原大小進(jìn)行繪制
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")
}
}
水印的本地緩存
緩存格式:

顏色緩存是一個坑...如果用系統(tǒng)自帶的顏色直接緩存的話會有問題,因為width black gary 和其他顏色的屬性是不一樣的,這三類顏色只有黑色和透明度而其他顏色都是RGB 不能進(jìn)行統(tǒng)一處理
于是我就自己歸檔了一個顏色數(shù)組(查系統(tǒng)的色值還挺麻煩的..)
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]],
...
]
然后聲明了兩個方法,一個是字典轉(zhuǎn)顏色,一個是顏色轉(zhuǎn)字典
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)
}
}
寫入緩存的時機(jī)是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()// 同步數(shù)據(jù)
}
func getUD(key:String) -> AnyObject? {
return NSUserDefaults.standardUserDefaults().objectForKey(key)
}
labelModel初始化的時候就會取緩存,如果沒取到就會給一個默認(rèn)值
init(){
if let text = getUD(textUDK) as? String {
self.text = text
}else{
text = "這是第一個水印"
}
}
當(dāng)ImageView被拖拽放大時 相對 Label的筆記變化
這個還未解決,在上線前已經(jīng)禁止掉了ImageView的手勢
每次imageView大小變化的時候用Label按照scale逆推回去就會發(fā)現(xiàn)比例不對,不能按照原比例繪制,求大神幫忙..
坑
歡迎來到吐槽時間...
LeanCloud-Swift-SDK就是一個坑啊...代碼難懂不說居然只支持iOS9.1以上版本...相信這一條就讓99%的項目不準(zhǔn)備引用了吧...
然后他們居然還在info.plist里面的short boundle version里面寫英文!握草我頭一次遇見打包時候這個Error類型,為此我還特意記了一條筆記....

推薦
推薦一個AppIcon快速生成插件,可以用Package Manager直接下載或者直接去git->https://github.com/kaphacius/IconMaker
使用方法:打開Assets->右鍵選中AppIcon->選中Make An App Icon -> 選擇圖片 -> 成功!

現(xiàn)在審核時各個型號的手機(jī)可以共用一套簡介圖片了(可能不是最近才改的,一直是另外一個小哥負(fù)責(zé)打包的 @辛勤的另外一個小哥)
結(jié)尾語
現(xiàn)在項目剛剛提交了審核,審核通過了再把a(bǔ)ppstore鏈接貼上...
我都是想起哪里說哪里...可能有些難懂,如果有不懂的地方可以在評論里和我說我會及時修改文章的
睡覺明早起來再改錯別字...