// // MenuCell.swift // Privado // // Created by Viktor on 16.07.2020. // Copyright © 2020 Privado LLC. All rights reserved. // import Foundation import UIKit protocol MenuCellInput { func check(set checked: Bool) func switched(set switched: Bool) } protocol MenuCellOutput { func switched(isChecked: Bool) } class MenuCell: UITableViewCell { struct Settings { enum Label { case one case details } enum Detail { case checkmark case details case none } enum Rightside { case detail case radio case none } let label: Label let detail: Detail let rightSide: Rightside } var settings: Settings? { didSet { for view in self.contentView.subviews { view.removeFromSuperview() } guard let preference = settings else { return } // rightside. Set it first because of fix width if preference.rightSide == .detail { self.prepareDisclosureArrow() } else if preference.rightSide == .radio { self.prepareRadio() } // detail if preference.detail == .details { self.prepareLabelDetail() } else if preference.detail == .checkmark { self.prepareCheckmark() } // label: two cases - One label on the center and Two labels (main and detail) if preference.label == .one { self.prepareLabelMain() } else { self.prepareTwoLineLabel() } } } var labelString: String? { didSet { self.labelMain.text = self.labelString } } var detailLabelString: String? { didSet { self.dLabel.text = self.detailLabelString } } var twoLineMainString: String? { didSet { self.twoLineMainLabel.text = self.twoLineMainString } } var twoLineDetailString: String? { didSet { self.twoLineDetailLabel.text = self.twoLineDetailString } } // MARK: - Constraint's li'l helpers fileprivate var customTrailingAnchor = NSLayoutXAxisAnchor() // RideSide UI fileprivate let rsDisclosureArrow: UIImageView = { let imageView = UIImageView() let image = UIImage(imageLiteralResourceName: Constants.disclosureImageName) imageView.image = image imageView.translatesAutoresizingMaskIntoConstraints = false return imageView }() fileprivate let rsRadioButton: UISwitch = { let radio = UISwitch() radio.translatesAutoresizingMaskIntoConstraints = false radio.isUserInteractionEnabled = false return radio }() // Detail UI fileprivate let dLabel: UILabel = { let label = UILabel() label.font = Constants.detailLabelFont label.textColor = Constants.detailTextColor label.textAlignment = .right label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 0 return label }() fileprivate let dCheckMark: UIImageView = { let imageView = UIImageView() let image = UIImage(imageLiteralResourceName: Constants.checkmarkImageName) imageView.image = image imageView.translatesAutoresizingMaskIntoConstraints = false return imageView }() fileprivate let labelMain: UILabel = { let label = UILabel() label.font = Constants.mainLabelFont label.textColor = Constants.mainTextColor label.textAlignment = .left label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 0 return label }() fileprivate let twoLineMainLabel: UILabel = { let label = UILabel() label.font = Constants.twoLineMainFont label.textColor = .white label.textAlignment = .left label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 0 return label }() fileprivate let twoLineDetailLabel: UILabel = { let label = UILabel() label.font = Constants.twoLineDetailFont label.numberOfLines = 0 label.textAlignment = .left label.translatesAutoresizingMaskIntoConstraints = false label.textColor = Constants.twoLineDetailColor return label }() fileprivate var content: UIView? var output: MenuCellOutput? private enum Constants { // shared static let contentColor = UIColor(rgb: 0x030f48) // main static let tm = UIImage(named: "tm_wireguard") static let preview = UIImage(named: "preview") static let previewLeading: CGFloat = 113 static let mainLabelFont = UIFont(name: "SFProText-Regular", size: 16) static let mainTextColor: UIColor = .white static let mainLabelLeadingOffset: CGFloat = 16 static let mainLabelTop: CGFloat = 12 static let mainLabelBottom: CGFloat = 11 static let twoLineTop: CGFloat = 12 static let twoLineDistance: CGFloat = 2 static let twoLineBottom: CGFloat = 16 static let twoLineMainFont = UIFont(name: "SFProText-Regular", size: 16) static let twoLineDetailFont = UIFont(name: "SFProText-Regular", size: 12) static let twoLineDetailColor = UIColor(red: 122, green: 134, blue: 190) // detail static let detailTextColor = UIColor(rgb: 0x7a86be) static let detailLabelFont = UIFont(name: "SFProText-Regular", size: 16) static let detailLabelTrailingOffset: CGFloat = 16 static let detailLabelMaxWidth: CGFloat = 230 static let detailLabelTop: CGFloat = 12 static let detailLabelBottom: CGFloat = 11 // rightside static let disclosureImageName = "preference_arrow" static let disclosureTrailingOffset: CGFloat = 16 static let checkmarkImageName = "preference_checkmark" static let checkmarkTrailingOffset: CGFloat = 16 static let radioTrailing: CGFloat = 16 } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.backgroundColor = .clear self.selectionStyle = .none self.content = self.createContent() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc func radioClick() { self.rsRadioButton.isOn = !self.rsRadioButton.isOn self.output?.switched(isChecked: self.rsRadioButton.isOn) } var isEnabled: Bool = true { didSet { self.isUserInteractionEnabled = self.isEnabled self.content?.alpha = self.isEnabled ? 1.0 : 0.5 } } } // MARK: - UI extension MenuCell { private func createContent() -> UIView { let content = UIView() content.backgroundColor = Constants.contentColor content.translatesAutoresizingMaskIntoConstraints = false self.addSubview(content) NSLayoutConstraint.activate([ content.leadingAnchor.constraint(equalTo: self.leadingAnchor), content.trailingAnchor.constraint(equalTo: self.trailingAnchor), content.topAnchor.constraint(equalTo: self.topAnchor), content.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -2) ]) self.customTrailingAnchor = content.trailingAnchor return content } func setPreview() { self.preparePreview() } // MARK: - Main labels UI private func preparePreview() { guard settings.isExist else { return } guard let content = self.content else { return } let previewImage = UIImageView(image: Constants.preview) previewImage.translatesAutoresizingMaskIntoConstraints = false content.addSubview(previewImage) NSLayoutConstraint.activate([ previewImage.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: Constants.previewLeading), previewImage.topAnchor.constraint(equalTo: content.topAnchor, constant: Constants.mainLabelTop) ]) } private func prepareLabelMain() { guard let content = self.content else { return } content.addSubview(self.labelMain) NSLayoutConstraint.activate([ self.labelMain.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: Constants.mainLabelLeadingOffset), self.labelMain.trailingAnchor.constraint(equalTo: self.customTrailingAnchor), self.labelMain.topAnchor.constraint(equalTo: content.topAnchor, constant: Constants.mainLabelTop), self.labelMain.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -Constants.mainLabelBottom) ]) } private func prepareTwoLineLabel() { guard let content = self.content else { return } content.addSubview(self.twoLineMainLabel) content.addSubview(self.twoLineDetailLabel) NSLayoutConstraint.activate([ self.twoLineMainLabel.topAnchor.constraint(equalTo: content.topAnchor, constant: Constants.twoLineTop), self.twoLineMainLabel.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: Constants.mainLabelLeadingOffset), self.twoLineMainLabel.trailingAnchor.constraint(equalTo: self.customTrailingAnchor), self.twoLineMainLabel.bottomAnchor.constraint(equalTo: self.twoLineDetailLabel.topAnchor, constant: -Constants.twoLineDistance), self.twoLineDetailLabel.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: Constants.mainLabelLeadingOffset), self.twoLineDetailLabel.trailingAnchor.constraint(equalTo: self.customTrailingAnchor), self.twoLineDetailLabel.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -Constants.twoLineBottom) ]) } private func prepareLabelDetail() { guard let content = self.content else { return } content.addSubview(self.dLabel) let trailingConstraint: NSLayoutConstraint trailingConstraint = self.dLabel.trailingAnchor.constraint(equalTo: self.customTrailingAnchor, constant: -Constants.detailLabelTrailingOffset) NSLayoutConstraint.activate([ self.dLabel.widthAnchor.constraint(lessThanOrEqualToConstant: Constants.detailLabelMaxWidth), trailingConstraint, self.dLabel.topAnchor.constraint(equalTo: content.topAnchor, constant: Constants.detailLabelTop), self.dLabel.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -Constants.detailLabelBottom) ]) self.customTrailingAnchor = self.dLabel.leadingAnchor } private func prepareCheckmark() { guard let content = self.content else { return } content.addSubview(self.dCheckMark) NSLayoutConstraint.activate([ self.dCheckMark.widthAnchor.constraint(equalToConstant: self.dCheckMark.image?.size.width ?? 0), self.dCheckMark.heightAnchor.constraint(equalToConstant: self.dCheckMark.image?.size.height ?? 0), self.dCheckMark.centerYAnchor.constraint(equalTo: content.centerYAnchor), self.dCheckMark.trailingAnchor.constraint(equalTo: self.customTrailingAnchor, constant: -Constants.checkmarkTrailingOffset) ]) self.customTrailingAnchor = self.dCheckMark.leadingAnchor self.dCheckMark.isHidden = true } // MARK: - RightSide private func prepareDisclosureArrow() { guard let content = self.content else { return } content.addSubview(self.rsDisclosureArrow) let width = self.rsDisclosureArrow.image?.size.width ?? 0 let height = self.rsDisclosureArrow.image?.size.height ?? 0 NSLayoutConstraint.activate([ self.rsDisclosureArrow.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -Constants.disclosureTrailingOffset), self.rsDisclosureArrow.centerYAnchor.constraint(equalTo: content.centerYAnchor), self.rsDisclosureArrow.widthAnchor.constraint(equalToConstant: width), self.rsDisclosureArrow.heightAnchor.constraint(equalToConstant: height) ]) self.customTrailingAnchor = self.rsDisclosureArrow.leadingAnchor } private func prepareRadio() { guard let content = self.content else { return } content.addSubview(self.rsRadioButton) NSLayoutConstraint.activate([ self.rsRadioButton.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -Constants.disclosureTrailingOffset), self.rsRadioButton.centerYAnchor.constraint(equalTo: content.centerYAnchor) ]) let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false // strange and non-logical if #available(iOS 14.0, *) { self.contentView.addSubview(button) } else { content.addSubview(button) } button.addTarget(self, action: #selector(radioClick), for: .touchUpInside) NSLayoutConstraint.activate([ button.topAnchor.constraint(equalTo: self.contentView.topAnchor), button.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor), button.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor), button.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor) ]) self.bringSubviewToFront(button) self.customTrailingAnchor = self.rsRadioButton.leadingAnchor } } // MARK: - LogOut class MenuLogoutCell: MenuCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.labelMain.textColor = UIColor(red: 228, green: 59, blue: 105) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension MenuCell: MenuCellInput { func check(set checked: Bool) { guard let settings = self.settings else { return } if settings.detail != .checkmark { return } self.dCheckMark.isHidden = !checked } func switched(set switched: Bool) { guard let settings = self.settings else { return } if settings.rightSide != .radio { return } self.rsRadioButton.isOn = switched } }