diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index dc3b4597..8b4ca7fd 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -11,7 +11,7 @@ import MVMCore public typealias ActionBlock = () -> Void -@objc open class Label: UILabel, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol { +@objcMembers open class Label: UILabel, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol { //------------------------------------------------------ // MARK: - Properties //------------------------------------------------------ @@ -23,17 +23,17 @@ public typealias ActionBlock = () -> Void var makeWholeViewClickable = false // Set this property if you want updateView to update the font based on this standard and the size passed in. - @objc public var standardFontSize: CGFloat = 0.0 + public var standardFontSize: CGFloat = 0.0 // Set this to use a custom sizing object during updateView instead of the standard. - @objc public var sizeObject: MFSizeObject? + public var sizeObject: MFSizeObject? - @objc public var scaleSize: NSNumber? + public var scaleSize: NSNumber? // Used for scaling the font in updateView. private var originalAttributedString: NSAttributedString? - @objc public var hasText: Bool { + public var hasText: Bool { guard let text = text, let attributedText = attributedText else { return false } return !text.isEmpty || !attributedText.string.isEmpty } @@ -151,7 +151,7 @@ public typealias ActionBlock = () -> Void guard let label = label else { return } label.text = json?.optionalStringForKey(KeyText) - + setLabel(label, withHTML: json?.optionalStringForKey("html")) if let textColorHex = json?.optionalStringForKey(KeyTextColor), !textColorHex.isEmpty { @@ -176,21 +176,21 @@ public typealias ActionBlock = () -> Void let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: label.font as UIFont, NSAttributedString.Key.foregroundColor: label.textColor as UIColor]) 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) - + switch attributeType { case "underline": attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range) - + case "strikethrough": attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range) - + case "color": if let colorHex = attribute.optionalStringForKey(KeyTextColor), !colorHex.isEmpty { attributedString.removeAttribute(.foregroundColor, range: range) @@ -199,13 +199,13 @@ public typealias ActionBlock = () -> Void case "font": let fontSize = attribute["size"] as? CGFloat var font: UIFont? - + if let fontName = attribute.optionalStringForKey("name") { font = MFFonts.mfFont(withName: fontName, size: fontSize ?? label.font.pointSize) } else if let fontSize = fontSize { font = label.font.withSize(fontSize) } - + if let font = font { attributedString.removeAttribute(.font, range: range) attributedString.addAttribute(.font, value: font, range: range) diff --git a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift index 883819e3..375945a1 100644 --- a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift +++ b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift @@ -9,7 +9,6 @@ import MVMCore -private typealias ActionableStringTuple = (front: String?, action: String?, end: String?) public typealias ActionObjectDelegate = NSObjectProtocol & MVMCoreActionDelegateProtocol public typealias ButtonObjectDelegate = NSObjectProtocol & ButtonDelegateProtocol public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProtocol & MVMCoreLoadDelegateProtocol & MVMCorePresentationDelegateProtocol & NSObjectProtocol @@ -24,7 +23,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt public var attributedText: NSAttributedString? { willSet(newAttributedText) { - if let newAttribText = newAttributedText, !newAttribText.string.isEmpty { + if let newAttribText = newAttributedText { let mutableAttributedText = NSMutableAttributedString(attributedString: newAttribText) let paragraphStyle = NSMutableParagraphStyle() @@ -60,6 +59,12 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt } } + public var actionBlock: ActionBlock? { + willSet(newActionBlock) { + label?.actionBlock = newActionBlock + } + } + private var actionRange: NSRange { return NSRange(location: frontText?.count ?? 0, length: actionText?.count ?? 0) } @@ -81,6 +86,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt label?.frontText = newFrontText } } + public var actionText: String? { willSet(newActionText) { label?.actionText = newActionText @@ -89,15 +95,14 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt public var backText: String? - public var actionBlock: ActionBlock? { - willSet(newActionBlock) { - label?.actionBlock = newActionBlock - } - } + private var internalText: String = "" - private var text: String? { - willSet(newText) { - attributedText = NSAttributedString(string: newText ?? "") + public var text: String? { + get { return internalText } + set { + guard let text = newValue else { return } + internalText = text + attributedText = NSAttributedString(string: text) setAlternateNormalTextAttributes([NSAttributedString.Key.font: normalTextFont as Any]) setAlternateActionTextAttributes([NSAttributedString.Key.font: actionTextFont as Any]) setAlternateNormalTextAttributes([NSAttributedString.Key.foregroundColor: normalTextColor as Any]) @@ -111,29 +116,33 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt public init() { super.init(frame: .zero) - setup() + createLabel() + setLabelAttributes() } required public init?(coder: NSCoder) { super.init(coder: coder) - setup() + createLabel() + setLabelAttributes() } override public init(frame: CGRect) { super.init(frame: frame) - setup() + createLabel() + setLabelAttributes() } - // MARK: - legacy + // legacy public init(frontText: String?, actionText: String?, backText: String?, actionBlock block: ActionBlock?) { super.init(frame: .zero) + createLabel() self.frontText = frontText self.actionText = actionText self.backText = backText actionBlock = block text = getTextFromStringComponents() - setup() + setLabelAttributes() } public convenience init(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { @@ -145,32 +154,22 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt } public convenience init(frontText: String?, backText: String?, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - self.init(frontText: frontText, - actionText: actionMap?.optionalStringForKey(KeyTitle), - backText: backText, - actionMap: actionMap, - additionalData: additionalData, - delegateObject: delegateObject) + self.init(frontText: frontText, actionText: actionMap?.optionalStringForKey(KeyTitle), backText: backText, actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) } public init(frontText: String?, actionText: String?, backText: String?, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - super.init(frame: CGRect.zero) + super.init(frame: .zero) setFrontText(frontText, actionText: actionText, actionMap: actionMap, backText: backText, additionalData: additionalData, delegateObject: delegateObject) } // Convenience Initializer which assumes that the clickable text will be embedded in curly braces {}. public convenience init(clickableTextEmbeddedInCurlyBraces fullText: String?, actionMapForClickableText actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - self.init(text: fullText, - startTag: "{", - endTag: "}", - actionMap: actionMap, - additionalData: additionalData, - delegateObject: delegateObject) + self.init(text: fullText, startTag: "{", endTag: "}", actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) } public init(text fullText: String?, startTag: String?, endTag: String?, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - super.init(frame: CGRect.zero) + super.init(frame: .zero) setText(fullText, startTag: startTag, endTag: endTag) @@ -183,7 +182,8 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt // MARK: - Configuration //------------------------------------------------------ - private func setup() { + /// Creates the Label that will be interacted with. + private func createLabel() { if self.label == nil { let label = Label(frame: .zero) @@ -191,12 +191,15 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt label.isUserInteractionEnabled = true label.setContentCompressionResistancePriority(.required, for: .vertical) addSubview(label) - NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[label]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["label": label])) - NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[label]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["label": label])) + NSLayoutConstraint.constraintPinSubview(label, pinTop: true, pinBottom: true, pinLeft: true, pinRight: true) self.label = label label.sizeToFit() } + } + + /// Sets up label. Best to call sometime after setting the text. + private func setLabelAttributes() { // Adding the underline setAlternateActionTextAttributes([NSAttributedString.Key.underlineStyle: NSNumber(value: NSUnderlineStyle.single.rawValue)]) @@ -204,16 +207,15 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt self.label?.attributedText = attributedText self.label?.accessibilityTraits = .button } - + @objc public func setActionMap(_ actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - weak var weakSelf: LabelWithInternalButton? = self - weak var weakButtonDelegate: ButtonDelegateProtocol? = (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate - - actionBlock = { + actionBlock = { [weak self, weak delegateObject] in var performAction = true - if let wSelf = weakSelf, let wButtonDelegate = weakButtonDelegate, wButtonDelegate.responds(to: #selector(ButtonObjectDelegate.button(_:shouldPerformActionWithMap:additionalData:))) { + if let wSelf = self, + let wButtonDelegate = (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate, + wButtonDelegate.responds(to: #selector(ButtonObjectDelegate.button(_:shouldPerformActionWithMap:additionalData:))) { performAction = wButtonDelegate.button?(wSelf, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? false } @@ -229,12 +231,13 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt @objc public func setFrontText(_ frontText: String?, actionText: String?, actionMap: [AnyHashable: Any]?, backText: String?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + createLabel() self.frontText = frontText setActionMap(actionMap, additionalData: additionalData, delegateObject: delegateObject) self.actionText = actionText self.backText = backText text = getTextFromStringComponents() - setup() + setLabelAttributes() } @objc public func setFrontText(_ frontText: String?, actionMap: [AnyHashable: Any]?, backText: String?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { @@ -283,14 +286,6 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt label?.attributedText = attributedText } - //------------------------------------------------------ - // MARK: - UIControl - //------------------------------------------------------ - -// override open func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { -// <#code#> -// } - //------------------------------------------------------ // MARK: - Helper //------------------------------------------------------ @@ -315,45 +310,25 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt private func setText(_ text: String?, startTag: String?, endTag: String?) { - let actionableTuple: ActionableStringTuple = rangeOfText(text, startTag: startTag, endTag: endTag) + createLabel() + frontText = text - if let text = text, - let leftTag = startTag, text.contains(leftTag), - let rightTag = endTag, text.contains(rightTag), - let front = actionableTuple.front, - let middle = actionableTuple.action, - let end = actionableTuple.end { - - frontText = front.trimmingCharacters(in: .whitespaces) - actionText = middle.trimmingCharacters(in: .whitespaces) - backText = end.trimmingCharacters(in: .whitespaces) - } else { - frontText = text - } - - self.text = getTextFromStringComponents() - setup() - } - - private func rangeOfText(_ text: String?, startTag: String?, endTag: String?) -> ActionableStringTuple { - - var actionableTuple: ActionableStringTuple = (front: nil, action: nil, end: nil) - - guard let text = text else { return actionableTuple } - - if let leftTag = startTag, text.contains(leftTag) { - - let firstHalf = text.components(separatedBy: leftTag) - actionableTuple.front = firstHalf.first - - if let rightTag = endTag, text.contains(rightTag) { - let secondHalf = firstHalf[1].components(separatedBy: rightTag) - actionableTuple.action = secondHalf[0] - actionableTuple.end = secondHalf[1] + if let text = text { + var initialSegments = [String]() + if let leftTag = startTag, text.contains(leftTag) { + initialSegments = text.components(separatedBy: leftTag) + frontText = initialSegments[0].trimmingCharacters(in: .whitespaces) + + if let rightTag = endTag, text.contains(rightTag) { + let secondPart = initialSegments[1].components(separatedBy: rightTag) + actionText = secondPart[0].trimmingCharacters(in: .whitespaces) + backText = secondPart[1].trimmingCharacters(in: .whitespaces) + } } } - return actionableTuple + self.text = getTextFromStringComponents() + setLabelAttributes() } // Reset the text and action map @@ -418,14 +393,16 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt @objc public func setActionTextString(_ actionText: String?) { + createLabel() self.actionText = actionText text = getTextFromStringComponents() - setup() + setLabelAttributes() } /// Used to just reset the texts and actions if already initialized @objc public func reset(withActionMap actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + createLabel() frontText = actionMap?.optionalStringForKey(KeyTitlePrefix) actionText = actionMap?.optionalStringForKey(KeyTitle) backText = actionMap?.optionalStringForKey(KeyTitlePostfix) @@ -435,7 +412,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt } text = getTextFromStringComponents() - setup() + setLabelAttributes() } //------------------------------------------------------ @@ -473,7 +450,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt @available(*, deprecated) public init(frontText: String?, actionText: String?, backText: String?, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, actionDelegate delegate: ActionObjectDelegate?, buttonDelegate: ButtonObjectDelegate?) { - super.init(frame: CGRect.zero) + super.init(frame: .zero) setFrontText(frontText, actionText: actionText, actionMap: actionMap, backText: backText, additionalData: additionalData, delegate: delegate, buttonDelegate: buttonDelegate) } @@ -517,29 +494,23 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt setText(fullText, startTag: startTag, endTag: endTag) - weak var weakDelegate: ActionObjectDelegate? = delegate - actionBlock = { - MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegate: weakDelegate as? CoreObjectActionLoadPresentDelegate) + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegate: delegate as? CoreObjectActionLoadPresentDelegate) } } @available(*, deprecated) private func setActionMap(_ actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, actionDelegate delegate: ActionObjectDelegate?, buttonDelegate: ButtonObjectDelegate?) { - weak var weakSelf: LabelWithInternalButton? = self - weak var weakDelegate: ActionObjectDelegate? = delegate - weak var weakButtonDelegate: ButtonObjectDelegate? = buttonDelegate - - actionBlock = { + actionBlock = { [weak self] in var performAction = true - if let wSelf = weakSelf, let wButtonDelegate = weakButtonDelegate, wButtonDelegate.responds(to: #selector(ButtonObjectDelegate.button(_:shouldPerformActionWithMap:additionalData:))) { + if let wSelf = self, let wButtonDelegate = buttonDelegate, wButtonDelegate.responds(to: #selector(ButtonObjectDelegate.button(_:shouldPerformActionWithMap:additionalData:))) { performAction = wButtonDelegate.button?(wSelf, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? false } - if let wDelegate = weakDelegate as? CoreObjectActionLoadPresentDelegate, performAction { - MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegate: wDelegate) + if performAction { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegate: delegate as? CoreObjectActionLoadPresentDelegate) } } } @@ -552,12 +523,13 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt @available(*, deprecated) @objc public func setFrontText(_ frontText: String?, actionText: String?, actionMap: [AnyHashable: Any]?, backText: String?, additionalData: [AnyHashable: Any]?, delegate: ActionObjectDelegate?, buttonDelegate: ButtonObjectDelegate?) { + createLabel() self.frontText = frontText setActionMap(actionMap, additionalData: additionalData, actionDelegate: delegate, buttonDelegate: buttonDelegate) self.actionText = actionText self.backText = backText text = getTextFromStringComponents() - setup() + setLabelAttributes() } @available(*, deprecated) @@ -578,10 +550,8 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt } if let b2Font = MFStyler.fontB2(), - let actions = actionMap, - actions.keys.count > 0, - let actionString = actions.optionalStringForKey(KeyTitle), - !actionString.isEmpty { + let actions = actionMap, actions.keys.count > 0, + let actionString = actions.optionalStringForKey(KeyTitle), !actionString.isEmpty { let actionStringOnLine = actionString + (addNewLine ? "\n" : " ") actionText = actionStringOnLine @@ -646,18 +616,18 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt @available(*, deprecated) @objc public func reset(withActionMap actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegate: ActionObjectDelegate?) { + createLabel() + frontText = actionMap?.optionalStringForKey(KeyTitlePrefix) actionText = actionMap?.optionalStringForKey(KeyTitle) backText = actionMap?.optionalStringForKey(KeyTitlePostfix) - weak var weakDelegate: ActionObjectDelegate? = delegate - actionBlock = { - MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegate: weakDelegate as? CoreObjectActionLoadPresentDelegate) + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegate: delegate as? CoreObjectActionLoadPresentDelegate) } text = getTextFromStringComponents() - setup() + setLabelAttributes() } }