// // KMWatermarkManager.swift // PDF Reader Pro // // Created by tangchao on 2022/12/19. // import Cocoa enum KMWatermarkKey: String { case watermarkType = "watermarkTypeKey" case text = "WatermarTextKey" case textFontSize = "WatermarkOfTextFontSizeKey" case textfontStyle = "WatermarkOfTextFontStyleKey" case textFontName = "textFontName" case textColor = "WatermarkTextColorKey" case red = "WatermarkOfRedKey" case green = "WatermarkOfGreenKey" case blue = "WatermarkOfBlueKey" case imagePath = "WatermarkOfImagePathKey" case rotate = "WatermarkOfRotationKey" case opacity = "WatermarkOfOpacityKey" case isScale = "WatermarkOfIsScaleKey" case scale = "WatermarkOfScaleKey" case verticalMode = "WatermarkOfVerticalModeKey" case verticalSpace = "WatermarkOfVerticalSpaceKey" case horizontalMode = "WatermarkOfHorizontalModeKey" case horizontalSpace = "WatermarkOfHorizontalSpaceKey" case isFront = "WatermarkOfIsFrontKey" case createDate = "WatermarkOfCreatTemplateDateKey" case pageRangeType = "WatermarkOfPageRangeTypeKey" case pageRangeString = "WatermarkOfPageStringKey" case tileVerticalSpace = "WatermarkTileVerticalSpaceKey" case tileHorizontalSpace = "WatermarkTileHorizontalSpaceKey" case isTile = "WatermarkOfIsTileKey" case lastAddData = "kAddLastWatermarkDataKey1" case id = "kWatermarkIDKey" case tag = "kWatermarkTagKey" case watermarkName = "watermarkNameKey" } class KMWatermarkManager: NSObject { let watermarkFolderPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!).stringByAppendingPathComponent("watermark") let watermarkPlistPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!).stringByAppendingPathComponent("watermark").stringByAppendingPathComponent("watermark.plist") let watermarkImageFolder = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!).stringByAppendingPathComponent("watermark").stringByAppendingPathComponent("image") static let defaultManager = KMWatermarkManager() var watermarks: Array = [] var defaultWatermarkData = KMWatermarkModel.defaultWDData() override init() { super.init() #if DEBUG print("watermarkPlistPath=\(watermarkPlistPath ?? "")") #endif if (!FileManager.default.fileExists(atPath: watermarkFolderPath!)) { try?FileManager.default.createDirectory(atPath: watermarkFolderPath!, withIntermediateDirectories: true, attributes: nil) } if (!FileManager.default.fileExists(atPath: watermarkImageFolder!)) { let create: ()? = try?FileManager.default.createDirectory(atPath: watermarkImageFolder!, withIntermediateDirectories: false) } if (FileManager.default.fileExists(atPath: watermarkPlistPath!)) { let dataDict = NSDictionary(contentsOfFile: watermarkPlistPath!) for keyIndex in 0 ..< (dataDict?.allKeys.count ?? 0) { let key: String = dataDict?.allKeys[keyIndex] as! String let watermarkDict: NSDictionary = dataDict?.object(forKey: key) as! NSDictionary let model = parseDictionary(dict: watermarkDict) watermarks.append(model) } watermarks.sort(){$0.tag > $1.tag} } } //MARK: - 增删改查 func addWatermark(watermark: KMWatermarkModel) -> Bool { if (!FileManager.default.fileExists(atPath: watermarkFolderPath!)) { let create: ()? = try?FileManager.default.createDirectory(atPath: watermarkFolderPath!, withIntermediateDirectories: true, attributes: nil) if (create == nil) { return false } } if (!FileManager.default.fileExists(atPath: watermarkPlistPath!)) { FileManager.default.createFile(atPath: watermarkPlistPath!, contents: nil) } let dict = NSDictionary(contentsOfFile: watermarkPlistPath!) var newDict:NSMutableDictionary! if (dict != nil) { newDict = NSMutableDictionary(dictionary: dict!) } else { newDict = NSMutableDictionary() } let watermarkDict = self.parseWaterMark(model: watermark) if (watermarkDict.isEmpty) { return false } let key: String = watermark.tag if newDict.object(forKey: key) != nil { newDict.setObject(watermarkDict, forKey: key as NSCopying) } else { newDict.addEntries(from: [key : watermarkDict]) } let result = newDict.write(toFile: watermarkPlistPath!, atomically: true) if (result) { if (self.watermarks.count < 1) { self.watermarks.append(watermark) } else { self.watermarks.insert(watermark, at: 0) } watermark.updatePreviewImage() } return result } func updateWatermark(watermark: KMWatermarkModel) -> Bool { if (!FileManager.default.fileExists(atPath: watermarkFolderPath!)) { let create: ()? = try?FileManager.default.createDirectory(atPath: watermarkFolderPath!, withIntermediateDirectories: false) if (create == nil) { return false } } if (!FileManager.default.fileExists(atPath: watermarkPlistPath!)) { FileManager.default.createFile(atPath: watermarkPlistPath!, contents: nil) } var flagModel: KMWatermarkModel! for model in self.watermarks { if (model.tag == watermark.tag) { flagModel = model break } } if (flagModel == nil) { return false } let dict = NSDictionary(contentsOfFile: watermarkPlistPath!) var newDict:NSMutableDictionary! if (dict != nil) { newDict = NSMutableDictionary(dictionary: dict!) } else { newDict = NSMutableDictionary() } let watermarkDict = self.parseWaterMark(model: watermark) if (watermarkDict.isEmpty) { return false } newDict.setObject(watermarkDict, forKey: flagModel.tag as NSCopying) let result = newDict.write(toFile: watermarkPlistPath!, atomically: true) if (result) { if let index = self.watermarks.firstIndex(of: flagModel) { self.watermarks[index] = watermark } watermark.updatePreviewImage() } return result } func removeWatermark(watermark: KMWatermarkModel) -> Bool { if (!FileManager.default.fileExists(atPath: watermarkPlistPath!)) { return false } let key: String = watermark.tag let dictionary = NSDictionary(contentsOfFile: watermarkPlistPath!) var newDictionary: NSMutableDictionary! if (dictionary != nil) { newDictionary = NSMutableDictionary(dictionary: dictionary!) } else { newDictionary = NSMutableDictionary() } newDictionary.removeObject(forKey: key) let result = newDictionary.write(toFile: watermarkPlistPath!, atomically: true) if (result) { if let filePath: String = watermark.imagePath { try?FileManager.default.removeItem(atPath: filePath) } if (self.watermarks.contains(watermark)) { self.watermarks.removeObject(watermark) } } return result } func removeAllWatermark() -> Bool { if (!FileManager.default.fileExists(atPath: watermarkPlistPath!)) { return false } let dictionary = NSDictionary(contentsOfFile: watermarkPlistPath!) var newDictionary: NSMutableDictionary! if (dictionary != nil) { newDictionary = NSMutableDictionary(dictionary: dictionary!) } else { newDictionary = NSMutableDictionary() } newDictionary.removeAllObjects() let result = newDictionary.write(toFile: watermarkPlistPath!, atomically: true) if (result) { self.watermarks.removeAll() } return result } //MARK: - private func setDictToWatermarK(dict: NSDictionary, _ model: KMWatermarkModel) { model.watermarkType = .text if let typeIndex = dict.object(forKey: KMWatermarkKey.watermarkType.rawValue) as? AnyObject { if let rawValue = typeIndex as? Int { model.watermarkType = CPDFWatermarkType(rawValue: rawValue) ?? .text } else if let rawValue = typeIndex as? String, let rawIndex = Int(rawValue) { model.watermarkType = CPDFWatermarkType(rawValue: rawIndex) ?? .text } } model.text = (dict.object(forKey: KMWatermarkKey.text.rawValue) as? String) model.fontName = (dict.object(forKey: KMWatermarkKey.textFontName.rawValue) as? String) ?? "" model.fontStyle = (dict.object(forKey: KMWatermarkKey.textfontStyle.rawValue) as? String) ?? "" model.fontSize = (dict.object(forKey: KMWatermarkKey.textFontSize.rawValue) as? CGFloat) ?? 12 if let pathValue = dict.object(forKey: KMWatermarkKey.imagePath.rawValue) { let path = watermarkFolderPath?.stringByAppendingPathComponent(pathValue as! String) if (FileManager.default.fileExists(atPath: path!)) { model.imagePath = path ?? "" } } model.isScale = (dict.object(forKey: KMWatermarkKey.isScale.rawValue) as? Bool) ?? false model.scale = (dict.object(forKey: KMWatermarkKey.scale.rawValue) as? CGFloat) ?? 1 model.rotation = dict.object(forKey: KMWatermarkKey.rotate.rawValue) as? CGFloat ?? 0 model.opacity = (dict.object(forKey: KMWatermarkKey.opacity.rawValue) as? CGFloat) ?? 1 model.verticalPosition = (dict.object(forKey: KMWatermarkKey.verticalMode.rawValue) as? CPDFWatermarkVerticalPosition) ?? .center model.ty = dict.object(forKey: KMWatermarkKey.verticalSpace.rawValue) as? CGFloat ?? 0 model.horizontalPosition = (dict.object(forKey: KMWatermarkKey.horizontalMode.rawValue) as? CPDFWatermarkHorizontalPosition) ?? .center model.tx = dict.object(forKey: KMWatermarkKey.horizontalSpace.rawValue) as? CGFloat ?? 0 if let textColorKey = dict.object(forKey: KMWatermarkKey.textColor.rawValue) as? String { model.textColor = NSColor.km_init(hex: textColorKey) } else { let red: CGFloat = dict.object(forKey: KMWatermarkKey.red.rawValue) as? CGFloat ?? 0 let green: CGFloat = dict.object(forKey: KMWatermarkKey.green.rawValue) as? CGFloat ?? 0 let blue: CGFloat = dict.object(forKey: KMWatermarkKey.blue.rawValue) as? CGFloat ?? 0 model.textColor = NSColor(red: red, green: green, blue: blue, alpha: 1.0) } model.isFront = (dict.object(forKey: KMWatermarkKey.isFront.rawValue) as? Bool) ?? false model.isTilePage = (dict.object(forKey: KMWatermarkKey.isTile.rawValue) as? Bool) ?? false model.horizontalSpacing = (dict.object(forKey: KMWatermarkKey.tileHorizontalSpace.rawValue) as? CGFloat) ?? 0 model.verticalSpacing = (dict.object(forKey: KMWatermarkKey.tileVerticalSpace.rawValue) as? CGFloat) ?? 0 model.tag = (dict.object(forKey: KMWatermarkKey.tag.rawValue) as? String) ?? "" model.watermarkName = (dict.object(forKey: KMWatermarkKey.watermarkName.rawValue) as? String) ?? "" } //MARK: - Parse func parseWaterMark(model: KMWatermarkModel) -> Dictionary { var dict: [String : Any] = [:] if model.watermarkType == .text { dict[KMWatermarkKey.watermarkType.rawValue] = "0" } else if model.watermarkType == .image { dict[KMWatermarkKey.watermarkType.rawValue] = "1" } if let text = model.text { dict[KMWatermarkKey.text.rawValue] = text } dict[KMWatermarkKey.textFontName.rawValue] = model.fontName dict[KMWatermarkKey.textfontStyle.rawValue] = model.fontStyle dict[KMWatermarkKey.textFontSize.rawValue] = model.fontSize if let imagePath = model.imagePath { dict[KMWatermarkKey.imagePath.rawValue] = imagePath } dict[KMWatermarkKey.isFront.rawValue] = model.isFront dict[KMWatermarkKey.isScale.rawValue] = model.isScale dict[KMWatermarkKey.scale.rawValue] = model.scale dict[KMWatermarkKey.opacity.rawValue] = model.opacity dict[KMWatermarkKey.rotate.rawValue] = model.rotation dict[KMWatermarkKey.verticalMode.rawValue] = model.verticalPosition.rawValue dict[KMWatermarkKey.verticalSpace.rawValue] = model.ty dict[KMWatermarkKey.horizontalMode.rawValue] = model.horizontalPosition.rawValue dict[KMWatermarkKey.horizontalSpace.rawValue] = model.tx dict[KMWatermarkKey.textColor.rawValue] = model.textColor.toHex() dict[KMWatermarkKey.isTile.rawValue] = model.isTilePage dict[KMWatermarkKey.tileVerticalSpace.rawValue] = model.verticalSpacing dict[KMWatermarkKey.tileHorizontalSpace.rawValue] = model.horizontalSpacing dict[KMWatermarkKey.tag.rawValue] = model.tag dict[KMWatermarkKey.watermarkName.rawValue] = model.watermarkName return dict } private func parseDictionary(dict: NSDictionary) -> KMWatermarkModel { let model = KMWatermarkModel() model.watermarkType = (dict.object(forKey: KMWatermarkKey.watermarkType.rawValue) as? CPDFWatermarkType) ?? .text model.text = (dict.object(forKey: KMWatermarkKey.text.rawValue) as? String) model.fontName = (dict.object(forKey: KMWatermarkKey.textFontName.rawValue) as? String) ?? "" model.fontStyle = (dict.object(forKey: KMWatermarkKey.textfontStyle.rawValue) as? String) ?? "" model.fontSize = (dict.object(forKey: KMWatermarkKey.textFontSize.rawValue) as? CGFloat) ?? 12 if let pathValue = dict.object(forKey: KMWatermarkKey.imagePath.rawValue) { model.imagePath = pathValue as? String } model.isScale = (dict.object(forKey: KMWatermarkKey.isScale.rawValue) as? Bool) ?? false model.scale = (dict.object(forKey: KMWatermarkKey.scale.rawValue) as? CGFloat) ?? 1 model.rotation = dict.object(forKey: KMWatermarkKey.rotate.rawValue) as? CGFloat ?? 0 model.opacity = (dict.object(forKey: KMWatermarkKey.opacity.rawValue) as? CGFloat) ?? 1 model.verticalPosition = (dict.object(forKey: KMWatermarkKey.verticalMode.rawValue) as? CPDFWatermarkVerticalPosition) ?? .center model.ty = dict.object(forKey: KMWatermarkKey.verticalSpace.rawValue) as? CGFloat ?? 0 model.horizontalPosition = (dict.object(forKey: KMWatermarkKey.horizontalMode.rawValue) as? CPDFWatermarkHorizontalPosition) ?? .center model.tx = dict.object(forKey: KMWatermarkKey.horizontalSpace.rawValue) as? CGFloat ?? 0 if let textColorKey = dict.object(forKey: KMWatermarkKey.textColor.rawValue) as? String { model.textColor = NSColor.km_init(hex: textColorKey) } else { let red: CGFloat = dict.object(forKey: KMWatermarkKey.red.rawValue) as? CGFloat ?? 0 let green: CGFloat = dict.object(forKey: KMWatermarkKey.green.rawValue) as? CGFloat ?? 0 let blue: CGFloat = dict.object(forKey: KMWatermarkKey.blue.rawValue) as? CGFloat ?? 0 model.textColor = NSColor(red: red, green: green, blue: blue, alpha: 1.0) } model.isFront = (dict.object(forKey: KMWatermarkKey.isFront.rawValue) as? Bool) ?? false model.isTilePage = (dict.object(forKey: KMWatermarkKey.isTile.rawValue) as? Bool) ?? false model.horizontalSpacing = (dict.object(forKey: KMWatermarkKey.tileHorizontalSpace.rawValue) as? CGFloat) ?? 0 model.verticalSpacing = (dict.object(forKey: KMWatermarkKey.tileVerticalSpace.rawValue) as? CGFloat) ?? 0 model.tag = (dict.object(forKey: KMWatermarkKey.tag.rawValue) as? String) ?? "" model.watermarkName = (dict.object(forKey: KMWatermarkKey.watermarkName.rawValue) as? String) ?? "" return model } //MARK: - Getter func drawImageAtpageRect(rect: NSRect, data: KMWatermarkModel) -> NSImage? { var size = NSZeroSize let text: String = data.text ?? "" if data.watermarkType == .image { if let image = data.image() { size = image.size size.width *= data.scale size.height *= data.scale } } else if data.watermarkType == .text { var font = NSFont.systemFont(ofSize: 52) if data.isTilePage { font = NSFont.systemFont(ofSize: data.fontSize) } let style = NSMutableParagraphStyle() style.alignment = .center style.lineBreakMode = .byCharWrapping let attributes: [NSAttributedString.Key: Any] = [ .paragraphStyle: style, .font: font ] size = text.boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes).size } let radian = data.rotation * (CGFloat.pi/180) let t = CGAffineTransform(rotationAngle: radian) var newRect = rect if !data.isTilePage { newRect = CGRect(x: 0, y: 0, width: size.width + 40, height: size.height + 40).applying(t) } let image = NSImage(size: newRect.size) image.lockFocus() NSGraphicsContext.current?.imageInterpolation = .high NSGraphicsContext.saveGraphicsState() NSColor.clear.set() rect.fill() NSGraphicsContext.restoreGraphicsState() guard let context = NSGraphicsContext.current?.cgContext else { return nil } let imageSize = newRect.size NSGraphicsContext.current = NSGraphicsContext(cgContext: context, flipped: false) NSGraphicsContext.saveGraphicsState() if data.watermarkType == .image { guard let image = data.image() else { return nil } var size = image.size size.width *= data.scale size.height *= data.scale let width = sqrt(image.size.height * image.size.height + image.size.width * image.size.width) let newRect = CGRect(x: -(width - image.size.width)/2, y: -(width - image.size.height)/2, width: width, height: width) let new_w = newRect.size.width let new_h = newRect.size.height let radian = data.rotation * (CGFloat.pi / 180) let t = CGAffineTransform(rotationAngle: radian) if data.isTilePage { context.translateBy(x: image.size.width/2, y: image.size.height/2) context.concatenate(t) context.translateBy(x: -(image.size.width/2), y: -(image.size.height/2)) let verticalWidth = size.width + data.horizontalSpacing * data.scale let horizontalHeight = size.height + data.verticalSpacing * data.scale let line: Int = Int((new_h - data.verticalSpacing * data.scale)/horizontalHeight + 1) let row: Int = Int((new_w - data.horizontalSpacing * data.scale) / verticalWidth + 1) let point = CGPoint(x: image.size.width/2 - size.width/2 + data.tx*data.scale, y:image.size.height/2 - size.height/2 + data.ty * data.scale) for i in 0.. 0 { var font = NSFont.systemFont(ofSize: 52) if data.isTilePage { font = NSFont.systemFont(ofSize: data.fontSize) } var color = data.textColor var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0 color.usingColorSpaceName(.calibratedRGB)?.getRed(&red, green: &green, blue: &blue, alpha: nil) color = NSColor.init(calibratedRed: red, green: green, blue: blue, alpha: data.opacity) size = .zero let alpha = data.opacity let tileHorizontalSpace = data.horizontalSpacing let tileVerticalSpace = data.verticalSpacing let horizontalSpace = data.tx let verticalSpace = data.ty let style = NSMutableParagraphStyle() style.alignment = .center style.lineBreakMode = .byCharWrapping let attributes: [NSAttributedString.Key: Any] = [ .paragraphStyle: style, .foregroundColor: NSColor(calibratedRed: red, green: green, blue: blue, alpha: alpha), .font: font ] size = text.boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes).size let textRect = CGRect(x: 0, y: 0, width: size.width, height: size.height) if data.isTilePage { let width = sqrt(image.size.height * image.size.height + image.size.width * image.size.width) let newRect = CGRect(x: -(width - image.size.width) / 2, y: -(width - image.size.height) / 2, width: width, height: width) let new_w = newRect.size.width let new_h = newRect.size.height context.translateBy(x: image.size.width / 2, y: image.size.height / 2) context.concatenate(CGAffineTransform(rotationAngle: radian)) context.translateBy(x: -(image.size.width / 2), y: -(image.size.height / 2)) let verticalWidth = size.width + tileHorizontalSpace / 3.0 let horizontalHeight = size.height + tileVerticalSpace / 3.0 let line: Int = Int((new_h - tileHorizontalSpace / 3.0) / horizontalHeight + 1) let row: Int = Int((new_w - tileVerticalSpace / 3.0) / verticalWidth + 1) let point = CGPoint(x: image.size.width / 2 - size.width / 2 + horizontalSpace / 3.0, y: image.size.height / 2 - size.height / 2 + verticalSpace / 3.0) for i in 0.. [String] { return ["6","8","10","12","14", "16","18","20","22","24", "26","28","30","32","34", "36","40","48","64","80", "96","112"] } func fetchAvailableName() -> String { var availableIndex: Int = 0 let watermark = "Watermark" for model in self.watermarks { let watermarkID = model.tag if (watermarkID.hasPrefix(watermark)) { let indexString: String = (watermarkID.components(separatedBy: watermark).last ?? "0") let index: Int = Int(indexString) ?? 0 if (index >= availableIndex) { availableIndex = index + 1 } } } return "\(watermark)\(availableIndex)" } //MARK: - Compare class func compareIsChangedModel(_ model: KMWatermarkModel, withDict dict: NSDictionary) -> Bool { if let value = dict["fontName"] { if model.fontName != (value as! String) { return true } } if let value = dict.object(forKey: KMWatermarkKey.text.rawValue) { if model.text != (value as! String) { return true } } if let value = dict.object(forKey: KMWatermarkKey.textFontName.rawValue) { if model.fontName != (value as! String) { return true } } if let value = dict.object(forKey: KMWatermarkKey.textfontStyle.rawValue) { if model.fontStyle != (value as! String) { return true } } if let value = dict.object(forKey: KMWatermarkKey.textFontSize.rawValue) { if model.fontSize != (value as! CGFloat) { return true } } if let value = dict.object(forKey: KMWatermarkKey.imagePath.rawValue) { if model.imagePath != (value as! String) { return true } } if let value = dict.object(forKey: KMWatermarkKey.isScale.rawValue) { if model.isScale != (value as! Bool) { return true } } if let value = dict.object(forKey: KMWatermarkKey.scale.rawValue) { if model.scale != (value as! CGFloat) { return true } } if let value = dict.object(forKey: KMWatermarkKey.rotate.rawValue) { if model.rotation != (value as! CGFloat) { return true } } if let value = dict.object(forKey: KMWatermarkKey.opacity.rawValue) { if model.opacity != (value as! CGFloat) { return true } } if let value = dict.object(forKey: KMWatermarkKey.verticalMode.rawValue) { if model.verticalPosition.rawValue != (value as! Int) { return true } } if let value = dict.object(forKey: KMWatermarkKey.verticalSpace.rawValue) { if model.ty != (value as! CGFloat) { return true } } if let value = dict.object(forKey: KMWatermarkKey.horizontalMode.rawValue) { if model.horizontalPosition.rawValue != (value as! Int) { return true } } if let value = dict.object(forKey: KMWatermarkKey.horizontalSpace.rawValue) { if model.tx != (value as! CGFloat) { return true } } if let value = dict.object(forKey: KMWatermarkKey.textColor.rawValue) { var modelColorText = model.textColor.colorToHexString() if modelColorText.count > 7 { modelColorText = modelColorText.substring(to: 7) } if var valueString = value as? String { if valueString.count > 7 { valueString = valueString.substring(to: 7) } if valueString != modelColorText { return true } } } if let value = dict.object(forKey: KMWatermarkKey.isFront.rawValue) { if model.isFront != (value as! Bool) { return true } } if let value = dict.object(forKey: KMWatermarkKey.isTile.rawValue) { if model.isTilePage != (value as! Bool) { return true } } if let value = dict.object(forKey: KMWatermarkKey.tileHorizontalSpace.rawValue) { if model.horizontalSpacing != (value as! CGFloat) { return true } } if let value = dict.object(forKey: KMWatermarkKey.tileVerticalSpace.rawValue) { if model.verticalSpacing != (value as! CGFloat) { return true } } return false } }