| // Copyright 2019-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 MaterialComponents.MaterialCollections |
| import MaterialComponents.MaterialDialogs |
| import MaterialComponents.MaterialDialogs_Theming |
| import MaterialComponents.MaterialTextControls_FilledTextFields |
| import MaterialComponents.MaterialTextControls_FilledTextFieldsTheming |
| import MaterialComponents.MaterialContainerScheme |
| import MaterialComponents.MaterialTypographyScheme |
| |
| class DialogsAccessoryExampleViewController: MDCCollectionViewController { |
| |
| @objc lazy var containerScheme: MDCContainerScheming = { |
| let scheme = MDCContainerScheme() |
| scheme.colorScheme = MDCSemanticColorScheme(defaults: .material201907) |
| scheme.typographyScheme = MDCTypographyScheme(defaults: .material201902) |
| return scheme |
| }() |
| |
| let kReusableIdentifierItem = "customCell" |
| |
| var menu: [String] = [] |
| |
| var handler: MDCActionHandler = { action in |
| print(action.title ?? "Some Action") |
| } |
| |
| override func viewDidLoad() { |
| super.viewDidLoad() |
| |
| view.backgroundColor = containerScheme.colorScheme.backgroundColor |
| |
| loadCollectionView(menu: [ |
| "Material Filled Text Field", |
| "UI Text Field", |
| "Confirmation Dialog", |
| "Autolayout in Custom View", |
| ]) |
| } |
| |
| func loadCollectionView(menu: [String]) { |
| self.collectionView?.register( |
| MDCCollectionViewTextCell.self, forCellWithReuseIdentifier: kReusableIdentifierItem) |
| self.menu = menu |
| } |
| |
| override func collectionView( |
| _ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath |
| ) { |
| guard let alert = performActionFor(row: indexPath.row) else { return } |
| self.present(alert, animated: true, completion: nil) |
| } |
| |
| private func performActionFor(row: Int) -> MDCAlertController? { |
| switch row { |
| case 0: |
| return performMDCTextField() |
| case 1: |
| return performUITextField() |
| case 2: |
| return performConfirmationDialog() |
| case 3: |
| return performCustomLabelWithButton() |
| default: |
| print("No row is selected") |
| return nil |
| } |
| } |
| |
| // Demonstrate a custom view with MDCFilledTextField being assigned to the accessoryView API. |
| // This example also demonstrates the use of autolayout in custom views. |
| func performMDCTextField() -> MDCAlertController { |
| let alert = MDCAlertController(title: "Rename File", message: nil) |
| alert.addAction(MDCAlertAction(title: "Rename", emphasis: .medium, handler: handler)) |
| alert.addAction(MDCAlertAction(title: "Cancel", emphasis: .low, handler: handler)) |
| |
| if let alertView = alert.view as? MDCAlertControllerView { |
| alertView.contentInsets.bottom = 16.0 |
| } |
| let view = UIView(frame: CGRect.zero) |
| let label = newLabel(text: "OLD_FILE.PNG will be renamed:") |
| |
| let namefield = MDCFilledTextField() |
| namefield.label.text = "New File Name" |
| namefield.placeholder = "Enter a new file name" |
| namefield.labelBehavior = MDCTextControlLabelBehavior.floats |
| namefield.clearButtonMode = UITextField.ViewMode.whileEditing |
| namefield.leadingAssistiveLabel.text = "An optional assistive message" |
| namefield.applyTheme(withScheme: containerScheme) |
| // Enable dynamic type. |
| namefield.adjustsFontForContentSizeCategory = true |
| namefield.font = UIFont.preferredFont( |
| forTextStyle: .body, compatibleWith: namefield.traitCollection) |
| namefield.leadingAssistiveLabel.font = UIFont.preferredFont( |
| forTextStyle: .caption2, compatibleWith: namefield.traitCollection) |
| |
| label.translatesAutoresizingMaskIntoConstraints = false |
| namefield.translatesAutoresizingMaskIntoConstraints = false |
| view.translatesAutoresizingMaskIntoConstraints = true |
| |
| view.addSubview(label) |
| view.addSubview(namefield) |
| |
| label.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true |
| label.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true |
| label.topAnchor.constraint(equalTo: view.topAnchor).isActive = true |
| label.bottomAnchor.constraint(equalTo: namefield.topAnchor, constant: -10).isActive = true |
| |
| namefield.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true |
| namefield.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true |
| namefield.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true |
| |
| alert.accessoryView = view |
| alert.mdc_adjustsFontForContentSizeCategory = true // Enable dynamic type. |
| alert.applyTheme(withScheme: self.containerScheme) |
| return alert |
| } |
| |
| func performUITextField() -> MDCAlertController { |
| let alert = MDCAlertController(title: "This is a title", message: "This is a message") |
| let textField = UITextField() |
| textField.placeholder = "This is a text field" |
| alert.accessoryView = textField |
| alert.addAction(MDCAlertAction(title: "Dismiss", emphasis: .medium, handler: handler)) |
| alert.mdc_adjustsFontForContentSizeCategory = true // Enable dynamic type. |
| alert.applyTheme(withScheme: self.containerScheme) |
| return alert |
| } |
| |
| // Demonstrate a confirmation dialog with a custom table view. |
| func performConfirmationDialog() -> MDCAlertController { |
| let alert = MDCAlertController(title: "Phone ringtone", message: "Please select a ringtone:") |
| alert.addAction(MDCAlertAction(title: "OK", handler: handler)) |
| alert.addAction(MDCAlertAction(title: "Cancel", handler: handler)) |
| |
| alert.accessoryView = ExampleTableSeparatorView() |
| |
| if let alertView = alert.view as? MDCAlertControllerView { |
| // Zero bottom-inset ensuring the bottom separator appears immediately above the actions. |
| alertView.contentInsets.bottom = 0 |
| // Decreasing vertical margin between the accessory view and the message |
| alertView.accessoryViewVerticalInset = 8 |
| // Aligning the accessory view with the dialog's edge by removing all horizontal insets. |
| alertView.accessoryViewHorizontalInset = -alertView.contentInsets.left |
| } |
| |
| alert.mdc_adjustsFontForContentSizeCategory = true // Enable dynamic type. |
| alert.applyTheme(withScheme: self.containerScheme) |
| return alert |
| } |
| |
| // Demonstrate a custom accessory view with auto layout, presenting a label and a button. |
| func performCustomLabelWithButton() -> MDCAlertController { |
| let alert = MDCAlertController(title: "Title", message: nil) |
| alert.addAction(MDCAlertAction(title: "Dismiss", emphasis: .medium, handler: handler)) |
| |
| let view = UIView(frame: CGRect.zero) |
| let label = newLabel(text: "Your storage is full. Your storage is full.") |
| let button = MDCButton() |
| button.setTitle("Learn More", for: UIControl.State.normal) |
| button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 0, bottom: 0, right: 8) |
| button.applyTextTheme(withScheme: containerScheme) |
| |
| label.translatesAutoresizingMaskIntoConstraints = false |
| button.translatesAutoresizingMaskIntoConstraints = false |
| view.translatesAutoresizingMaskIntoConstraints = true |
| |
| view.addSubview(label) |
| view.addSubview(button) |
| |
| label.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true |
| label.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true |
| label.topAnchor.constraint(equalTo: view.topAnchor).isActive = true |
| label.bottomAnchor.constraint(equalTo: button.topAnchor).isActive = true |
| |
| button.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true |
| button.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor).isActive = true |
| button.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true |
| |
| alert.accessoryView = view |
| alert.mdc_adjustsFontForContentSizeCategory = true // Enable dynamic type. |
| alert.applyTheme(withScheme: self.containerScheme) |
| |
| return alert |
| } |
| |
| func newLabel(text: String) -> UILabel { |
| let label = UILabel() |
| label.textColor = containerScheme.colorScheme.onSurfaceColor |
| label.font = containerScheme.typographyScheme.subtitle2 |
| label.text = text |
| label.numberOfLines = 0 |
| return label |
| } |
| |
| } |
| |
| // MDCCollectionViewController Data Source |
| extension DialogsAccessoryExampleViewController { |
| |
| override func numberOfSections(in collectionView: UICollectionView) -> Int { |
| return 1 |
| } |
| |
| override func collectionView( |
| _ collectionView: UICollectionView, numberOfItemsInSection section: Int |
| ) -> Int { |
| return menu.count |
| } |
| |
| override func collectionView( |
| _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath |
| ) -> UICollectionViewCell { |
| |
| let cell = collectionView.dequeueReusableCell( |
| withReuseIdentifier: kReusableIdentifierItem, |
| for: indexPath) |
| guard let customCell = cell as? MDCCollectionViewTextCell else { return cell } |
| |
| customCell.isAccessibilityElement = true |
| customCell.accessibilityTraits = .button |
| |
| let cellTitle = menu[indexPath.row] |
| customCell.accessibilityLabel = cellTitle |
| customCell.textLabel?.text = cellTitle |
| |
| return customCell |
| } |
| } |
| |
| // MARK: Catalog by convention |
| extension DialogsAccessoryExampleViewController { |
| |
| @objc class func catalogMetadata() -> [String: Any] { |
| return [ |
| "breadcrumbs": ["Dialogs", "Dialog With Accessory View"], |
| "primaryDemo": false, |
| "presentable": true, |
| ] |
| } |
| } |
| |
| // MARK: Snapshot Testing by Convention |
| extension DialogsAccessoryExampleViewController { |
| |
| func resetTests() { |
| if presentedViewController != nil { |
| dismiss(animated: false) |
| } |
| } |
| |
| @objc func testTextField() { |
| resetTests() |
| self.present(performUITextField(), animated: false, completion: nil) |
| } |
| |
| @objc func testMDCTextField() { |
| resetTests() |
| self.present(performMDCTextField(), animated: false, completion: nil) |
| } |
| |
| @objc func testCustomLabelWithButton() { |
| resetTests() |
| self.present(performCustomLabelWithButton(), animated: false, completion: nil) |
| } |
| |
| @objc func testConfirmationDialog() { |
| resetTests() |
| self.present(performConfirmationDialog(), animated: false, completion: nil) |
| } |
| } |
| |
| // An example view with a tableview and a bottom separator. |
| class ExampleTableSeparatorView: UIView, UITableViewDataSource { |
| |
| let ringtones = ["Callisto", "Luna", "Phobos", "Dione"] |
| |
| let tableView: UITableView = { |
| let tv = AutoSizedTableView() |
| tv.register(UITableViewCell.self, forCellReuseIdentifier: "cell") |
| tv.separatorStyle = .none |
| tv.rowHeight = 40 |
| return tv |
| }() |
| |
| init() { |
| super.init(frame: .zero) |
| setup() |
| } |
| |
| required init?(coder: NSCoder) { |
| fatalError("init(coder:) has not been implemented") |
| } |
| |
| func setup() { |
| tableView.dataSource = self |
| tableView.alwaysBounceVertical = false |
| addSubview(tableView) |
| |
| let separator = UIView(frame: .zero) |
| separator.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5) |
| addSubview(separator) |
| |
| tableView.translatesAutoresizingMaskIntoConstraints = false |
| separator.translatesAutoresizingMaskIntoConstraints = false |
| |
| NSLayoutConstraint.activate([ |
| tableView.topAnchor.constraint(lessThanOrEqualTo: self.topAnchor), |
| tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor), |
| tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor), |
| separator.topAnchor.constraint(equalTo: tableView.bottomAnchor), |
| separator.leadingAnchor.constraint(equalTo: self.leadingAnchor), |
| separator.trailingAnchor.constraint(equalTo: self.trailingAnchor), |
| separator.bottomAnchor.constraint(equalTo: self.bottomAnchor), |
| separator.heightAnchor.constraint(equalToConstant: 2), |
| ]) |
| |
| tableView.reloadData() |
| let currentRingtone = IndexPath(row: 1, section: 0) |
| tableView.selectRow(at: currentRingtone, animated: false, scrollPosition: .top) |
| } |
| |
| func numberOfSections(in tableView: UITableView) -> Int { |
| return 1 |
| } |
| |
| func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
| return ringtones.count |
| } |
| |
| func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { |
| let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) |
| cell.textLabel?.text = ringtones[indexPath.row] |
| cell.indentationLevel = 1 |
| return cell |
| } |
| } |
| |
| // A tableview with intrinsic size that matches its content size. |
| final class AutoSizedTableView: UITableView { |
| override var contentSize: CGSize { |
| didSet { |
| invalidateIntrinsicContentSize() |
| } |
| } |
| |
| override var intrinsicContentSize: CGSize { |
| layoutIfNeeded() |
| return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height) |
| } |
| } |