From 3765c771ec72eb5410dde68757427aea1134bdea Mon Sep 17 00:00:00 2001 From: Kyle Matthew Hedden Date: Tue, 16 Aug 2022 20:15:43 -0400 Subject: [PATCH] model throw, view adapt --- .../Atomic/Atoms/Views/Label/Label.swift | 50 ++++++++++++------- .../Label/LabelAttributeImageModel.swift | 13 +++-- .../Views/Label/LabelAttributeModel.swift | 21 ++++---- .../Atomic/Atoms/Views/Label/LabelModel.swift | 27 +++++----- .../MoleculeModelProtocol.swift | 1 + 5 files changed, 65 insertions(+), 47 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 47d6c69b..3fe4ab77 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -326,7 +326,9 @@ public typealias ActionBlock = () -> () let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: font.updateSize(standardFontSize), NSAttributedString.Key.foregroundColor: textColor as UIColor]) for attribute in attributes { - let range = NSRange(location: attribute.location, length: attribute.length) + guard let range = validateAttribute(range: NSRange(location: attribute.location, length: attribute.length) , in: attributedString) + else { continue } + switch attribute { case let underlineAtt as LabelAttributeUnderlineModel: attributedString.addAttribute(.underlineStyle, value: underlineAtt.underlineValue.rawValue, range: range) @@ -458,10 +460,9 @@ public typealias ActionBlock = () -> () for case let attribute as [String: Any] in attributes { guard let attributeType = attribute.optionalStringForKey(KeyType), let location = attribute["location"] as? Int, - let length = attribute["length"] as? Int - else { continue } - - let range = NSRange(location: location, length: length) + let length = attribute["length"] as? Int, + let range = validateAttribute(range: NSRange(location: location, length: length), in: attributedString) + else { continue } switch attributeType { case "underline": @@ -834,19 +835,9 @@ extension Label { private func addActionAttributes(range: NSRange, string: NSMutableAttributedString?) { guard let string = string, - range.location > 0 && range.location < string.length - else { - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute starting location \(range.lowerBound) is out of bounds for '\(string?.string ?? "")'. Attribute is discarded.", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) - return - } - - var range = range - if range.upperBound > string.length { - let newRange = NSRange(location: range.location, length: string.length - range.location) - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute ending location \(range.upperBound) is out of bounds for '\(string)'. Adjusting to \(newRange.upperBound).", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) - range = newRange - } - + let range = validateAttribute(range: range, in: string) + else { return } + string.addAttributes([NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue], range: range) } @@ -1005,3 +996,26 @@ extension Label { return false } } + +//------------------------------------------------------ +// MARK: - Validations +//------------------------------------------------------ + +func validateAttribute(range: NSRange, in string: NSAttributedString) -> NSRange? { + guard range.location >= 0 && range.location < string.length else { + if let loggingHandler = MVMCoreLoggingHandler.shared(), loggingHandler.responds(to: #selector(MVMCoreLoggingHandler.addError(toLog:))) { + loggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute starting location \(range.lowerBound) is out of bounds for '\(string.string)'. Attribute is discarded.", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) + } + return nil + } + + if range.upperBound > string.length { + let newRange = NSRange(location: range.location, length: string.length - range.location) + if let loggingHandler = MVMCoreLoggingHandler.shared(), loggingHandler.responds(to: #selector(MVMCoreLoggingHandler.addError(toLog:))) { + loggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute ending location \(range.upperBound) is out of bounds for '\(string)'. Adjusting to \(newRange.upperBound).", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) + } + return newRange + } + + return range +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift index 9ec6b4f6..2f8235e0 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift @@ -35,13 +35,16 @@ class LabelAttributeImageModel: LabelAttributeModel { case name case URL } + + //-------------------------------------------------- + // MARK: - Validations + //-------------------------------------------------- - public override func validateInRange(of text: String) -> Bool { - guard location > 0 && location <= text.count else { - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute starting location \(location) is out of bounds for '\(text)'. Attribute is discarded.", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) - return false + public override func validateInRange(of text: String) -> MolecularError? { + guard location >= 0 && location <= text.count else { + return MolecularError.validationError("Attribute starting location \(location) is out of bounds for '\(text)'.") } - return true + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift index 37da30a4..590e79b9 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift @@ -44,21 +44,20 @@ case length } - public func validateInRange(of text: String) -> Bool { + //-------------------------------------------------- + // MARK: - Validations + //-------------------------------------------------- + + public func validateInRange(of text: String) -> MolecularError? { // Prevent invalid starting locations. - guard location > 0 && location <= text.count else { - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute starting location \(location) is out of bounds for '\(text)'. Attribute is discarded.", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) - return false + guard location >= 0 && location <= text.count else { + return MolecularError.validationError("Attribute starting location \(location) is out of bounds for '\(text)'.") } - - // Truncate long lengths + // Prevent lengths extending beyond the bounds of the string. guard length + location <= text.count else { - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute length \(length) is out of bounds for '\(text)'. Adjusting to \(text.count - location).", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) - length = text.count - location - return true + return MolecularError.validationError("Attribute length \(length) starting at \(location) is out of bounds for '\(text)'.") } - - return true + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift index d7408be1..f23b3f90 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift @@ -21,17 +21,7 @@ public var fontName: String? public var fontSize: CGFloat? public var textAlignment: NSTextAlignment? - private var _attributes: [LabelAttributeModel]? - public var attributes: [LabelAttributeModel]? { - get { _attributes } - set { - if let attributes = newValue { - _attributes = validate(attributes) - } else { - _attributes = nil - } - } - } + public var attributes: [LabelAttributeModel]? public var html: String? public var hero: Int? public var makeWholeViewClickable: Bool? @@ -75,8 +65,14 @@ //-------------------------------------------------- // MARK: - Validations //-------------------------------------------------- - public func validate(_ attributes: [LabelAttributeModel]) -> [LabelAttributeModel] { - return attributes.filter { $0.validateInRange(of: text) } + + public func validate(_ attributes: [LabelAttributeModel]) -> MolecularError? { + for attribute in attributes { + if let molecularError = attribute.validateInRange(of: text) { + return molecularError + } + } + return nil } //-------------------------------------------------- @@ -99,6 +95,11 @@ makeWholeViewClickable = try typeContainer.decodeIfPresent(Bool.self, forKey: .makeWholeViewClickable) numberOfLines = try typeContainer.decodeIfPresent(Int.self, forKey: .numberOfLines) shouldMaskRecordedView = try typeContainer.decodeIfPresent(Bool.self, forKey: .shouldMaskRecordedView) ?? false + + // Later make protocol based validate outside of decoding? + if let attributes = attributes, let error = validate(attributes) { + throw error + } } open func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift index 1f1fdc1e..6845d44b 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift @@ -1,6 +1,7 @@ public enum MolecularError: Swift.Error { case error(String) + case validationError(String) case countImbalance(String) }