Moving interaction functionality to label.
This commit is contained in:
parent
07f8921a4f
commit
8da88c4c87
@ -9,12 +9,19 @@
|
|||||||
|
|
||||||
import MVMCore
|
import MVMCore
|
||||||
|
|
||||||
|
public typealias ActionBlock = () -> Void
|
||||||
|
|
||||||
@objc open class Label: UILabel, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol {
|
@objc open class Label: UILabel, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol {
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
|
|
||||||
|
var actionBlock: ActionBlock?
|
||||||
|
var actionText: String?
|
||||||
|
var frontText: String?
|
||||||
|
|
||||||
|
var makeWholeViewClickable = false
|
||||||
|
|
||||||
// Set this property if you want updateView to update the font based on this standard and the size passed in.
|
// 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
|
@objc public var standardFontSize: CGFloat = 0.0
|
||||||
|
|
||||||
@ -303,6 +310,71 @@ import MVMCore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - UIControl Override
|
||||||
extension Label {
|
extension Label {
|
||||||
|
|
||||||
|
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
|
||||||
|
if areTouches(inActionString: touches) {
|
||||||
|
if let action = actionBlock {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func areTouches(inActionString touches: Set<UITouch>?) -> Bool {
|
||||||
|
|
||||||
|
if UIAccessibility.isVoiceOverRunning || makeWholeViewClickable {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let location: CGPoint? = touches?.first?.location(in: self)
|
||||||
|
let index: Int = NSRange(location: frontText?.count ?? 0, length: actionText?.count ?? 0).location
|
||||||
|
let rangeArray = getRangeArrayOfWords(in: actionText, withInitalIndex: index)
|
||||||
|
let rectArray = getRectArray(from: rangeArray) as? [NSValue] ?? []
|
||||||
|
|
||||||
|
for rect in rectArray {
|
||||||
|
let wordRect: CGRect = rect.cgRectValue
|
||||||
|
|
||||||
|
if let position = location, wordRect.contains(position) {
|
||||||
|
return true
|
||||||
|
|
||||||
|
} else if wordRect.origin == .zero && wordRect.size.height == 0 && wordRect.size.width == 0 {
|
||||||
|
// Incase word rect is not found for any reason, make the whole label to be clicable to avoid non functioning link in production.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getRangeArrayOfWords(in string: String?, withInitalIndex index: Int) -> [Any]? {
|
||||||
|
|
||||||
|
var index = index
|
||||||
|
let words = string?.components(separatedBy: " ") ?? []
|
||||||
|
var rangeArray = [AnyHashable]()
|
||||||
|
|
||||||
|
for word in words {
|
||||||
|
let finalWord = word + " "
|
||||||
|
let length: Int = finalWord.count
|
||||||
|
let wordRange = NSRange(location: index, length: length)
|
||||||
|
let rangeValue = NSValue(range: wordRange)
|
||||||
|
rangeArray.append(rangeValue)
|
||||||
|
index += length
|
||||||
|
}
|
||||||
|
|
||||||
|
return rangeArray
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getRectArray(from rangeArray: [Any]?) -> [Any]? {
|
||||||
|
|
||||||
|
var rectArray = [AnyHashable]()
|
||||||
|
|
||||||
|
for range in rangeArray as? [NSValue] ?? [] {
|
||||||
|
let rectValue = NSValue(cgRect: boundingRect(forCharacterRange: range.rangeValue))
|
||||||
|
rectArray.append(rectValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rectArray
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
import MVMCore
|
import MVMCore
|
||||||
|
|
||||||
public typealias ActionBlock = () -> Void
|
|
||||||
private typealias ActionableStringTuple = (front: String?, action: String?, end: String?)
|
private typealias ActionableStringTuple = (front: String?, action: String?, end: String?)
|
||||||
public typealias ActionObjectDelegate = NSObjectProtocol & MVMCoreActionDelegateProtocol
|
public typealias ActionObjectDelegate = NSObjectProtocol & MVMCoreActionDelegateProtocol
|
||||||
public typealias ButtonObjectDelegate = NSObjectProtocol & ButtonDelegateProtocol
|
public typealias ButtonObjectDelegate = NSObjectProtocol & ButtonDelegateProtocol
|
||||||
@ -21,7 +20,6 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
|
|||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
|
|
||||||
public var actionBlock: ActionBlock?
|
|
||||||
public weak var label: Label?
|
public weak var label: Label?
|
||||||
|
|
||||||
public var attributedText: NSAttributedString? {
|
public var attributedText: NSAttributedString? {
|
||||||
@ -66,7 +64,11 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
|
|||||||
return NSRange(location: frontText?.count ?? 0, length: actionText?.count ?? 0)
|
return NSRange(location: frontText?.count ?? 0, length: actionText?.count ?? 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var makeWholeViewClickable = false
|
public var makeWholeViewClickable = false {
|
||||||
|
willSet(newBool) {
|
||||||
|
label?.makeWholeViewClickable = newBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override open var isEnabled: Bool {
|
override open var isEnabled: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
@ -74,10 +76,25 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var frontText: String?
|
public var frontText: String? {
|
||||||
public var actionText: String?
|
willSet(newFrontText) {
|
||||||
|
label?.frontText = newFrontText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public var actionText: String? {
|
||||||
|
willSet(newActionText) {
|
||||||
|
label?.actionText = newActionText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var backText: String?
|
public var backText: String?
|
||||||
|
|
||||||
|
public var actionBlock: ActionBlock? {
|
||||||
|
willSet(newActionBlock) {
|
||||||
|
label?.actionBlock = newActionBlock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var text: String? {
|
private var text: String? {
|
||||||
willSet(newText) {
|
willSet(newText) {
|
||||||
attributedText = NSAttributedString(string: newText ?? "")
|
attributedText = NSAttributedString(string: newText ?? "")
|
||||||
@ -169,10 +186,9 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
|
|||||||
private func setup() {
|
private func setup() {
|
||||||
|
|
||||||
if self.label == nil {
|
if self.label == nil {
|
||||||
let label = Label(frame: CGRect.zero)
|
let label = Label(frame: .zero)
|
||||||
|
|
||||||
backgroundColor = .clear
|
label.isUserInteractionEnabled = true
|
||||||
label.isUserInteractionEnabled = false
|
|
||||||
label.setContentCompressionResistancePriority(.required, for: .vertical)
|
label.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||||
addSubview(label)
|
addSubview(label)
|
||||||
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[label]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["label": label]))
|
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[label]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["label": label]))
|
||||||
@ -237,10 +253,8 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let b2Font = MFStyler.fontB2(),
|
if let b2Font = MFStyler.fontB2(),
|
||||||
let actions = actionMap,
|
let actions = actionMap, actions.keys.count > 0,
|
||||||
actions.keys.count > 0,
|
let actionString = actions.optionalStringForKey(KeyTitle), !actionString.isEmpty {
|
||||||
let actionString = actions.optionalStringForKey(KeyTitle),
|
|
||||||
!actionString.isEmpty {
|
|
||||||
|
|
||||||
let actionStringOnLine = actionString + (addNewLine ? "\n" : " ")
|
let actionStringOnLine = actionString + (addNewLine ? "\n" : " ")
|
||||||
actionText = actionStringOnLine
|
actionText = actionStringOnLine
|
||||||
@ -270,50 +284,13 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
|
|||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
// MARK: - UIControl Override
|
// MARK: - UIControl
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
|
|
||||||
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
// override open func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
|
||||||
|
// <#code#>
|
||||||
if areTouches(inActionString: touches) {
|
// }
|
||||||
sendActions(for: .touchUpInside)
|
|
||||||
if let action = actionBlock {
|
|
||||||
action()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendActions(for: .touchUpOutside)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func areTouches(inActionString touches: Set<UITouch>?) -> Bool {
|
|
||||||
|
|
||||||
if UIAccessibility.isVoiceOverRunning || makeWholeViewClickable {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
let location: CGPoint? = touches?.first?.location(in: label)
|
|
||||||
let actionString = actionText
|
|
||||||
let index: Int = actionRange.location
|
|
||||||
let rangeArray = getRangeArrayOfWords(in: actionString, withInitalIndex: index)
|
|
||||||
let rectArray = getRectArray(fromRangeArray: rangeArray)
|
|
||||||
var result = false
|
|
||||||
|
|
||||||
for aValueOfRect in rectArray as? [NSValue] ?? [] {
|
|
||||||
let wordRect: CGRect = aValueOfRect.cgRectValue
|
|
||||||
|
|
||||||
if let position = location, wordRect.contains(position) {
|
|
||||||
result = true
|
|
||||||
break
|
|
||||||
} else if wordRect.origin.x == 0 && wordRect.origin.y == 0 && wordRect.size.height == 0 && wordRect.size.width == 0 {
|
|
||||||
// Incase word rect is not found for any reason, make the whole label to be clicable to avoid non functioning link in production.
|
|
||||||
result = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
// MARK: - Helper
|
// MARK: - Helper
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
@ -331,40 +308,6 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
|
|||||||
return "\(frontText ?? "")\(actionText ?? "")\(backText ?? "")"
|
return "\(frontText ?? "")\(actionText ?? "")\(backText ?? "")"
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getRangeArrayOfWords(in string: String?, withInitalIndex index: Int) -> [Any]? {
|
|
||||||
|
|
||||||
var index = index
|
|
||||||
let words = string?.components(separatedBy: " ")
|
|
||||||
var rangeArray = [AnyHashable]()
|
|
||||||
|
|
||||||
for subString in words ?? [] {
|
|
||||||
let finalSubString = subString + " "
|
|
||||||
let wordIndex: Int = index
|
|
||||||
let length: Int = finalSubString.count
|
|
||||||
let subStringRange = NSRange(location: wordIndex, length: length)
|
|
||||||
let rangeValue = NSValue(range: subStringRange)
|
|
||||||
rangeArray.append(rangeValue)
|
|
||||||
index += length
|
|
||||||
}
|
|
||||||
|
|
||||||
return rangeArray
|
|
||||||
}
|
|
||||||
|
|
||||||
private func getRectArray(fromRangeArray rangeArray: [Any]?) -> [Any]? {
|
|
||||||
|
|
||||||
var rectArray = [AnyHashable]()
|
|
||||||
|
|
||||||
for aValueOfRange in rangeArray as? [NSValue] ?? [] {
|
|
||||||
let wordRange: NSRange = aValueOfRange.rangeValue
|
|
||||||
if let rect: CGRect = label?.boundingRect(forCharacterRange: wordRange) {
|
|
||||||
let rectValue = NSValue(cgRect: rect)
|
|
||||||
rectArray.append(rectValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rectArray
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc public func setCurlyBracedText(_ text: String) {
|
@objc public func setCurlyBracedText(_ text: String) {
|
||||||
|
|
||||||
setText(text, startTag: "{", endTag: "}")
|
setText(text, startTag: "{", endTag: "}")
|
||||||
@ -375,10 +318,8 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
|
|||||||
let actionableTuple: ActionableStringTuple = rangeOfText(text, startTag: startTag, endTag: endTag)
|
let actionableTuple: ActionableStringTuple = rangeOfText(text, startTag: startTag, endTag: endTag)
|
||||||
|
|
||||||
if let text = text,
|
if let text = text,
|
||||||
let leftTag = startTag,
|
let leftTag = startTag, text.contains(leftTag),
|
||||||
text.contains(leftTag),
|
let rightTag = endTag, text.contains(rightTag),
|
||||||
let rightTag = endTag,
|
|
||||||
text.contains(rightTag),
|
|
||||||
let front = actionableTuple.front,
|
let front = actionableTuple.front,
|
||||||
let middle = actionableTuple.action,
|
let middle = actionableTuple.action,
|
||||||
let end = actionableTuple.end {
|
let end = actionableTuple.end {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user