前言
这里引用应用描述里的一句话:
本项目代码全部开源,只删除了LeanCloud相关的id和key,如果我以后不小心把key和id push上去了各位小伙伴一定要提醒我哈…
这个项目是业余时间瞎敲出来的…很多代码都是自己一边尝试一边敲出来的…所以大家可以挑重点看
我准备把WaterMark 和以后其他的开源项目写成一个系列的博客,每次更新版本都会把遇到的难点和坑点总结出来..喜欢的话大家可以收藏,关注支持一下
希望本篇博客能帮助菜鸟了解iOS项目开发中的常识
欢迎大家一起讨论正确的开发姿势
跪求大神带我飞!
(在下英文渣,请各位老爷观看时利用脑内runtime 把不对的单词替换成对的单词…哈哈哈,我会好好背单词的!)
项目地址:https://github.com/Lafree317/WaterLabel
AppStroe:https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1148289486&mt=8
回归正题
目前功能:
- 原尺寸/缩略图 添加水印
- 水印缓存
- 水印编辑
架构
项目文件结构
项目中现在集成的第三方开源库有:
- 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
1
2
3
4
5
6
7
8class ZEFeedBackContoller: UIViewController,UITextFieldDelegate,UIScrollViewDelegate {
// 各种属性
override func viewDidLoad() {
super.viewDidLoad()
setUI()
}Model
1
2
3
4
5extension ZEFeedBackContoller {
// model方法
func sendFeedBack(){
}
}View
1
2
3
4extension ZEFeedBackContoller {
// 添加UI
func setUI(){
}
首先强调一点…这是我自己胡乱尝试敲出来的…没有看过类似代码..所以不推荐在正常项目中使用,只是讲一下自己的思路希望能帮助到你
这样做的好处我觉得有几点
- 代码都为C的代码,不存在跨类调用方法和取值(高内聚?)
- 上下两个控制器传值调用的时候也更方便一些(低耦合?)
- C代码很少,只把主要的方法C中,如果想扩展相应功能或修改bug可以直接找到位置去进行操作
我觉得不好的地方
- 代码较为混乱,不适合多人开发?
- 具体功能重用性差,不利于其他项目使用
我一直自己摸索学习Swift,还没有找到它正确的开发姿势,希望大家能教我正确的开发姿势
项目中还有一小部分面向协议
如给UIView添加一个滑动手势,这里应该如果优化一下应该 extenion到具体类 而不是直接给UIView添加..1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16protocol 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一个一个的绘制到图片上 最后导出一张绘制好的图片保存到相册中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36/**
添加水印
*/
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,来进行文字编辑.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 通过传入的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的比例逆推回原大小进行绘制
1 | class EditImageView: UIImageView { |
水印的本地缓存
缓存格式:
颜色缓存是一个坑…如果用系统自带的颜色直接缓存的话会有问题,因为width black gary 和其他颜色的属性是不一样的,这三类颜色只有黑色和透明度而其他颜色都是RGB 不能进行统一处理
于是我就自己归档了一个颜色数组(查系统的色值还挺麻烦的..)
1 | let titleColorArr:Array<[String:[String:CGFloat]]> = [ |
然后声明了两个方法,一个是字典转颜色,一个是颜色转字典
1 | class ColorFile { |
写入缓存的时机是LabelModel每一个属性改变的时候1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var 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初始化的时候就会取缓存,如果没取到就会给一个默认值1
2
3
4
5
6
7init(){
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->https://github.com/kaphacius/IconMaker
使用方法:打开Assets->右键选中AppIcon->选中Make An App Icon -> 选择图片 -> 成功!
现在审核时各个型号的手机可以共用一套简介图片了(可能不是最近才改的,一直是另外一个小哥负责打包的 @辛勤的另外一个小哥)
结尾语
现在项目刚刚提交了审核,审核通过了再把appstore链接贴上…
我都是想起哪里说哪里…可能有些难懂,如果有不懂的地方可以在评论里和我说我会及时修改文章的
睡觉明早起来再改错别字…