// // RoundedCellViewHeader.swift // App // // Created by me on 16/06/2020. // Copyright © 2020. All rights reserved. // import UIKit import SnapKit class RoundedCellViewHeader: UIView { enum LeftViewType { case none, textSizeSlider((Float) -> ()), title(String), titleAndIcon(String, UIImage) var shouldExpandHorizontally: Bool { switch self { case .title(_), .titleAndIcon(_, _): return true default: return false } } } // MARK: - Attributes /// The title label of the cell view. private lazy var titleLabel = CustomLabel(font: Fonts.body_small_bold) private lazy var iconImageView: UIImageView = { let iconImageView = UIImageView() iconImageView.contentMode = .scaleAspectFit return iconImageView }() /// The (optional) text size slider. private lazy var textSizeSlider: TextSizeSlider = { let textSizeSlider = TextSizeSlider(value: WritePreferencesManager.currentFontSizeAsClampedValue) textSizeSlider.addTarget(self, action: #selector(textSizeSliderChanged), for: .valueChanged) return textSizeSlider }() private lazy var textSizeSliderWithIcons = TextSizeSliderWithIcons(slider: textSizeSlider) /// The help button. private lazy var helpButton: UIButton = { let helpButton = IconButton(icon: R.image.info()!) helpButton.layer.cornerRadius = 18 helpButton.addTarget(self, action: #selector(helpButtonPressed), for: .touchUpInside) helpButton.accessibilityIdentifier = "Help" return helpButton }() /// The helper text label. private lazy var helperDescriptionLabel: UILabel = { let helperDescriptionLabel = CustomLabel(font: Fonts.body_small_medium) return helperDescriptionLabel }() private lazy var subtitleLabel = CustomLabel(font: Fonts.body_small_medium, style: .transparent) /// Header constraints for expanding private var closedHeaderConstraint: Constraint? private var openHeaderConstraint: Constraint? /// Stores if the header should be black when opened. private var shouldMakeHeaderBlack: Bool /// Tracks if the cell view has a help feature (help button and description) or not. private var hasHelp: Bool /// Tracks if the help description view is open or not. private var isHelpOpen = false /// The callback to call when the text slider changes. private var textSliderCallback: ((Float) -> ())? private(set) var leftViewType: LeftViewType var subtitle: String { get { return subtitleLabel.text ?? "" } set { subtitleLabel.text = newValue } } // MARK: - Setup init(leftViewType: LeftViewType, leftViewIsTransparent: Bool = true, subtitle: String? = nil, helperText: String?, shouldMakeHeaderBlack: Bool = false) { self.hasHelp = helperText != nil self.leftViewType = leftViewType self.shouldMakeHeaderBlack = shouldMakeHeaderBlack super.init(frame: .zero) let insets = Spaces.roundedCellInsets helperDescriptionLabel.text = helperText clipsToBounds = true layer.cornerRadius = 16 // this layout guide allows to vertically center content while having an offset from the top (useful for internal header) let layoutGuide = UILayoutGuide() addLayoutGuide(layoutGuide) layoutGuide.snp.makeConstraints { make in make.top.equalToSuperview().inset(insets.top-11) make.left.right.equalToSuperview() if subtitle == nil { closedHeaderConstraint = make.bottom.equalToSuperview().constraint } make.height.equalTo(36) } if let subtitle = subtitle { subtitleLabel.text = subtitle addSubview(subtitleLabel) subtitleLabel.snp.makeConstraints { make in make.left.equalToSuperview().inset(insets.left) make.right.equalToSuperview().inset(insets.right) make.top.equalTo(layoutGuide.snp.bottom).offset(2) closedHeaderConstraint = make.bottom.equalToSuperview().inset(10).constraint } } // help button and description (we don't add it if there is a subtitle) if hasHelp && subtitle == nil { addSubview(helpButton) helpButton.snp.makeConstraints { make in make.width.height.equalTo(36) make.right.equalTo(layoutGuide).inset(insets.right-11) make.top.bottom.equalTo(layoutGuide) } addSubview(helperDescriptionLabel) helperDescriptionLabel.snp.makeConstraints { make in make.top.equalTo(layoutGuide.snp.bottom).offset(16) openHeaderConstraint = make.bottom.equalToSuperview().inset(20).constraint make.left.equalToSuperview().inset(insets.left) make.right.equalToSuperview().inset(insets.right) } helperDescriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) // prevent label from being shrinked } // left view (uilabel or text size slider) var leftView: UIView? = nil switch leftViewType { case .textSizeSlider(let callback): leftView = textSizeSliderWithIcons textSliderCallback = callback case .title(let title): leftView = titleLabel titleLabel.text = title case .titleAndIcon(let title, let icon): leftView = iconAndTitle(title: title, icon: icon) case .none: break } if let view = leftView { view.alpha = leftViewIsTransparent ? Constants.transparentAlpha : 1.0 addSubview(view) view.snp.makeConstraints { make in make.left.equalTo(layoutGuide).inset(insets.left) make.top.bottom.equalTo(layoutGuide) if leftViewType.shouldExpandHorizontally { // extend width if label if hasHelp { make.right.equalTo(helpButton.snp.left).offset(-8) } else { make.right.equalTo(layoutGuide).inset(insets.right) } } } } setHelp(open: false, triggerLayout: false) } /// This should not be called, since we are not using Storyboard or XIB. @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func iconAndTitle(title: String, icon: UIImage) -> UIView { let view = UIView() view.addSubview(iconImageView) iconImageView.image = icon iconImageView.snp.makeConstraints { make in make.height.width.equalTo(20) make.centerY.equalToSuperview() make.height.lessThanOrEqualToSuperview() make.left.equalToSuperview() } iconImageView.fixImageSizeCompressionHugging() view.addSubview(titleLabel) titleLabel.text = title titleLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.height.lessThanOrEqualToSuperview() make.left.equalTo(iconImageView.snp.right).offset(10) make.right.equalToSuperview() } return view } // MARK: - Methods /// Called when the text size slider changes. @objc func textSizeSliderChanged() { textSliderCallback?(textSizeSlider.value) } /// Called when the help button is pressed. @objc func helpButtonPressed() { if isHelpOpen { self.setHelp(open: !self.isHelpOpen) } else { UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: { self.setHelp(open: !self.isHelpOpen) }, completion: nil) } } /// Opens or closes the helper description view. func setHelp(open: Bool, triggerLayout: Bool = true) { guard hasHelp || !open else { return } isHelpOpen = open if shouldMakeHeaderBlack { backgroundColor = open ? UIColor(white: 0, alpha: 0.4) : .clear } // titleLabel.transform = open ? CGAffineTransform(translationX: 0, y: 7) : .identity helperDescriptionLabel.alpha = open ? 1 : 0 helpButton.alpha = open ? 1 : Constants.transparentAlpha helpButton.backgroundColor = open ? .white : .clear helpButton.tintColor = open ? .black : .white // Update constraints if open { closedHeaderConstraint?.deactivate() openHeaderConstraint?.activate() } else { openHeaderConstraint?.deactivate() closedHeaderConstraint?.activate() } if triggerLayout { (superview as? RoundedCellView)?.layoutIfNeeded() } } }