From 6f4db10d33b371906dd31d3360baf2d4721fbf27 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 16 Sep 2019 10:24:18 -0400 Subject: [PATCH 01/11] beginning work on hero. --- MVMCoreUI/Atoms/Views/Label.swift | 41 +++++++++++++++++++ .../Molecules/ActionDetailWithImage.swift | 5 +++ 2 files changed, 46 insertions(+) diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index ed99fcac..99576509 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -502,6 +502,47 @@ public typealias ActionBlock = () -> () let accessibleAction = customAccessibilityAction(range: range) clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1)) } + +// func rectOfCharacter() { +// +//// label.intrinsicContentSize.width +// if let range = mylabel.text?.range(of: String(describing: mylabel.text?.characters.last!)) { +// let prefix = mylabel.text?.substring(to: range.lowerBound) +// let size: CGSize = prefix!.size(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 35.0)]) +// let position = CGPoint(x:size.width,y: 0) +// myScrollView.setContentOffset(position, animated: true) +// } +// } + + static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { + + guard let attributedText = label.attributedText else { return CGRect() } + + 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 + textContainer.lineBreakMode = label.lineBreakMode + textContainer.maximumNumberOfLines = label.numberOfLines + textContainer.size = label.bounds.size + + var glyphRange = NSRange() + + // Convert the range for glyphs. + layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange) + + return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) + } } // MARK: - Atomization diff --git a/MVMCoreUI/Molecules/ActionDetailWithImage.swift b/MVMCoreUI/Molecules/ActionDetailWithImage.swift index ec91c479..8cdddb5f 100644 --- a/MVMCoreUI/Molecules/ActionDetailWithImage.swift +++ b/MVMCoreUI/Molecules/ActionDetailWithImage.swift @@ -166,5 +166,10 @@ import UIKit } else { button.isHidden = true } + + if let text = header.messageLabel.text, text.contains("frog") { + let rect = Label.boundingRect(forCharacterRange: NSRange(location: 65, length: 4), in: header.messageLabel) + print(rect) + } } } From b9e80398c16c150d68cfbdb17e16a7b151823f51 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 17 Sep 2019 09:41:14 -0400 Subject: [PATCH 02/11] Decent palce. --- MVMCoreUI/Atoms/Views/Label.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index 99576509..862fd829 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -514,7 +514,7 @@ public typealias ActionBlock = () -> () // } // } - static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { + public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { guard let attributedText = label.attributedText else { return CGRect() } @@ -531,7 +531,7 @@ public typealias ActionBlock = () -> () layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) - textContainer.lineFragmentPadding = 0 + textContainer.lineFragmentPadding = 0.0 textContainer.lineBreakMode = label.lineBreakMode textContainer.maximumNumberOfLines = label.numberOfLines textContainer.size = label.bounds.size From fc4fcc25f6b1ebcbb578d8d3c16fa39ed5e1a157 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 17 Sep 2019 16:37:27 -0400 Subject: [PATCH 03/11] Current take on hero. Needs to be refactored and placed into a protocol. --- MVMCoreUI/Atoms/Views/Label.swift | 12 +++++ .../Templates/MoleculeListTemplate.swift | 45 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index 862fd829..81159965 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -25,6 +25,7 @@ public typealias ActionBlock = () -> () /// Set this to use a custom sizing object during updateView instead of the standard. public var sizeObject: MFSizeObject? public var scaleSize: NSNumber? + public var hero: Int? // Used for scaling the font in updateView. private var originalAttributedString: NSAttributedString? @@ -562,6 +563,17 @@ extension Label { clauses = [] Label.setUILabel(self, withJSON: json, delegate: delegateObject, additionalData: additionalData) originalAttributedString = attributedText + + hero = json?["hero"] as? Int + } + + func noticeBounds(_ rect: CGRect, color: UIColor) -> UIView { + + let view = UIView(frame: rect) + view.layer.borderColor = color.cgColor + view.layer.borderWidth = 1.0 + + return view } public func setAsMolecule() { diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index 0270cb63..0db27a35 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -88,6 +88,38 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { return cell } + open override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + + cell.layoutIfNeeded() + let heroLabel = findHeroLabel(views: cell.contentView.subviews) + let rect = Label.boundingRect(forCharacterRange: NSRange(location: heroLabel!.hero!, length: 1), in: heroLabel!) + let rectView = heroLabel?.noticeBounds(rect, color: .clear) + heroLabel?.addSubview(rectView!) + let convert = cell.contentView.convert(rectView!.center, from: heroLabel) + + cell.accessoryView?.center.y = convert.y + rectView?.removeFromSuperview() + } + + // TODO: Write recursively. + func findHeroLabel(views: [UIView]) -> Label? { + + var queue: [UIView] = views + + repeat { + for view in queue { + if let label = view as? Label, label.hero != nil { + return label + } else if !view.subviews.isEmpty { + queue.append(contentsOf: view.subviews) + } + queue.removeFirst() + } + } while (!queue.isEmpty) + + return nil + } + open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let cell = tableView.cellForRow(at: indexPath) as? MoleculeListCellProtocol { cell.didSelectCell?(atIndex: indexPath, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, additionalData: nil) @@ -111,11 +143,24 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { if let tableView = tableView { let point = molecule.convert(molecule.bounds.origin, to: tableView) if let indexPath = tableView.indexPathForRow(at: point), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { + //------------- tableView.beginUpdates() +// let cell = tableView.cellForRow(at: indexPath) +// cell.layoutIfNeeded() +// let heroLabel = findHeroLabel(views: cell.contentView.subviews) +// let rect = Label.boundingRect(forCharacterRange: NSRange(location: heroLabel!.hero!, length: 1), in: heroLabel!) +// let rectView = heroLabel?.noticeBounds(rect, color: .blue) +// heroLabel?.addSubview(rectView!) +// // let rect = Label.boundingRect(forCharacterRange: NSRange(location: 46, length: 6), in: self) +// let convert = cell.contentView.convert(rectView!.center, from: heroLabel) +// +// cell.accessoryView?.center.y = convert.y + /// ------------ tableView.endUpdates() } } } + 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. From 5bc420ac05399ebf31d2310cfe932472b5b77f94 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 18 Sep 2019 14:13:33 -0400 Subject: [PATCH 04/11] accessoryVIew can now center itself in relation to the hero character. --- MVMCoreUI/Atoms/Views/Label.swift | 24 +------- .../Molecules/ActionDetailWithImage.swift | 5 -- .../Items/MoleculeTableViewCell.swift | 57 ++++++++++++++++--- .../Molecules/MVMCoreUIMoleculeViewProtocol.h | 2 + .../Templates/MoleculeListTemplate.swift | 53 ++--------------- 5 files changed, 61 insertions(+), 80 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index 81159965..cab341b1 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -25,6 +25,8 @@ public typealias ActionBlock = () -> () /// Set this to use a custom sizing object during updateView instead of the standard. public var sizeObject: MFSizeObject? 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. @@ -503,18 +505,7 @@ public typealias ActionBlock = () -> () let accessibleAction = customAccessibilityAction(range: range) clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1)) } - -// func rectOfCharacter() { -// -//// label.intrinsicContentSize.width -// if let range = mylabel.text?.range(of: String(describing: mylabel.text?.characters.last!)) { -// let prefix = mylabel.text?.substring(to: range.lowerBound) -// let size: CGSize = prefix!.size(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 35.0)]) -// let position = CGPoint(x:size.width,y: 0) -// myScrollView.setContentOffset(position, animated: true) -// } -// } - + public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { guard let attributedText = label.attributedText else { return CGRect() } @@ -567,15 +558,6 @@ extension Label { hero = json?["hero"] as? Int } - func noticeBounds(_ rect: CGRect, color: UIColor) -> UIView { - - let view = UIView(frame: rect) - view.layer.borderColor = color.cgColor - view.layer.borderWidth = 1.0 - - return view - } - public func setAsMolecule() { styleB2(true) } diff --git a/MVMCoreUI/Molecules/ActionDetailWithImage.swift b/MVMCoreUI/Molecules/ActionDetailWithImage.swift index 8cdddb5f..ec91c479 100644 --- a/MVMCoreUI/Molecules/ActionDetailWithImage.swift +++ b/MVMCoreUI/Molecules/ActionDetailWithImage.swift @@ -166,10 +166,5 @@ import UIKit } else { button.isHidden = true } - - if let text = header.messageLabel.text, text.contains("frog") { - let rect = Label.boundingRect(forCharacterRange: NSRange(location: 65, length: 4), in: header.messageLabel) - print(rect) - } } } diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 641b3721..ce9fe93c 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -53,6 +53,16 @@ import UIKit } } + open override func layoutSubviews() { + super.layoutSubviews() + + if let center = heroAccessoryCenter { + accessoryView?.center.y = center.y + } + } + + var heroAccessoryCenter: CGPoint? + func styleStandard() { topMarginPadding = 24 bottomMarginPadding = 24 @@ -128,15 +138,51 @@ import UIKit } } + /// NOTE: Should only be called when displayed or about to be displayed. + public func alignAccessoryToHero() { + + 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) + let rectView = UIView(frame: rect) + heroLabel.addSubview(rectView) + accessoryView?.center.y = contentView.convert(rectView.center, from: heroLabel).y + heroAccessoryCenter = accessoryView?.center + rectView.removeFromSuperview() + } + + /** + Used to traverse 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 public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - self.json = json; + self.json = json style(with: json?.optionalStringForKey("style")) if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") { updateViewHorizontalDefaults = useHorizontalMargins } + if (json?.optionalBoolForKey("useVerticalMargins") ?? true) == false { topMarginPadding = 0 bottomMarginPadding = 0 @@ -161,9 +207,8 @@ import UIKit bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData) } - guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { - return - } + guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { return } + if molecule == nil { if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { contentView.addSubview(moleculeView) @@ -215,9 +260,7 @@ import UIKit // MARK: - Arrow /// Adds the standard mvm style caret to the accessory view public func addCaretViewAccessory() { - guard accessoryView == nil else { - return - } + guard accessoryView == nil else { return } let width: CGFloat = 6 let height: CGFloat = 10 caretView = CaretView(lineThickness: CaretView.thin) diff --git a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h b/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h index 4c69b1f7..273329aa 100644 --- a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h +++ b/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h @@ -24,6 +24,8 @@ /// Resets to default state before set with json is called again. - (void)reset; +/// Currently designed for UITableViewCell. Aligns the accessory view with the center of a character in a line of text. +- (void)alignAccessoryToHero; /// For the molecule list to load more efficiently. + (CGFloat)estimatedHeightForRow:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject; diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index 0db27a35..84a7e64b 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -51,9 +51,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { // MARK: - table open override func registerWithTable() { super.registerWithTable() - guard let moleculesInfo = moleculesInfo else { - return - } + guard let moleculesInfo = moleculesInfo else { return } + for moleculeInfo in moleculesInfo { tableView?.register(moleculeInfo.class, forCellReuseIdentifier: moleculeInfo.identifier) } @@ -90,34 +89,9 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { open override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - cell.layoutIfNeeded() - let heroLabel = findHeroLabel(views: cell.contentView.subviews) - let rect = Label.boundingRect(forCharacterRange: NSRange(location: heroLabel!.hero!, length: 1), in: heroLabel!) - let rectView = heroLabel?.noticeBounds(rect, color: .clear) - heroLabel?.addSubview(rectView!) - let convert = cell.contentView.convert(rectView!.center, from: heroLabel) - - cell.accessoryView?.center.y = convert.y - rectView?.removeFromSuperview() - } - - // TODO: Write recursively. - func findHeroLabel(views: [UIView]) -> Label? { - - var queue: [UIView] = views - - repeat { - for view in queue { - if let label = view as? Label, label.hero != nil { - return label - } else if !view.subviews.isEmpty { - queue.append(contentsOf: view.subviews) - } - queue.removeFirst() - } - } while (!queue.isEmpty) - - return nil + if cell.accessoryView != nil, let protocolCell = cell as? (MoleculeTableViewCell & MVMCoreUIMoleculeViewProtocol) { + protocolCell.alignAccessoryToHero() + } } open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { @@ -143,31 +117,16 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { if let tableView = tableView { let point = molecule.convert(molecule.bounds.origin, to: tableView) if let indexPath = tableView.indexPathForRow(at: point), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { - //------------- tableView.beginUpdates() -// let cell = tableView.cellForRow(at: indexPath) -// cell.layoutIfNeeded() -// let heroLabel = findHeroLabel(views: cell.contentView.subviews) -// let rect = Label.boundingRect(forCharacterRange: NSRange(location: heroLabel!.hero!, length: 1), in: heroLabel!) -// let rectView = heroLabel?.noticeBounds(rect, color: .blue) -// heroLabel?.addSubview(rectView!) -// // let rect = Label.boundingRect(forCharacterRange: NSRange(location: 46, length: 6), in: self) -// let convert = cell.contentView.convert(rectView!.center, from: heroLabel) -// -// cell.accessoryView?.center.y = convert.y - /// ------------ tableView.endUpdates() } } } - 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. DispatchQueue.main.async { - guard let cell = sender as? MoleculeTableViewCell, let indexPath = self.tableView?.indexPath(for: cell) else { - return - } + guard let cell = sender as? MoleculeTableViewCell, let indexPath = self.tableView?.indexPath(for: cell) else { return } var indexPaths: [IndexPath] = [] for molecule in molecules { if let info = self.getMoleculeInfo(with: molecule) { From 4ea916bbcd47f01ba6892648bc5f048caf51517c Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 18 Sep 2019 14:14:37 -0400 Subject: [PATCH 05/11] commenting. --- MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index ce9fe93c..1f348bb5 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -151,11 +151,9 @@ import UIKit rectView.removeFromSuperview() } - /** - Used to traverse the view hierarchy for a 🦸‍♂️heroic Label. - */ + /// Used to traverse the view hierarchy for a 🦸‍♂️heroic Label. private func findHeroLabel(views: [UIView]) -> Label? { - + if views.isEmpty { return nil } @@ -169,7 +167,7 @@ import UIKit } queue.append(contentsOf: view.subviews) } - + return findHeroLabel(views: queue) } From 1c416b5653b4feb0b308b04135aeb5c1c3cf8ce1 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 18 Sep 2019 14:15:56 -0400 Subject: [PATCH 06/11] revised. --- MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 1f348bb5..840e681b 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -151,7 +151,7 @@ import UIKit rectView.removeFromSuperview() } - /// Used to traverse the view hierarchy for a 🦸‍♂️heroic Label. + /// Traverses the view hierarchy for a 🦸‍♂️heroic Label. private func findHeroLabel(views: [UIView]) -> Label? { if views.isEmpty { From b5dcf68c5d4344e346d71479dbc2d7b4228c99e5 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 17 Oct 2019 12:17:14 -0400 Subject: [PATCH 07/11] refactoring taxtcontainer. --- MVMCoreUI/Atoms/Views/Label.swift | 44 ++++++++++++------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index cab341b1..cf1ed6f4 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -505,10 +505,12 @@ public typealias ActionBlock = () -> () let accessibleAction = customAccessibilityAction(range: range) clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1)) } - - public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { - - guard let attributedText = label.attributedText else { return CGRect() } + + /// Provides a text container in memory of how the text would appear on screen. + func abstractTextContainer() -> NSTextContainer? { + + // 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 = label.textAlignment @@ -524,9 +526,16 @@ public typealias ActionBlock = () -> () textStorage.addLayoutManager(layoutManager) textContainer.lineFragmentPadding = 0.0 - textContainer.lineBreakMode = label.lineBreakMode - textContainer.maximumNumberOfLines = label.numberOfLines - textContainer.size = label.bounds.size + textContainer.lineBreakMode = lineBreakMode + textContainer.maximumNumberOfLines = numberOfLines + textContainer.size = bounds.size + + return textContainer + } + + public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { + + let textContainer = abstractTextContainer() var glyphRange = NSRange() @@ -669,26 +678,7 @@ extension UITapGestureRecognizer { return true } - // Must configure the attributed string to translate what would appear on screen to accurately analyze. - guard let attributedText = label.attributedText else { return false } - - 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 + guard let textContainer = label.abstractTextContainer() else { return false } let indexOfGlyph = layoutManager.glyphIndex(for: location(in: label), in: textContainer) From 73ac4b932bb37dc413b829296d40a017d63ef599 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 17 Oct 2019 12:39:05 -0400 Subject: [PATCH 08/11] refacgtorerd. --- MVMCoreUI/Atoms/Views/Label.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index cf1ed6f4..fefd686d 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -506,14 +506,17 @@ public typealias ActionBlock = () -> () clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1)) } - /// Provides a text container in memory of how the text would appear on screen. - func abstractTextContainer() -> NSTextContainer? { + /** + 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)? { // 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 = label.textAlignment + paragraph.alignment = textAlignment let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText) stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count)) @@ -530,12 +533,14 @@ public typealias ActionBlock = () -> () textContainer.maximumNumberOfLines = numberOfLines textContainer.size = bounds.size - return textContainer + return (textContainer, layoutManager) } public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { - let textContainer = abstractTextContainer() + guard let abstractContainer = label.abstractTextContainer() else { return CGRect() } + let textContainer = abstractContainer.0 + let layoutManager = abstractContainer.1 var glyphRange = NSRange() @@ -678,7 +683,9 @@ extension UITapGestureRecognizer { return true } - guard let textContainer = label.abstractTextContainer() else { return false } + guard let abstractContainer = label.abstractTextContainer() else { return false } + let textContainer = abstractContainer.0 + let layoutManager = abstractContainer.1 let indexOfGlyph = layoutManager.glyphIndex(for: location(in: label), in: textContainer) From 6d52663b4f66bd8ee7ae6760e2ff4b84c6d371c7 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 17 Oct 2019 14:06:01 -0400 Subject: [PATCH 09/11] fixed small error in refactor. --- MVMCoreUI/Atoms/Views/Label.swift | 4 ++-- MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index fefd686d..6fc496bd 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -510,7 +510,7 @@ public typealias ActionBlock = () -> () 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)? { + 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 } @@ -533,7 +533,7 @@ public typealias ActionBlock = () -> () textContainer.maximumNumberOfLines = numberOfLines textContainer.size = bounds.size - return (textContainer, layoutManager) + return (textContainer, layoutManager, textStorage) } public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 568a551c..2e8b9bfc 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -126,14 +126,12 @@ import UIKit /// 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 dinmensions 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) - let rectView = UIView(frame: rect) - heroLabel.addSubview(rectView) - accessoryView?.center.y = contentView.convert(rectView.center, from: heroLabel).y + accessoryView?.center.y = contentView.convert(UIView(frame: rect).center, from: heroLabel).y heroAccessoryCenter = accessoryView?.center - rectView.removeFromSuperview() } /// Traverses the view hierarchy for a 🦸‍♂️heroic Label. From f067a20ba97734c8c82a044fe4630aaeac014dbb Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 18 Oct 2019 08:47:14 -0400 Subject: [PATCH 10/11] hero stuff. --- MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift | 10 ++++++++-- MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h | 3 --- MVMCoreUI/Templates/MoleculeListCellProtocol.h | 3 +++ MVMCoreUI/Templates/MoleculeListTemplate.swift | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 2e8b9bfc..2ae52775 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -56,6 +56,7 @@ 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 } @@ -83,6 +84,11 @@ import UIKit bottomSeparatorView?.hide() } + public func willDisplay() { + + alignAccessoryToHero() + } + // MARK: - Inits public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -126,7 +132,7 @@ import UIKit /// 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 dinmensions of subviews. + // 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) @@ -240,7 +246,7 @@ import UIKit // MARK: - Arrow /// Adds the standard mvm style caret to the accessory view - public func addCaretViewAccessory() { + @objc public func addCaretViewAccessory() { guard accessoryView == nil else { return } let width: CGFloat = 6 let height: CGFloat = 10 diff --git a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h b/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h index 273329aa..a499eb7c 100644 --- a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h +++ b/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h @@ -24,9 +24,6 @@ /// Resets to default state before set with json is called again. - (void)reset; -/// Currently designed for UITableViewCell. Aligns the accessory view with the center of a character in a line of text. -- (void)alignAccessoryToHero; - /// For the molecule list to load more efficiently. + (CGFloat)estimatedHeightForRow:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject; diff --git a/MVMCoreUI/Templates/MoleculeListCellProtocol.h b/MVMCoreUI/Templates/MoleculeListCellProtocol.h index 7603c820..8eae2e58 100644 --- a/MVMCoreUI/Templates/MoleculeListCellProtocol.h +++ b/MVMCoreUI/Templates/MoleculeListCellProtocol.h @@ -17,4 +17,7 @@ /// Handle action - (void)didSelectCellAtIndex:(nonnull NSIndexPath *)indexPath delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject additionalData:(nullable NSDictionary *)additionalData; +/// Currently designed for UITableViewCell. Aligns the accessory view with the center of a character in a line of text. +- (void)willDisplay; + @end diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index 84a7e64b..b400f0a9 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -89,8 +89,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { open override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - if cell.accessoryView != nil, let protocolCell = cell as? (MoleculeTableViewCell & MVMCoreUIMoleculeViewProtocol) { - protocolCell.alignAccessoryToHero() + if let protocolCell = cell as? MoleculeListCellProtocol { + protocolCell.willDisplay?() } } From 0bb1debc77500bfa1b4d833b4800db334ab3ff40 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 18 Oct 2019 10:28:22 -0400 Subject: [PATCH 11/11] no comment. --- MVMCoreUI/Templates/MoleculeListCellProtocol.h | 1 - 1 file changed, 1 deletion(-) diff --git a/MVMCoreUI/Templates/MoleculeListCellProtocol.h b/MVMCoreUI/Templates/MoleculeListCellProtocol.h index 8eae2e58..3fa19a02 100644 --- a/MVMCoreUI/Templates/MoleculeListCellProtocol.h +++ b/MVMCoreUI/Templates/MoleculeListCellProtocol.h @@ -17,7 +17,6 @@ /// Handle action - (void)didSelectCellAtIndex:(nonnull NSIndexPath *)indexPath delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject additionalData:(nullable NSDictionary *)additionalData; -/// Currently designed for UITableViewCell. Aligns the accessory view with the center of a character in a line of text. - (void)willDisplay; @end