Merge branch 'feature/label_hero_character' into 'develop'
Label Hero See merge request BPHV_MIPS/mvm_core_ui!136
This commit is contained in:
commit
9ef670cfc4
@ -26,6 +26,9 @@ public typealias ActionBlock = () -> ()
|
|||||||
public var sizeObject: MFSizeObject?
|
public var sizeObject: MFSizeObject?
|
||||||
public var scaleSize: NSNumber?
|
public var scaleSize: NSNumber?
|
||||||
|
|
||||||
|
/// A specific text index to use as a unique marker.
|
||||||
|
public var hero: Int?
|
||||||
|
|
||||||
// Used for scaling the font in updateView.
|
// Used for scaling the font in updateView.
|
||||||
private var originalAttributedString: NSAttributedString?
|
private var originalAttributedString: NSAttributedString?
|
||||||
|
|
||||||
@ -502,6 +505,50 @@ public typealias ActionBlock = () -> ()
|
|||||||
let accessibleAction = customAccessibilityAction(range: range)
|
let accessibleAction = customAccessibilityAction(range: range)
|
||||||
clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1))
|
clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Provides a text container and layout manager of how the text would appear on screen.
|
||||||
|
They are used in tandem to derive low-level TextKit results of the label.
|
||||||
|
*/
|
||||||
|
public func abstractTextContainer() -> (NSTextContainer, NSLayoutManager, NSTextStorage)? {
|
||||||
|
|
||||||
|
// Must configure the attributed string to translate what would appear on screen to accurately analyze.
|
||||||
|
guard let attributedText = attributedText else { return nil }
|
||||||
|
|
||||||
|
let paragraph = NSMutableParagraphStyle()
|
||||||
|
paragraph.alignment = textAlignment
|
||||||
|
|
||||||
|
let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText)
|
||||||
|
stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count))
|
||||||
|
|
||||||
|
let textStorage = NSTextStorage(attributedString: stagedAttributedString)
|
||||||
|
let layoutManager = NSLayoutManager()
|
||||||
|
let textContainer = NSTextContainer(size: .zero)
|
||||||
|
|
||||||
|
layoutManager.addTextContainer(textContainer)
|
||||||
|
textStorage.addLayoutManager(layoutManager)
|
||||||
|
|
||||||
|
textContainer.lineFragmentPadding = 0.0
|
||||||
|
textContainer.lineBreakMode = lineBreakMode
|
||||||
|
textContainer.maximumNumberOfLines = numberOfLines
|
||||||
|
textContainer.size = bounds.size
|
||||||
|
|
||||||
|
return (textContainer, layoutManager, textStorage)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect {
|
||||||
|
|
||||||
|
guard let abstractContainer = label.abstractTextContainer() else { return CGRect() }
|
||||||
|
let textContainer = abstractContainer.0
|
||||||
|
let layoutManager = abstractContainer.1
|
||||||
|
|
||||||
|
var glyphRange = NSRange()
|
||||||
|
|
||||||
|
// Convert the range for glyphs.
|
||||||
|
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)
|
||||||
|
|
||||||
|
return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Atomization
|
// MARK: - Atomization
|
||||||
@ -521,6 +568,8 @@ extension Label {
|
|||||||
clauses = []
|
clauses = []
|
||||||
Label.setUILabel(self, withJSON: json, delegate: delegateObject, additionalData: additionalData)
|
Label.setUILabel(self, withJSON: json, delegate: delegateObject, additionalData: additionalData)
|
||||||
originalAttributedString = attributedText
|
originalAttributedString = attributedText
|
||||||
|
|
||||||
|
hero = json?["hero"] as? Int
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setAsMolecule() {
|
public func setAsMolecule() {
|
||||||
@ -634,26 +683,9 @@ extension UITapGestureRecognizer {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must configure the attributed string to translate what would appear on screen to accurately analyze.
|
guard let abstractContainer = label.abstractTextContainer() else { return false }
|
||||||
guard let attributedText = label.attributedText else { return false }
|
let textContainer = abstractContainer.0
|
||||||
|
let layoutManager = abstractContainer.1
|
||||||
let paragraph = NSMutableParagraphStyle()
|
|
||||||
paragraph.alignment = label.textAlignment
|
|
||||||
|
|
||||||
let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText)
|
|
||||||
stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count))
|
|
||||||
|
|
||||||
let textStorage = NSTextStorage(attributedString: stagedAttributedString)
|
|
||||||
let layoutManager = NSLayoutManager()
|
|
||||||
let textContainer = NSTextContainer(size: .zero)
|
|
||||||
|
|
||||||
layoutManager.addTextContainer(textContainer)
|
|
||||||
textStorage.addLayoutManager(layoutManager)
|
|
||||||
|
|
||||||
textContainer.lineFragmentPadding = 0.0
|
|
||||||
textContainer.lineBreakMode = label.lineBreakMode
|
|
||||||
textContainer.maximumNumberOfLines = label.numberOfLines
|
|
||||||
textContainer.size = label.bounds.size
|
|
||||||
|
|
||||||
let indexOfGlyph = layoutManager.glyphIndex(for: location(in: label), in: textContainer)
|
let indexOfGlyph = layoutManager.glyphIndex(for: location(in: label), in: textContainer)
|
||||||
|
|
||||||
|
|||||||
@ -53,6 +53,17 @@ import UIKit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
// Ensures accessory view aligns to the center y derived from the
|
||||||
|
if let center = heroAccessoryCenter {
|
||||||
|
accessoryView?.center.y = center.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var heroAccessoryCenter: CGPoint?
|
||||||
|
|
||||||
func styleStandard() {
|
func styleStandard() {
|
||||||
topMarginPadding = 24
|
topMarginPadding = 24
|
||||||
bottomMarginPadding = 24
|
bottomMarginPadding = 24
|
||||||
@ -73,6 +84,11 @@ import UIKit
|
|||||||
bottomSeparatorView?.hide()
|
bottomSeparatorView?.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func willDisplay() {
|
||||||
|
|
||||||
|
alignAccessoryToHero()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Inits
|
// MARK: - Inits
|
||||||
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
@ -113,15 +129,47 @@ import UIKit
|
|||||||
contentView.preservesSuperviewLayoutMargins = false
|
contentView.preservesSuperviewLayoutMargins = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// NOTE: Should only be called when displayed or about to be displayed.
|
||||||
|
public func alignAccessoryToHero() {
|
||||||
|
|
||||||
|
// Layout call required to force draw in memory to get dimensions of subviews.
|
||||||
|
layoutIfNeeded()
|
||||||
|
guard let heroLabel = findHeroLabel(views: contentView.subviews), let hero = heroLabel.hero else { return }
|
||||||
|
let rect = Label.boundingRect(forCharacterRange: NSRange(location: hero, length: 1), in: heroLabel)
|
||||||
|
accessoryView?.center.y = contentView.convert(UIView(frame: rect).center, from: heroLabel).y
|
||||||
|
heroAccessoryCenter = accessoryView?.center
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverses the view hierarchy for a 🦸♂️heroic Label.
|
||||||
|
private func findHeroLabel(views: [UIView]) -> Label? {
|
||||||
|
|
||||||
|
if views.isEmpty {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var queue = [UIView]()
|
||||||
|
|
||||||
|
for view in views {
|
||||||
|
// Only one Label will have a hero in a table cell.
|
||||||
|
if let label = view as? Label, label.hero != nil {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
queue.append(contentsOf: view.subviews)
|
||||||
|
}
|
||||||
|
|
||||||
|
return findHeroLabel(views: queue)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||||
public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||||
self.json = json;
|
self.json = json
|
||||||
|
|
||||||
style(with: json?.optionalStringForKey("style"))
|
style(with: json?.optionalStringForKey("style"))
|
||||||
|
|
||||||
if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") {
|
if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") {
|
||||||
updateViewHorizontalDefaults = useHorizontalMargins
|
updateViewHorizontalDefaults = useHorizontalMargins
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json?.optionalBoolForKey("useVerticalMargins") ?? true) == false {
|
if (json?.optionalBoolForKey("useVerticalMargins") ?? true) == false {
|
||||||
topMarginPadding = 0
|
topMarginPadding = 0
|
||||||
bottomMarginPadding = 0
|
bottomMarginPadding = 0
|
||||||
@ -146,9 +194,8 @@ import UIKit
|
|||||||
bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData)
|
bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else {
|
guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
if molecule == nil {
|
if molecule == nil {
|
||||||
if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) {
|
if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) {
|
||||||
contentView.addSubview(moleculeView)
|
contentView.addSubview(moleculeView)
|
||||||
@ -199,10 +246,8 @@ import UIKit
|
|||||||
|
|
||||||
// MARK: - Arrow
|
// MARK: - Arrow
|
||||||
/// Adds the standard mvm style caret to the accessory view
|
/// Adds the standard mvm style caret to the accessory view
|
||||||
public func addCaretViewAccessory() {
|
@objc public func addCaretViewAccessory() {
|
||||||
guard accessoryView == nil else {
|
guard accessoryView == nil else { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
let width: CGFloat = 6
|
let width: CGFloat = 6
|
||||||
let height: CGFloat = 10
|
let height: CGFloat = 10
|
||||||
caretView = CaretView(lineThickness: CaretView.thin)
|
caretView = CaretView(lineThickness: CaretView.thin)
|
||||||
|
|||||||
@ -24,7 +24,6 @@
|
|||||||
/// Resets to default state before set with json is called again.
|
/// Resets to default state before set with json is called again.
|
||||||
- (void)reset;
|
- (void)reset;
|
||||||
|
|
||||||
|
|
||||||
/// For the molecule list to load more efficiently.
|
/// For the molecule list to load more efficiently.
|
||||||
+ (CGFloat)estimatedHeightForRow:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject;
|
+ (CGFloat)estimatedHeightForRow:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject;
|
||||||
|
|
||||||
|
|||||||
@ -17,4 +17,6 @@
|
|||||||
/// Handle action
|
/// Handle action
|
||||||
- (void)didSelectCellAtIndex:(nonnull NSIndexPath *)indexPath delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject additionalData:(nullable NSDictionary *)additionalData;
|
- (void)didSelectCellAtIndex:(nonnull NSIndexPath *)indexPath delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject additionalData:(nullable NSDictionary *)additionalData;
|
||||||
|
|
||||||
|
- (void)willDisplay;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -51,9 +51,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController {
|
|||||||
// MARK: - table
|
// MARK: - table
|
||||||
open override func registerWithTable() {
|
open override func registerWithTable() {
|
||||||
super.registerWithTable()
|
super.registerWithTable()
|
||||||
guard let moleculesInfo = moleculesInfo else {
|
guard let moleculesInfo = moleculesInfo else { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
for moleculeInfo in moleculesInfo {
|
for moleculeInfo in moleculesInfo {
|
||||||
tableView?.register(moleculeInfo.class, forCellReuseIdentifier: moleculeInfo.identifier)
|
tableView?.register(moleculeInfo.class, forCellReuseIdentifier: moleculeInfo.identifier)
|
||||||
}
|
}
|
||||||
@ -88,6 +87,13 @@ open class MoleculeListTemplate: ThreeLayerTableViewController {
|
|||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||||
|
|
||||||
|
if let protocolCell = cell as? MoleculeListCellProtocol {
|
||||||
|
protocolCell.willDisplay?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
if let cell = tableView.cellForRow(at: indexPath) as? MoleculeListCellProtocol {
|
if let cell = tableView.cellForRow(at: indexPath) as? MoleculeListCellProtocol {
|
||||||
cell.didSelectCell?(atIndex: indexPath, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, additionalData: nil)
|
cell.didSelectCell?(atIndex: indexPath, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, additionalData: nil)
|
||||||
@ -120,9 +126,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController {
|
|||||||
open override func addMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) {
|
open override func addMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) {
|
||||||
// This dispatch is needed to fix a race condition that can occur if this function is called during the table setup.
|
// This dispatch is needed to fix a race condition that can occur if this function is called during the table setup.
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
guard let cell = sender as? MoleculeTableViewCell, let indexPath = self.tableView?.indexPath(for: cell) else {
|
guard let cell = sender as? MoleculeTableViewCell, let indexPath = self.tableView?.indexPath(for: cell) else { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
var indexPaths: [IndexPath] = []
|
var indexPaths: [IndexPath] = []
|
||||||
for molecule in molecules {
|
for molecule in molecules {
|
||||||
if let info = self.getMoleculeInfo(with: molecule) {
|
if let info = self.getMoleculeInfo(with: molecule) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user