From 5bc420ac05399ebf31d2310cfe932472b5b77f94 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 18 Sep 2019 14:13:33 -0400 Subject: [PATCH] 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) {