From b4c53f838b2b1efed0ff312ca24ad256056a5559 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 9 Sep 2019 14:07:32 -0400 Subject: [PATCH] Tabs table cell --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + .../Containers/TabBarController/TopTabbar.h | 3 + .../Containers/TabBarController/TopTabbar.m | 6 +- .../AccordionMoleculeTableViewCell.swift | 4 +- .../Items/MoleculeTableViewCell.swift | 2 +- .../Molecules/Items/TabsTableViewCell.swift | 81 +++++++++++++++++++ .../MVMCoreUIMoleculeMappingObject.m | 3 +- .../OtherHandlers/MoleculeDelegateProtocol.h | 4 +- .../Templates/MoleculeListTemplate.swift | 44 +++++----- 9 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 MVMCoreUI/Molecules/Items/TabsTableViewCell.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 9768fcf8..59f736a5 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */; }; D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* StandardFooterView.swift */; }; + D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */; }; D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */; }; D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */; }; D282AACB2243C61700C46919 /* ButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AACA2243C61700C46919 /* ButtonView.swift */; }; @@ -222,6 +223,7 @@ D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIPageControl.m; sourceTree = ""; }; D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPagingProtocol.h; sourceTree = ""; }; D274CA322236A78900B01B62 /* StandardFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardFooterView.swift; sourceTree = ""; }; + D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTableViewCell.swift; sourceTree = ""; }; D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFLoadImageView.swift; sourceTree = ""; }; D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFTransparentGIFView.swift; sourceTree = ""; }; D282AACA2243C61700C46919 /* ButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonView.swift; sourceTree = ""; }; @@ -451,6 +453,7 @@ D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */, D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, + D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */, ); path = Items; sourceTree = ""; @@ -997,6 +1000,7 @@ DBC4391922442197001AB423 /* DashLine.swift in Sources */, D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */, + D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */, D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */, D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, diff --git a/MVMCoreUI/Containers/TabBarController/TopTabbar.h b/MVMCoreUI/Containers/TabBarController/TopTabbar.h index e63911e7..d851294b 100644 --- a/MVMCoreUI/Containers/TabBarController/TopTabbar.h +++ b/MVMCoreUI/Containers/TabBarController/TopTabbar.h @@ -30,6 +30,9 @@ @property (nonatomic, readonly) NSInteger selectedIndex; +/// A flag for if there should be padding before the first item. +@property (nonatomic) BOOL paddingBeforeFirstTab; + //method to set the height - (void)pinHeight:(CGFloat)height; diff --git a/MVMCoreUI/Containers/TabBarController/TopTabbar.m b/MVMCoreUI/Containers/TabBarController/TopTabbar.m index c52e9d66..e9d77c77 100644 --- a/MVMCoreUI/Containers/TabBarController/TopTabbar.m +++ b/MVMCoreUI/Containers/TabBarController/TopTabbar.m @@ -75,6 +75,7 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (void)setupView { + self.paddingBeforeFirstTab = YES; self.maxHeight = BAR_HEIGHT; self.selectedIndex = 0; self.backgroundColor = [UIColor whiteColor]; @@ -229,6 +230,9 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { + if (!self.paddingBeforeFirstTab && section == 0) { + return UIEdgeInsetsMake(SECTION_TOPPIN, 0,SECTION_BOTPIN, 0); + } return UIEdgeInsetsMake(SECTION_TOPPIN, SECTION_PADDING,SECTION_BOTPIN, 0); } @@ -341,7 +345,7 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (void)selectIndex:(NSInteger)index animated:(BOOL)animated { - if (self.collectionView) { + if (self.collectionView && [self.datasource numberOfTopTabbarItems:self] > 0) { [MVMCoreDispatchUtility performBlockOnMainThread:^{ NSInteger currentIndex = self.selectedIndex; self.selectedIndex = index; diff --git a/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift index b188d45b..075d33f1 100644 --- a/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift @@ -34,9 +34,9 @@ import UIKit } if accordionButton.isSelected { - delegateObject?.moleculeDelegate?.addMolecules?(molecules, senderIndexPath: indexPath) + delegateObject?.moleculeDelegate?.addMolecules?(molecules, sender: self, animation: .automatic) } else { - delegateObject?.moleculeDelegate?.removeMolecules?(molecules, senderIndexPath: indexPath) + delegateObject?.moleculeDelegate?.removeMolecules?(molecules, sender: self, animation: .automatic) } if (json?.boolForKey("hideSeparatorWhenExpanded") ?? false) && (self.bottomSeparatorView?.shouldBeVisible() ?? false) { diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 210eac6f..641b3721 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -137,7 +137,7 @@ import UIKit if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") { updateViewHorizontalDefaults = useHorizontalMargins } - if json?.optionalBoolForKey("useVerticalMargins") ?? false { + if (json?.optionalBoolForKey("useVerticalMargins") ?? true) == false { topMarginPadding = 0 bottomMarginPadding = 0 } diff --git a/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift new file mode 100644 index 00000000..1c509a8f --- /dev/null +++ b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift @@ -0,0 +1,81 @@ +// +// TabsTableViewCell.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/6/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class TabsTableViewCell: MoleculeTableViewCell { + let tabs = TopTabbar(frame: .zero) + var delegateObject: MVMCoreUIDelegateObject? + var previousTabIndex = 0 + + // MARK: - MFViewProtocol + override public func setupView() { + super.setupView() + guard tabs.superview == nil else { + return + } + tabs.paddingBeforeFirstTab = false + topMarginPadding = 8 + bottomMarginPadding = 0 + + tabs.translatesAutoresizingMaskIntoConstraints = false + tabs.delegate = self + tabs.datasource = self + contentView.addSubview(tabs) + + NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: tabs, useMargins: true).values)) + tabs.reloadData() + } + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + tabs.updateView(size) + } + + // MARK: - MoleculeDelegateProtocol + public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject + tabs.reloadData() + } + + public override func reset() { + super.reset() + topMarginPadding = 8 + bottomMarginPadding = 0 + } +} + +extension TabsTableViewCell: TopTabbarDelegate { + public func shouldSelectItem(at index: Int, topTabbar: TopTabbar) -> Bool { + if let moleculesArrays = json?.arrayForKey(KeyMolecules), let molecules = moleculesArrays[topTabbar.selectedIndex] as? [[AnyHashable: Any]] { + delegateObject?.moleculeDelegate?.removeMolecules?(molecules, sender: self, animation: index < tabs.selectedIndex ? .right : .left) + } + previousTabIndex = tabs.selectedIndex + return true + } + + public func topTabbar(_ topTabbar: TopTabbar, didSelectItemAt index: Int) { + if let moleculesArrays = json?.arrayForKey(KeyMolecules), let molecules = moleculesArrays[index] as? [[AnyHashable: Any]] { + delegateObject?.moleculeDelegate?.addMolecules?(molecules, sender: self, animation: index < previousTabIndex ? .left : .right) + } + } +} + +extension TabsTableViewCell: TopTabbarDataSource { + public func number(ofTopTabbarItems topTabbar: TopTabbar) -> Int { + return json?.optionalDictionaryForKey("tabs")?.optionalArrayForKey("tabs")?.count ?? 0 + } + + public func topTabbar(_ topTabbar: TopTabbar, titleForItemAt index: Int) -> String? { + guard let tabs = json?.optionalDictionaryForKey("tabs")?.arrayForKey("tabs"), let label = tabs[index] as? [AnyHashable: Any], let title = label.optionalStringForKey(KeyText) else { + return "Select" + } + return title + } +} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 9175781b..d99641ea 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -58,7 +58,8 @@ @"labelSwitch": LabelSwitch.class, @"headlineBodySwitch": HeadlineBodySwitch.class, @"headlineBodyTextButton": HeadlineBodyTextButton.class, - @"headlineBodyTextButtonSwitch": HeadlineBodyTextButtonSwitch.class + @"headlineBodyTextButtonSwitch": HeadlineBodyTextButtonSwitch.class, + @"tabsListItem": TabsTableViewCell.class } mutableCopy]; }); return mapping; diff --git a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h index cfa1504f..57eb63f5 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h +++ b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h @@ -17,7 +17,7 @@ - (void)moleculeLayoutUpdated:(nonnull UIView *)molecule; /// Asks the delegate to add or remove molecules. -- (void)addMolecules:(nonnull NSArray *)molecules senderIndexPath:(nonnull NSIndexPath *)indexPath; -- (void)removeMolecules:(nonnull NSArray *)molecules senderIndexPath:(nonnull NSIndexPath *)indexPath; +- (void)addMolecules:(nonnull NSArray *)molecules sender:(nonnull UITableViewCell *)sender animation:(UITableViewRowAnimation)animation; +- (void)removeMolecules:(nonnull NSArray *)molecules sender:(nonnull UITableViewCell *)sender animation:(UITableViewRowAnimation)animation; @end diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index 7ad011b3..0270cb63 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -117,34 +117,40 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { } } - open override func addMolecules(_ molecules: [[AnyHashable: Any]], senderIndexPath indexPath: IndexPath) { - var indexPaths: [IndexPath] = [] - var moleculeList: [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])] = [] - for (index, molecule) in molecules.enumerated() { - if let info = getMoleculeInfo(with: molecule) { - moleculeList.append(info) - indexPaths.append(IndexPath(row: indexPath.row + 1 + index, section: 0)) - tableView?.register(info.class, forCellReuseIdentifier: info.identifier) + 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 } + var indexPaths: [IndexPath] = [] + for molecule in molecules { + if let info = self.getMoleculeInfo(with: molecule) { + self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) + let index = indexPath.row + 1 + indexPaths.count + self.moleculesInfo?.insert(info, at: index) + indexPaths.append(IndexPath(row: index, section: 0)) + } + } + self.tableView?.insertRows(at: indexPaths, with: animation) + self.updateViewConstraints() + self.view.layoutIfNeeded() } - moleculesInfo?.insert(contentsOf: moleculeList, at: indexPath.row + 1) - tableView?.insertRows(at: indexPaths, with: .automatic) } - open override func removeMolecules(_ molecules: [[AnyHashable: Any]], senderIndexPath indexPath: IndexPath) { - guard let moleculesList = moleculesInfo else { - return - } + open override func removeMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { var indexPaths: [IndexPath] = [] - for (index, moleculeInfo) in moleculesList.enumerated() { - if molecules.contains(where: { (molecule) -> Bool in + for molecule in molecules { + if let removeIndex = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in return NSDictionary(dictionary: molecule).isEqual(to: moleculeInfo.molecule) }) { - indexPaths.append(IndexPath(row: index, section: 0)) - moleculesInfo?.remove(at: index) + moleculesInfo?.remove(at: removeIndex) + indexPaths.append(IndexPath(row: removeIndex + indexPaths.count, section: 0)) } } - tableView?.deleteRows(at: indexPaths, with: .automatic) + self.tableView?.deleteRows(at: indexPaths, with: animation) + self.updateViewConstraints() + self.view.layoutIfNeeded() } // MARK: - Convenience