preface

Here’s a quote from the app description:

My wife was doing wechat business very hard, so SHE wanted to make a software to relieve her pressure, so this software was born. With her encouragement, I put this software online on Appstroe, hoping to help you

The code of this project is all open source, and only the id and key related to LeanCloud are deleted. If I accidentally push the key and ID up in the future, please remind me.

This project was hammered out in my spare time… A lot of code is typed out as you try it out… So you can pick and choose

I plan to write a series of blog posts about WaterMark and other open source projects in the future, with each update providing a summary of the difficulties and pitfalls… If you like, you can collect it and support it

Hopefully this blog will help newbies understand the common sense of iOS project development

Welcome to discuss the correct development posture

Kneel and beg god to fly me!

(In the English residue, please watch the use of your brain runtime to replace the wrong words with pairs of words… Ha ha ha, I will recite the words well!

Project address :github.com/Lafree317/W…

AppStroe:itunes.apple.com/WebObjects/…

Return to the chase

Current Functions:

  • Add watermark to full size/thumbnail
  • Watermark cache
  • Watermark editor

architecture

Project file structure

The third party open source libraries currently integrated in the project are:

  • Pod
    • LeanCloud Swift-Alpha (Pit): Cloud storage for feedback data
    • MBProgressHUD 1.0: Prompt control is required for each project
    • RxSwift and RxCocoa: it is not used in the code yet, so prepare the write response when writing the login
    • SnapKit: Swift version of Messary fried chicken easy to use
  • Vendors
    • ShowString my little brother modified a prompt box, the advantage is that the prompt can also be UI interaction on the page
    • TZImagePickerController is a 1000+star image selection library, this developer is very interested in issuing issues and will reply to you soon
    • IGLDropDownMenu a drawer-down animation, very convenient to use
    • ZEViewKit I package some small controls, ready to like some together uploaded to Pod
    • There is a ZEVC in the Scenes that is intended to be a common feedback and login interface for all future open source projects

The project architecture uses the most basic MVC

Because they are not corporate projects and like to mess around with things, some of them have been deformed by me. Here are the details:

I found Swfit’s Extension was too powerful, so I replaced the Model layer and View layer with Extension. For example, I changed the code of feedBackController to:

  • Controller

    class ZEFeedBackContoller: UIViewController.UITextFieldDelegate.UIScrollViewDelegate {
      // Various attributes
    
      override func viewDidLoad(a) {
          super.viewDidLoad()
    
          setUI()
      }Copy the code
  • Model

    extension ZEFeedBackContoller {
      / / model method
      func sendFeedBack(a){}}Copy the code
  • View

    extension ZEFeedBackContoller {
      / / add the UI
      func setUI(a){}Copy the code

First of all… I tried to knock it out myself… Never seen code like this.. So it’s not recommended to use it in normal projects, but I just want to talk about my ideas and hopefully help you

I think there are several advantages to this

  • C code with no cross-class method calls and values (high cohesion?)
  • It is also easier for the upper and lower controllers to pass value calls (low coupling?)
  • C code is very small, only the main method in C, if you want to expand the corresponding function or modify the bug can directly find the location to operate

I feel bad about it

  • Is the code messy and not suitable for multiplayer development?
  • Poor reuse of specific functions is not conducive to the use of other projects

I have been learning Swift by myself and haven’t found the correct development posture. I hope you can teach me the correct development posture

There is also a small protocol-oriented part of the project

Like adding a swipe gesture to UIView, this should be extended to a concrete class if optimized, instead of directly adding a..

protocol ViewGestureRecognizer{}extension UIView:ViewGestureRecognizer{
    func addPan(a){
        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)}}Copy the code

Some of the details

The watermarking is done by the EditModel class

The principle is to use the UIImage context of UIGraphics to first draw the background picture according to the size of the picture, then draw the labels on the picture one by one and finally export a drawn picture and save it in the album

    /** Add watermark */
    func save(a){
        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)// Start drawing
            image.drawInRect(CGRect(origin: CGPoint.zero, size: image.size))
            for label in wself.labelArr { // Add multiple watermarks
                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(a)// Get the image
            UIGraphicsEndImageContext(a)// Finish drawing
            UIImageWriteToSavedPhotosAlbum(imageA, self.nil.nil)/ / save
            dispatch_async(dispatch_get_main_queue(), {
                ZEHud.sharedInstance.hideHud()
                ShowString.sharedManager().showStringView("Saved successfully")
                wself.imageView.image = imageA
                wself.assets.removeAtIndex(wself.index)
                wself.changeImage(wself.index)
                ifweakSelf! .assets.count= =0{ weakSelf? .performSelector(#selector(weakSelf? .nodataPop), withObject:nil, afterDelay: 0.75)}})}}Copy the code

WaterMark is implemented in the project by the WaterMark class, which inherits from UILabel(easy to get the size), and then adds a TextField under the Label to edit the text.

    // Label text is converted by the bool passed in
    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() }Copy the code

I designed the step of editing the watermark into long press Label -> pop up EditView -> each operation to do the corresponding processing (cache, change the state of Label)

The style of the label is implemented by NSMutableAttributedString this class, this class can be directly used in the drawing

The difficulties in

Calculate the ratio of Label to ImageView

I declare a scale property for the ImageView class. Calling this property will calculate the scale variable

When the Label is drawn, it is drawn back to its original size in a scale of 1/scale

class EditImageView: UIImageView {
    var scale:CGFloat {
        get{
            returnframe.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")}}Copy the code

A local cache of watermarks

Cache format:

Color cache is a pit… There will be problems if the system color is used to cache directly, because the attributes of Width Black Gary and other colors are different. These three colors only have black and transparency, while other colors are RGB and cannot be processed uniformly

So I archived an array of colors myself (it was a hassle to check the system colors).

let titleColorArr:Array"[String: [String:CGFloat[[]] > ="Black": ["red":0."green":0."blue":0."alpha":1]],
    ["Gray": ["red":102."green":102."blue":102."alpha":1]],
    ["White": ["red":255."green":255."blue":255."alpha":1]],
    ["Transparent": ["red":255."green":255."blue":255."alpha":0]],... ]Copy the code

Then we declare two methods, one is dictionary to color, one is color to dictionary

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)
    }
}Copy the code

The time to write to the cache is when each property of the LabelModel changes

    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()// Synchronize data
    }
    func getUD(key:String) -> AnyObject? {
        return NSUserDefaults.standardUserDefaults().objectForKey(key)
    }Copy the code

When the labelModel is initialized, it will fetch the cache, and if it doesn’t, it will give a default value

init() {if  let text =  getUD(textUDK) as? String {
            self.text = text
        }else{
            text = "This is the first watermark."}}Copy the code

Note changes relative to the Label when the ImageView is dragged and enlarged

This is not fixed yet, the ImageView gesture has been disabled before it goes live

Every time the imageView size changes, use the Label to push back according to the scale. It will find that the scale is not correct, and can not draw according to the original scale.

pit

Welcome to joke time…

Leancloud-swift-sdk is a pit… Support only iOS9.1 and above… Believe this and 99% of projects are not going to reference it…

Then they even write English in the short boundle version of info.plist! I first encountered this Error type when packing, for which I also specially wrote down a note….

recommended

A quick plugin for AppIcon is recommended, which can be downloaded directly using Package Manager or directly go to git->github.com/kaphacius/I…

Use method: Open Assets-> right-click AppIcon-> Make An AppIcon-> Select pictures -> Success!

It is now possible for all models of mobile phones to share one set of profile pictures (maybe not just changed recently, it is always another guy who is responsible for packing @ another guy of Hard work)

The closing

Now the project has just been submitted for review, and the appstore link is pasted after the review is approved…

I’m talking about everything… It may be difficult to understand. If you don’t understand anything, please tell me in the comments and I will revise the article in time

Go to bed and wake up tomorrow morning to correct the typos…