// Copyright 2018-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.MaterialCards import MaterialComponents.MaterialShapeLibrary class CardView: MDCCard { let contentView = UIView() let label = UILabel() let imageView = UIImageView() var imageWidthConstraint: NSLayoutConstraint! var shouldUpdateConstraints = true override init(frame: CGRect) { super.init(frame: frame) commonCardViewInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonCardViewInit() } func commonCardViewInit() { self.layoutMargins = .zero contentView.layoutMargins = .zero contentView.translatesAutoresizingMaskIntoConstraints = false contentView.isUserInteractionEnabled = false self.addSubview(contentView) label.text = "abcde fghi jklm nopq rstuv wxyz abcd efgh ijkl mnop qrst uvw xyz abcde fghi jkl" + "nopq rstuv wxyz abcd efgh ijkl mnop qrst uvw xyz abcde fghi jklm nopq rstuv wxyz abcdefg" label.numberOfLines = 0 label.translatesAutoresizingMaskIntoConstraints = false self.contentView.addSubview(label) imageView.clipsToBounds = true imageView.contentMode = .scaleAspectFill imageView.translatesAutoresizingMaskIntoConstraints = false let bundle = Bundle(for: ShapedCardViewController.self) imageView.image = UIImage(named: "sample-image", in: bundle, compatibleWith: nil) self.contentView.addSubview(imageView) setupConstraints() } func setupConstraints() { contentView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true contentView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true contentView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true let margins = self.contentView.layoutMarginsGuide label.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true label.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true imageView.centerXAnchor.constraint(equalTo: margins.centerXAnchor).isActive = true imageView.topAnchor.constraint(equalTo: self.contentView.topAnchor).isActive = true label.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor).isActive = true imageView.bottomAnchor.constraint(equalTo: label.topAnchor).isActive = true imageView.heightAnchor.constraint(equalTo: label.heightAnchor, multiplier: 1).isActive = true imageWidthConstraint = imageView.widthAnchor.constraint(equalToConstant: 0) imageWidthConstraint.isActive = true } override func layoutSubviews() { super.layoutSubviews() let shapeLayer = (self.layer as! MDCShapedShadowLayer).shapeLayer contentView.layer.mask = shapeLayer imageWidthConstraint.constant = shapeLayer.path!.boundingBox.size.width } } class ShapedCardViewController: UIViewController { var card = CardView() var primarySlider = UISlider() var secondarySlider = UISlider() override func viewDidLoad() { view.backgroundColor = .white super.viewDidLoad() initialShape() view.addSubview(card) view.addSubview(primarySlider) view.addSubview(secondarySlider) card.translatesAutoresizingMaskIntoConstraints = false card.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: 20).isActive = true card.bottomAnchor.constraint(equalTo: primarySlider.topAnchor, constant: -20).isActive = true card.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true card.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor).isActive = true primarySlider.translatesAutoresizingMaskIntoConstraints = false primarySlider.bottomAnchor.constraint( equalTo: secondarySlider.topAnchor, constant: -20 ).isActive = true primarySlider.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true primarySlider.widthAnchor.constraint( equalTo: view.widthAnchor, multiplier: 0.5, constant: 0 ).isActive = true primarySlider.addTarget( self, action: #selector(didChangeSliderValue(slider:)), for: .valueChanged) secondarySlider.translatesAutoresizingMaskIntoConstraints = false secondarySlider.bottomAnchor.constraint( equalTo: view.bottomAnchor, constant: -20 ).isActive = true secondarySlider.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true secondarySlider.widthAnchor.constraint( equalTo: view.widthAnchor, multiplier: 0.5, constant: 0 ).isActive = true secondarySlider.addTarget( self, action: #selector(didChangeSliderValue(slider:)), for: .valueChanged) let barButton = UIBarButtonItem( title: "Change Shape", style: .plain, target: self, action: #selector(changeShape)) self.navigationItem.rightBarButtonItem = barButton } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() primarySlider.maximumValue = Float(min(card.bounds.width, card.bounds.height) / 2) primarySlider.sendActions(for: .valueChanged) secondarySlider.maximumValue = Float(min(card.bounds.width, card.bounds.height) / 2) secondarySlider.sendActions(for: .valueChanged) } @objc func didChangeSliderValue(slider: UISlider) { if let shape = card.shapeGenerator as? MDCRectangleShapeGenerator { if slider == primarySlider { let cutCornerTreatment = MDCCutCornerTreatment(cut: CGFloat(slider.value)) shape.setCorners(cutCornerTreatment) } else if slider == secondarySlider { let triangleEdgeTreatment = MDCTriangleEdgeTreatment( size: CGFloat(slider.value), style: MDCTriangleEdgeStyleCut) shape.setEdges(triangleEdgeTreatment) } card.setNeedsLayout() } } @objc func changeShape() { primarySlider.isHidden = true secondarySlider.isHidden = true switch card.shapeGenerator { case is MDCRectangleShapeGenerator: let shapeGenerator = MDCCurvedRectShapeGenerator( cornerSize: CGSize( width: 50, height: 100)) card.shapeGenerator = shapeGenerator card.contentView.layoutMargins = UIEdgeInsets(top: 0, left: 40, bottom: 0, right: 40) case is MDCCurvedRectShapeGenerator: let shapeGenerator = MDCPillShapeGenerator() card.shapeGenerator = shapeGenerator card.contentView.layoutMargins = UIEdgeInsets(top: 0, left: 80, bottom: 0, right: 80) case is MDCPillShapeGenerator: let shapeGenerator = MDCSlantedRectShapeGenerator() shapeGenerator.slant = 40 card.shapeGenerator = shapeGenerator card.contentView.layoutMargins = UIEdgeInsets(top: 0, left: 40, bottom: 0, right: 40) case is MDCSlantedRectShapeGenerator: fallthrough default: initialShape() } card.setNeedsLayout() } func initialShape() { primarySlider.isHidden = false secondarySlider.isHidden = false let shapeGenerator = MDCRectangleShapeGenerator() let cutCornerTreatment = MDCCutCornerTreatment(cut: 0) shapeGenerator.setCorners(cutCornerTreatment) let triangleEdgeTreatment = MDCTriangleEdgeTreatment(size: 0, style: MDCTriangleEdgeStyleCut) shapeGenerator.setEdges(triangleEdgeTreatment) card.shapeGenerator = shapeGenerator card.contentView.layoutMargins = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5) } } extension ShapedCardViewController { @objc class func catalogMetadata() -> [String: Any] { return [ "breadcrumbs": ["Cards", "Shaped Card"], "primaryDemo": false, "presentable": false, ] } }