| // Copyright 2020-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 DialogsTitleImageExampleViewController: 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: [ |
| "Title Icon", |
| "Custom Title Icon", |
| "Title Image - Scaled Down to Fit", |
| "Title Image - Bleeding Edge", |
| "Custom Title 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 = alertController(for: indexPath.row) else { return } |
| self.present(alert, animated: true, completion: nil) |
| } |
| |
| private func alertController(for row: Int) -> MDCAlertController? { |
| switch row { |
| case 0: |
| return alertWithTitleIcon() |
| case 1: |
| return alertWithCustomTitleIcon() |
| case 2: |
| return alertWithScaledToFitImage() |
| case 3: |
| return alertWithScaledBleedingImage() |
| case 4: |
| presentAlertWithCustomTitleView() |
| return nil |
| default: |
| print("No row is selected") |
| return nil |
| } |
| } |
| |
| func alertWithTitleIcon() -> MDCAlertController { |
| let alert = createMDCAlertController(title: "Title Icon") |
| alert.titleIcon = image(named: "outline_lock_black_24pt") |
| alert.applyTheme(withScheme: self.containerScheme) |
| return alert |
| } |
| |
| func alertWithCustomTitleIcon() -> MDCAlertController { |
| let alert = createMDCAlertController(title: "Custom Title Icon") |
| // Custom size. |
| alert.titleIcon = image(named: "baseline_alarm_on_black_48pt") |
| // Custom alignment. |
| alert.titleIconAlignment = .center |
| // Custom insets. |
| if let alertView = alert.view as? MDCAlertControllerView { |
| alertView.titleIconInsets.bottom = 20 |
| } |
| alert.applyTheme(withScheme: self.containerScheme) |
| // Custom color (overriding the theme's title icon color). |
| alert.titleIconTintColor = .orange |
| return alert |
| } |
| |
| func alertWithScaledToFitImage() -> MDCAlertController { |
| let alert = createMDCAlertController(title: "Scaled Title Icon") |
| alert.titleIcon = image(named: "STAY_AMSTERDAM") |
| // Justified alignment size the image to fit the top space of the dialog. Images are scaled down |
| // if needed, but never scaled up. |
| alert.titleIconAlignment = .justified |
| alert.applyTheme(withScheme: self.containerScheme) |
| return alert |
| } |
| |
| func alertWithScaledBleedingImage() -> MDCAlertController { |
| let alert = createMDCAlertController(title: "Scaled to fill Title Icon") |
| alert.titleIcon = image(named: "STAY_AMSTERDAM-WIDE") |
| // Justified alignment size the image to fit the top space of the dialog. |
| alert.titleIconAlignment = .justified |
| if let alertView = alert.view as? MDCAlertControllerView { |
| alertView.titleIconInsets = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0) |
| } |
| alert.applyTheme(withScheme: self.containerScheme) |
| return alert |
| } |
| |
| func presentAlertWithCustomTitleView(animated: Bool = true) { |
| let alert = createMDCAlertController(title: "Custom Title View") |
| |
| // Create a custom view with a centered image and a light background. |
| let view = UIView(frame: CGRect(x: 0, y: 0, width: 160, height: 86)) |
| let alarmView = UIImageView() |
| view.addSubview(alarmView) |
| |
| // Apply theme colors. |
| view.backgroundColor = containerScheme.colorScheme.primaryColor.withAlphaComponent(0.2) |
| alarmView.tintColor = containerScheme.colorScheme.primaryColor |
| |
| // Resize the imageView to fit to the size of the new loaded image. |
| if let alarm = image(named: "baseline_alarm_on_black_48pt") { |
| alarmView.image = alarm |
| alarmView.sizeToFit() |
| } |
| |
| // Sets the customView as the titleIconView. |
| alert.titleIconView = view |
| |
| // Set .justified alignment with 0 insets to ensure the view's color bleeds through to the edge. |
| alert.titleIconAlignment = .justified |
| if let alertView = alert.view as? MDCAlertControllerView { |
| alertView.titleIconInsets = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0) |
| } |
| |
| alert.applyTheme(withScheme: self.containerScheme) |
| |
| if animated { |
| alarmView.alpha = 0 |
| } |
| self.present( |
| alert, animated: animated, |
| completion: { |
| // The view is centered correctly after the alert is presented. |
| alarmView.center = view.center |
| if animated { |
| // Start the image animation after the alert is presetned. |
| alarmView.animateIn() |
| } |
| }) |
| } |
| |
| private func image(named: String) -> UIImage? { |
| let bundle = Bundle(for: DialogsTitleImageExampleViewController.self) |
| return UIImage(named: named, in: bundle, compatibleWith: nil) |
| } |
| |
| private func createMDCAlertController(title: String?) -> MDCAlertController { |
| let alert = MDCAlertController( |
| title: title, |
| message: """ |
| Lorem ipsum dolor sit amet, consectetur adipiscing elit. |
| """) |
| alert.addAction(MDCAlertAction(title: "OK", emphasis: .high, handler: handler)) |
| alert.addAction(MDCAlertAction(title: "Cancel", handler: handler)) |
| |
| // Enable dynamic type. |
| alert.mdc_adjustsFontForContentSizeCategory = true |
| |
| return alert |
| } |
| } |
| |
| // MDCCollectionViewController Data Source |
| extension DialogsTitleImageExampleViewController { |
| |
| 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 DialogsTitleImageExampleViewController { |
| |
| @objc class func catalogMetadata() -> [String: Any] { |
| return [ |
| "breadcrumbs": ["Dialogs", "Title Icon, Image & View"], |
| "primaryDemo": false, |
| "presentable": true, |
| ] |
| } |
| } |
| |
| // MARK: Snapshot Testing by Convention |
| extension DialogsTitleImageExampleViewController { |
| |
| func resetTests() { |
| if presentedViewController != nil { |
| dismiss(animated: false) |
| } |
| } |
| |
| @objc func testTitleIcon() { |
| resetTests() |
| self.present( |
| alertWithTitleIcon(), animated: false, completion: nil) |
| } |
| |
| @objc func testCustomTitleIcon() { |
| resetTests() |
| self.present( |
| alertWithCustomTitleIcon(), animated: false, completion: nil) |
| } |
| |
| @objc func testScaledToFit() { |
| resetTests() |
| self.present( |
| alertWithScaledToFitImage(), animated: false, completion: nil) |
| } |
| |
| @objc func testScaledBleeding() { |
| resetTests() |
| self.present( |
| alertWithScaledBleedingImage(), animated: false, completion: nil) |
| } |
| |
| @objc func testCustomTitleView() { |
| resetTests() |
| presentAlertWithCustomTitleView(animated: false) |
| } |
| } |
| |
| extension UIView { |
| fileprivate func animateIn( |
| duration: TimeInterval = 4, |
| repeating: Bool = true, |
| options: UIView.AnimationOptions = [.curveEaseOut] |
| ) { |
| self.alpha = 1 |
| let initialTransform = self.transform.translatedBy(x: -150, y: 0) |
| self.transform = initialTransform |
| let transform1 = |
| initialTransform |
| .concatenating(CGAffineTransform(translationX: 80, y: 0)) |
| .rotated(by: CGFloat.pi * 0.8) |
| let transform2 = |
| initialTransform |
| .concatenating(CGAffineTransform(translationX: 160, y: 0)) |
| .rotated(by: CGFloat.pi * 1.5) |
| let transform3 = |
| initialTransform |
| .concatenating(CGAffineTransform(translationX: 240, y: 0)) |
| let transform4 = |
| initialTransform |
| .concatenating(CGAffineTransform(translationX: 360, y: 0)) |
| .rotated(by: CGFloat.pi * 0.75) |
| |
| let animationOptions: UInt |
| if repeating { |
| animationOptions = |
| UIView.AnimationOptions.curveLinear.rawValue | UIView.AnimationOptions.repeat.rawValue |
| } else { |
| animationOptions = UIView.AnimationOptions.curveLinear.rawValue |
| } |
| |
| let keyFrameAnimationOptions = UIView.KeyframeAnimationOptions(rawValue: animationOptions) |
| |
| UIView.animateKeyframes( |
| withDuration: duration, delay: 0, |
| options: [keyFrameAnimationOptions, .calculationModeLinear], |
| animations: { |
| UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.14) { // 0.375 |
| self.transform = transform1 |
| } |
| UIView.addKeyframe(withRelativeStartTime: 0.14, relativeDuration: 0.14) { // 0.375 |
| self.transform = transform2 |
| } |
| UIView.addKeyframe(withRelativeStartTime: 0.28, relativeDuration: 0.15) { //0.25) { |
| self.transform = transform3 |
| } |
| UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.15) { //0.25) { |
| self.transform = transform4 |
| } |
| }, completion: nil) |
| } |
| } |