| // Copyright 2015-present the Material Components for iOS authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import UIKit |
| |
| import CatalogByConvention |
| |
| import MaterialComponents.MaterialAppBar |
| import MaterialComponents.MaterialAppBar_ColorThemer |
| import MaterialComponents.MaterialAppBar_TypographyThemer |
| import MaterialComponents.MaterialButtons |
| import MaterialComponents.MaterialButtons_ButtonThemer |
| import MaterialComponents.MaterialCollections |
| import MaterialComponents.MaterialTypography |
| import MaterialComponents.MaterialButtons_Theming |
| |
| class NodeViewTableViewDemoCell: UITableViewCell { |
| |
| let label = UILabel() |
| |
| override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { |
| super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) |
| textLabel!.font = MDCTypography.subheadFont() |
| imageView!.image = UIImage(named: "Demo") |
| |
| // Ensure subtitle text is proportionally less pronounced than the title label |
| detailTextLabel?.alpha = MDCTypography.captionFontOpacity() |
| detailTextLabel?.font = MDCTypography.body2Font() |
| |
| let lineDivider = UIView() |
| lineDivider.backgroundColor = UIColor(white: 0.85, alpha: 1) |
| lineDivider.translatesAutoresizingMaskIntoConstraints = false |
| self.addSubview(lineDivider) |
| |
| NSLayoutConstraint( |
| item: lineDivider, |
| attribute: .height, |
| relatedBy: .equal, |
| toItem: nil, |
| attribute: .notAnAttribute, |
| multiplier: 1.0, |
| constant: 1).isActive = true |
| |
| // Line divider to section view |
| NSLayoutConstraint( |
| item: textLabel!, |
| attribute: .leading, |
| relatedBy: .equal, |
| toItem: lineDivider, |
| attribute: .leading, |
| multiplier: 1.0, |
| constant: 0).isActive = true |
| NSLayoutConstraint( |
| item: self, |
| attribute: .trailing, |
| relatedBy: .equal, |
| toItem: lineDivider, |
| attribute: .trailing, |
| multiplier: 1.0, |
| constant: 0).isActive = true |
| NSLayoutConstraint( |
| item: self, |
| attribute: .bottom, |
| relatedBy: .equal, |
| toItem: lineDivider, |
| attribute: .bottom, |
| multiplier: 1.0, |
| constant: 0).isActive = true |
| } |
| |
| required init(coder: NSCoder) { |
| super.init(coder: coder)! |
| } |
| |
| override func prepareForReuse() { |
| super.prepareForReuse() |
| textLabel!.textColor = UIColor.black |
| imageView!.image = UIImage(named: "Demo") |
| } |
| } |
| |
| class NodeViewTableViewPrimaryDemoCell: UITableViewCell { |
| |
| let containedButton = MDCButton() |
| |
| override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { |
| super.init(style: style, reuseIdentifier: reuseIdentifier) |
| setupContainedButton() |
| } |
| |
| required init(coder: NSCoder) { |
| super.init(coder: coder)! |
| setupContainedButton() |
| } |
| |
| func setupContainedButton() { |
| containedButton.setTitle("Start Demo", for: .normal) |
| containedButton.translatesAutoresizingMaskIntoConstraints = false |
| containedButton.accessibilityIdentifier = "start.demo"; |
| contentView.addSubview(containedButton) |
| |
| // constraints |
| NSLayoutConstraint(item: contentView, |
| attribute: .left, |
| relatedBy: .equal, |
| toItem: containedButton, |
| attribute: .left, |
| multiplier: 1, |
| constant: -16).isActive = true |
| NSLayoutConstraint(item: contentView, |
| attribute: .right, |
| relatedBy: .equal, |
| toItem: containedButton, |
| attribute: .right, |
| multiplier: 1, |
| constant: 16).isActive = true |
| NSLayoutConstraint(item: contentView, |
| attribute: .top, |
| relatedBy: .equal, |
| toItem: containedButton, |
| attribute: .top, |
| multiplier: 1, |
| constant: -10).isActive = true |
| NSLayoutConstraint(item: contentView, |
| attribute: .bottom, |
| relatedBy: .equal, |
| toItem: containedButton, |
| attribute: .bottom, |
| multiplier: 1, |
| constant: 6).isActive = true |
| } |
| |
| } |
| |
| |
| |
| class MDCNodeListViewController: CBCNodeListViewController { |
| var mainSectionHeader : UIView? |
| var mainSectionHeaderTitleLabel : UILabel? |
| var mainSectionHeaderDescriptionLabel : UILabel? |
| var additionalExamplesSectionHeader : UIView? |
| let sectionNames = ["Description", "Additional Examples"] |
| let estimadedDescriptionSectionHeight = CGFloat(100) |
| let estimadedAdditionalExamplesSectionHeight = CGFloat(50) |
| let padding = CGFloat(16) |
| var componentDescription = "" |
| var selectedNode: CBCNode? = nil |
| var titleMaxY = CGFloat(36) |
| var titleDescriptionMargin = CGFloat(34) |
| var descriptionLineHeight = CGFloat(24) |
| var demoButtonRowHeight = CGFloat(54) |
| var additionalDemoRowHeight = CGFloat(64) |
| |
| enum Section: Int { |
| case description = 0 |
| case additionalExamples = 1 |
| } |
| |
| deinit { |
| NotificationCenter.default.removeObserver(self, |
| name: AppTheme.didChangeGlobalThemeNotificationName, |
| object: nil) |
| } |
| |
| override init(node: CBCNode) { |
| super.init(node: node) |
| |
| var childrenNodes = node.children.filter { $0.isExample() } |
| |
| // Make sure that primary demo appears first |
| if let primaryDemoNodeIndex = childrenNodes.index(where: { $0.isPrimaryDemo() }), |
| primaryDemoNodeIndex != 0 { |
| let primaryDemoNode = childrenNodes[primaryDemoNodeIndex] |
| childrenNodes.remove(at: primaryDemoNodeIndex) |
| childrenNodes.insert(primaryDemoNode, at: 0) |
| } |
| |
| node.children = childrenNodes |
| |
| componentDescription = childrenNodes.first?.exampleDescription() ?? "" |
| |
| NotificationCenter.default.addObserver( |
| self, |
| selector: #selector(self.themeDidChange), |
| name: AppTheme.didChangeGlobalThemeNotificationName, |
| object: nil) |
| } |
| |
| override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { |
| super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) |
| } |
| |
| required init?(coder aDecoder: NSCoder) { |
| super.init(coder: aDecoder) |
| } |
| |
| override func viewDidLoad() { |
| super.viewDidLoad() |
| |
| mainSectionHeader = MainSectionHeader() |
| additionalExamplesSectionHeader = createAdditionalExamplesSectionHeader() |
| self.tableView.backgroundColor = AppTheme.containerScheme.colorScheme.backgroundColor |
| self.tableView.separatorStyle = .none |
| self.tableView.sectionHeaderHeight = UITableView.automaticDimension |
| |
| var charactersCount = 0 |
| charactersCount = node.title.count |
| if charactersCount > 0 { |
| self.tableView.accessibilityIdentifier = "Table" + node.title |
| } else { |
| self.tableView.accessibilityIdentifier = "DemoTableList" |
| } |
| |
| self.tableView.register(NodeViewTableViewPrimaryDemoCell.self, |
| forCellReuseIdentifier: "NodeViewTableViewPrimaryDemoCell") |
| self.tableView.register(NodeViewTableViewDemoCell.self, |
| forCellReuseIdentifier: "NodeViewTableViewDemoCell") |
| } |
| |
| override func viewWillAppear(_ animated: Bool) { |
| super.viewWillAppear(animated) |
| |
| self.navigationController?.setNavigationBarHidden(true, animated: animated) |
| } |
| |
| @objc func themeDidChange(notification: NSNotification) { |
| setNeedsStatusBarAppearanceUpdate() |
| self.tableView.reloadData() |
| } |
| } |
| |
| // MARK: UITableViewDataSource |
| extension MDCNodeListViewController { |
| |
| override func numberOfSections(in tableView: UITableView) -> Int { |
| if node.children.count == 1 { |
| return 1 |
| } |
| return sectionNames.count |
| } |
| |
| override func tableView(_ tableView: UITableView, |
| titleForHeaderInSection section: Int) -> String? { |
| return sectionNames[section] |
| } |
| override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { |
| if indexPath.section == Section.description.rawValue { |
| return demoButtonRowHeight |
| } |
| return additionalDemoRowHeight |
| } |
| |
| override func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { |
| if let mainSectionHeader = mainSectionHeader, section == Section.description.rawValue { |
| let labelPreferredMaxLayoutWidth = tableView.frame.size.width - (2 * padding) |
| mainSectionHeaderDescriptionLabel?.preferredMaxLayoutWidth = labelPreferredMaxLayoutWidth |
| mainSectionHeaderTitleLabel?.preferredMaxLayoutWidth = labelPreferredMaxLayoutWidth |
| let targetSize = CGSize(width: tableView.frame.size.width, |
| height: estimadedDescriptionSectionHeight) |
| return mainSectionHeader.systemLayoutSizeFitting(targetSize).height |
| } |
| return estimadedAdditionalExamplesSectionHeight |
| } |
| // swiftlint:disable function_body_length |
| override func tableView(_ tableView: UITableView, |
| viewForHeaderInSection section: Int) -> UIView? { |
| if section == 0 { |
| return mainSectionHeader |
| } |
| return additionalExamplesSectionHeader |
| } |
| |
| func createAdditionalExamplesSectionHeader() -> UIView { |
| let sectionView = UIView() |
| sectionView.backgroundColor = AppTheme.containerScheme.colorScheme.backgroundColor |
| let lineDivider = UIView() |
| lineDivider.backgroundColor = UIColor(white: 0.85, alpha: 1) |
| lineDivider.translatesAutoresizingMaskIntoConstraints = false |
| |
| let sectionTitleLabel = UILabel() |
| sectionTitleLabel.font = MDCTypography.body2Font() |
| sectionTitleLabel.translatesAutoresizingMaskIntoConstraints = false |
| sectionTitleLabel.text = sectionNames[1] |
| sectionTitleLabel.numberOfLines = 0 |
| sectionTitleLabel.setContentCompressionResistancePriority(.required, for: .vertical) |
| |
| sectionView.addSubview(lineDivider) |
| sectionView.addSubview(sectionTitleLabel) |
| |
| // Line divider constraints |
| NSLayoutConstraint( |
| item: lineDivider, |
| attribute: .height, |
| relatedBy: .equal, |
| toItem: nil, |
| attribute: .notAnAttribute, |
| multiplier: 1.0, |
| constant: 1).isActive = true |
| |
| // Line divider to section view |
| NSLayoutConstraint( |
| item: sectionView, |
| attribute: .leading, |
| relatedBy: .equal, |
| toItem: lineDivider, |
| attribute: .leading, |
| multiplier: 1.0, |
| constant: 0).isActive = true |
| NSLayoutConstraint( |
| item: sectionView, |
| attribute: .trailing, |
| relatedBy: .equal, |
| toItem: lineDivider, |
| attribute: .trailing, |
| multiplier: 1.0, |
| constant: 0).isActive = true |
| NSLayoutConstraint( |
| item: sectionView, |
| attribute: .top, |
| relatedBy: .equal, |
| toItem: lineDivider, |
| attribute: .top, |
| multiplier: 1.0, |
| constant: 0).isActive = true |
| |
| // Line divider to Title Label |
| NSLayoutConstraint( |
| item: lineDivider, |
| attribute: .top, |
| relatedBy: .equal, |
| toItem: sectionTitleLabel, |
| attribute: .top, |
| multiplier: 1.0, |
| constant: -padding).isActive = true |
| |
| let preiOS11Behavior = { |
| NSLayoutConstraint( |
| item: sectionView, |
| attribute: .leading, |
| relatedBy: .equal, |
| toItem: sectionTitleLabel, |
| attribute: .leading, |
| multiplier: 1.0, |
| constant: -self.padding).isActive = true |
| NSLayoutConstraint( |
| item: sectionView, |
| attribute: .trailing, |
| relatedBy: .equal, |
| toItem: sectionTitleLabel, |
| attribute: .trailing, |
| multiplier: 1.0, |
| constant: self.padding).isActive = true |
| } |
| // Title Label to Section View |
| if #available(iOS 11.0, *) { |
| // Align to the safe area insets. |
| sectionTitleLabel.leadingAnchor |
| .constraint(equalTo: sectionView.safeAreaLayoutGuide.leadingAnchor, |
| constant: padding).isActive = true |
| sectionTitleLabel.trailingAnchor |
| .constraint(equalTo: sectionView.safeAreaLayoutGuide.trailingAnchor, |
| constant: -padding).isActive = true |
| } else { |
| preiOS11Behavior() |
| } |
| |
| NSLayoutConstraint( |
| item: sectionView, |
| attribute: .bottom, |
| relatedBy: .equal, |
| toItem: sectionTitleLabel, |
| attribute: .bottom, |
| multiplier: 1.0, |
| constant: padding).isActive = true |
| |
| return sectionView |
| } |
| |
| func MainSectionHeader() -> UIView { |
| let sectionView = UIView() |
| sectionView.backgroundColor = AppTheme.containerScheme.colorScheme.backgroundColor |
| |
| let sectionTitleLabel = UILabel() |
| sectionTitleLabel.font = MDCTypography.body2Font() |
| sectionTitleLabel.translatesAutoresizingMaskIntoConstraints = false |
| sectionTitleLabel.text = sectionNames[0] |
| sectionTitleLabel.numberOfLines = 0 |
| sectionTitleLabel.setContentCompressionResistancePriority(.required, for: .vertical) |
| mainSectionHeaderTitleLabel = sectionTitleLabel |
| |
| let descriptionLabel = UILabel() |
| descriptionLabel.font = MDCTypography.body1Font() |
| |
| let paragraphStyle = NSMutableParagraphStyle() |
| paragraphStyle.lineSpacing = descriptionLineHeight - descriptionLabel.font.lineHeight |
| let attrs = [NSAttributedString.Key.paragraphStyle: paragraphStyle] |
| |
| descriptionLabel.attributedText = |
| NSAttributedString(string:componentDescription, attributes:attrs) |
| descriptionLabel.alpha = MDCTypography.body1FontOpacity() |
| descriptionLabel.translatesAutoresizingMaskIntoConstraints = false |
| descriptionLabel.numberOfLines = 0 |
| descriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) |
| mainSectionHeaderDescriptionLabel = descriptionLabel |
| |
| sectionView.addSubview(sectionTitleLabel) |
| sectionView.addSubview(descriptionLabel) |
| |
| // sectionTitleLabel to SectionView |
| let preiOS11Behavior = { |
| NSLayoutConstraint( |
| item: sectionView, |
| attribute: .leading, |
| relatedBy: .equal, |
| toItem: sectionTitleLabel, |
| attribute: .leading, |
| multiplier: 1.0, |
| constant: -self.padding).isActive = true |
| NSLayoutConstraint( |
| item: sectionView, |
| attribute: .trailing, |
| relatedBy: .equal, |
| toItem: sectionTitleLabel, |
| attribute: .trailing, |
| multiplier: 1.0, |
| constant: self.padding).isActive = true |
| } |
| if #available(iOS 11.0, *) { |
| // Align to the safe area insets. |
| sectionTitleLabel.leadingAnchor |
| .constraint(equalTo: sectionView.safeAreaLayoutGuide.leadingAnchor, |
| constant: padding).isActive = true |
| sectionTitleLabel.trailingAnchor |
| .constraint(equalTo: sectionView.safeAreaLayoutGuide.trailingAnchor, |
| constant: -padding).isActive = true |
| } else { |
| preiOS11Behavior() |
| } |
| |
| NSLayoutConstraint( |
| item: sectionTitleLabel, |
| attribute: .lastBaseline, |
| relatedBy: .equal, |
| toItem: sectionView, |
| attribute: .top, |
| multiplier: 1.0, |
| constant: titleMaxY).isActive = true |
| |
| // descriptionLabel to sectionTitleLabel |
| NSLayoutConstraint( |
| item: sectionTitleLabel, |
| attribute: .leading, |
| relatedBy: .equal, |
| toItem: descriptionLabel, |
| attribute: .leading, |
| multiplier: 1.0, |
| constant: 0).isActive = true |
| NSLayoutConstraint( |
| item: sectionTitleLabel, |
| attribute: .trailing, |
| relatedBy: .equal, |
| toItem: descriptionLabel, |
| attribute: .trailing, |
| multiplier: 1.0, |
| constant: 0).isActive = true |
| NSLayoutConstraint( |
| item: sectionTitleLabel, |
| attribute: .lastBaseline, |
| relatedBy: .equal, |
| toItem: descriptionLabel, |
| attribute: .firstBaseline, |
| multiplier: 1.0, |
| constant: -titleDescriptionMargin).isActive = true |
| |
| // descriptionLabel to SectionView |
| NSLayoutConstraint( |
| item: sectionView, |
| attribute: .bottom, |
| relatedBy: .equal, |
| toItem: descriptionLabel, |
| attribute: .bottom, |
| multiplier: 1.0, |
| constant: 10).isActive = true |
| |
| return sectionView |
| } |
| |
| |
| override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
| if section == Section.description.rawValue { |
| return 1 |
| } |
| return node.children.count - 1 |
| } |
| |
| override func tableView(_ tableView: UITableView, |
| cellForRowAt indexPath: IndexPath) -> UITableViewCell { |
| var cell : UITableViewCell? |
| if indexPath.section == Section.description.rawValue { |
| cell = tableView.dequeueReusableCell(withIdentifier: "NodeViewTableViewPrimaryDemoCell") |
| cell?.selectionStyle = .none |
| let primaryDemoCell = cell as! NodeViewTableViewPrimaryDemoCell |
| let button = primaryDemoCell.containedButton |
| button.applyContainedTheme(withScheme: AppTheme.containerScheme) |
| button.addTarget(self, action: #selector(primaryDemoButtonClicked), for: .touchUpInside) |
| } else { |
| cell = tableView.dequeueReusableCell(withIdentifier: "NodeViewTableViewDemoCell") |
| var subtitleText: String? |
| if indexPath.section == Section.description.rawValue { |
| subtitleText = node.children[indexPath.row].exampleViewControllerName() |
| cell!.textLabel!.text = "Demo" |
| } else { |
| subtitleText = node.children[indexPath.row + 1].exampleViewControllerName() |
| cell!.textLabel!.text = node.children[indexPath.row + 1].title |
| } |
| if subtitleText != nil { |
| if let swiftModuleRange = subtitleText?.range(of: ".") { |
| subtitleText = subtitleText!.substring(from: swiftModuleRange.upperBound) |
| } |
| cell!.detailTextLabel?.text = subtitleText! |
| } |
| cell!.accessibilityIdentifier = "Cell" + cell!.textLabel!.text! |
| cell!.accessoryType = .disclosureIndicator |
| } |
| cell?.backgroundColor = AppTheme.containerScheme.colorScheme.backgroundColor |
| return cell! |
| } |
| |
| override func accessibilityPerformMagicTap() -> Bool { |
| primaryDemoButtonClicked() |
| return true |
| } |
| |
| @objc func primaryDemoButtonClicked () { |
| let indexPath = IndexPath(row: 0, section: Section.description.rawValue) |
| self.tableView(self.tableView, didSelectRowAt: indexPath) |
| } |
| } |
| |
| // MARK: UITableViewDelegate |
| extension MDCNodeListViewController { |
| |
| override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { |
| var node = self.node.children[0] |
| if indexPath.section == Section.additionalExamples.rawValue { |
| node = self.node.children[indexPath.row + 1] |
| } |
| var vc: UIViewController |
| if node.isExample() { |
| selectedNode = node |
| vc = createViewController(from: node) |
| vc.title = node.title |
| } else { |
| vc = MDCNodeListViewController(node: node) |
| } |
| self.navigationController?.pushViewController(vc, animated: true) |
| } |
| |
| func createViewController(from node: CBCNode) -> UIViewController { |
| let contentVC = node.createExampleViewController() |
| themeExample(vc: contentVC) |
| self.navigationController?.setMenuBarButton(for: contentVC) |
| return contentVC |
| } |
| |
| func themeExample(vc: UIViewController) { |
| let colorSel = NSSelectorFromString("setColorScheme:"); |
| if vc.responds(to: colorSel) { |
| vc.perform(colorSel, with: AppTheme.containerScheme.colorScheme) |
| } |
| let typoSel = NSSelectorFromString("setTypographyScheme:"); |
| if vc.responds(to: typoSel) { |
| vc.perform(typoSel, with: AppTheme.containerScheme.typographyScheme) |
| } |
| let containerSel = NSSelectorFromString("setContainerScheme:") |
| if vc.responds(to: containerSel) { |
| vc.perform(containerSel, with: AppTheme.containerScheme) |
| } |
| } |
| } |