diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index edb8c1a2..4f567600 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -831,11 +831,16 @@ extension Label { guard let string = string, range.location > 0 && range.location < string.length - else { return } + 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 { - range = NSRange(location: range.location, length: string.length - range.location) + 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 } string.addAttributes([NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue], range: range) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift index 7396e749..9ec6b4f6 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift @@ -36,6 +36,14 @@ class LabelAttributeImageModel: LabelAttributeModel { case URL } + 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 + } + return true + } + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift index 7df4097a..37da30a4 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift @@ -44,6 +44,23 @@ case length } + public func validateInRange(of text: String) -> Bool { + // 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 + } + + // Truncate long lengths + 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 true + } + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift index 2eec132f..02c58f8e 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift @@ -21,7 +21,17 @@ public var fontName: String? public var fontSize: CGFloat? public var textAlignment: NSTextAlignment? - public var attributes: [LabelAttributeModel]? + private var _attributes: [LabelAttributeModel]? + public var attributes: [LabelAttributeModel]? { + get { _attributes } + set { + if let attributes = newValue { + _attributes = validate(attributes) + } else { + _attributes = nil + } + } + } public var html: String? public var hero: Int? public var makeWholeViewClickable: Bool? @@ -60,6 +70,13 @@ self.text = text } + //-------------------------------------------------- + // MARK: - Validations + //-------------------------------------------------- + public func validate(_ attributes: [LabelAttributeModel]) -> [LabelAttributeModel] { + return attributes.filter { $0.validateInRange(of: text) } + } + //-------------------------------------------------- // MARK: - Codec //--------------------------------------------------