123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- /*****************************************************************************
- * ActionSheetCell.swift
- *
- * Copyright © 2018 VLC authors and VideoLAN
- * Copyright © 2018 Videolabs
- *
- * Authors: Soomin Lee <bubu@mikan.io>
- *
- * Refer to the COPYING file of the official project for license.
- *****************************************************************************/
- enum ActionSheetCellAccessoryType {
- case toggleSwitch
- case checkmark
- case disclosureChevron
- }
- class ActionSheetCellImageView: UIImageView {
- override var image: UIImage? {
- didSet {
- super.image = image
- isHidden = false
- }
- }
- override init(image: UIImage? = nil) {
- super.init(image: image)
- isHidden = true
- }
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- }
- /// Model that determines the layout presentation of the ActionSheetCell.
- @objc (VLCActionSheetCellModel)
- @objcMembers class ActionSheetCellModel: NSObject {
- var title: String
- var iconImage: UIImage?
- var viewToPresent: UIView?
- var accessoryType: ActionSheetCellAccessoryType
- var cellIdentifier: MediaPlayerActionSheetCellIdentifier?
-
- init(
- title: String,
- imageIdentifier: String,
- accessoryType: ActionSheetCellAccessoryType = .checkmark,
- viewToPresent: UIView? = nil,
- cellIdentifier: MediaPlayerActionSheetCellIdentifier? = nil) {
- self.title = title
- iconImage = UIImage(named: imageIdentifier)?.withRenderingMode(.alwaysTemplate)
- self.accessoryType = accessoryType
- self.viewToPresent = viewToPresent
- self.cellIdentifier = cellIdentifier
- }
- }
- @objc (VLCActionSheetCellDelegate)
- protocol ActionSheetCellDelegate {
- func actionSheetCellShouldUpdateColors() -> Bool
- func actionSheetCellDidToggleSwitch(for cell: ActionSheetCell, state: Bool)
- }
- @objc(VLCActionSheetCell)
- class ActionSheetCell: UICollectionViewCell {
- /// UIViewController to present on cell selection
- weak var viewToPresent: UIView?
- /// Rightmost accessory view that the cell should use. Default `checkmark`.
- /// If `viewControllerToPresent` is set, defaults to `disclosureChevron`, otherwise `checkmark` is main default.
- private(set) var accessoryView = UIView ()
- weak var delegate: ActionSheetCellDelegate?
- var identifier: MediaPlayerActionSheetCellIdentifier?
- @objc static var identifier: String {
- return String(describing: self)
- }
- override var isSelected: Bool {
- didSet {
- updateColors()
- // only checkmarks should be hidden if they arent selected
- if accessoryType == .checkmark {
- accessoryView.isHidden = !isSelected
- }
- }
- }
- let icon: ActionSheetCellImageView = {
- let icon = ActionSheetCellImageView()
- icon.translatesAutoresizingMaskIntoConstraints = false
- icon.setContentHuggingPriority(.required, for: .horizontal)
- icon.contentMode = .scaleAspectFit
- return icon
- }()
- let name: UILabel = {
- let name = UILabel()
- name.textColor = PresentationTheme.current.colors.cellTextColor
- name.font = UIFont.systemFont(ofSize: 15)
- name.translatesAutoresizingMaskIntoConstraints = false
- return name
- }()
- lazy private var accessoryTypeImageView: UIImageView = {
- let imageView = UIImageView()
- imageView.contentMode = .scaleAspectFit
- imageView.backgroundColor = .none
- imageView.setContentHuggingPriority(.required, for: .horizontal)
- imageView.translatesAutoresizingMaskIntoConstraints = false
- return imageView
- }()
-
- lazy private var toggleSwitch: UISwitch = {
- let toggleSwitch = UISwitch()
- toggleSwitch.onTintColor = .orange
- toggleSwitch.translatesAutoresizingMaskIntoConstraints = false
- toggleSwitch.addTarget(self, action: #selector(switchToggled(_:)), for: .valueChanged)
- return toggleSwitch
- }()
- private(set) var accessoryType: ActionSheetCellAccessoryType = .checkmark {
- didSet {
- switch accessoryType {
- case .checkmark:
- accessoryTypeImageView.image = UIImage(named: "checkmark")?.withRenderingMode(.alwaysTemplate)
- add(view: accessoryTypeImageView, to: accessoryView)
- case .disclosureChevron:
- accessoryTypeImageView.image = UIImage(named: "disclosureChevron")?.withRenderingMode(.alwaysTemplate)
- add(view: accessoryTypeImageView, to: accessoryView)
- case .toggleSwitch:
- add(view: toggleSwitch, to: accessoryView)
- }
- if accessoryType == .checkmark {
- accessoryView.isHidden = !isSelected
- } else {
- accessoryView.isHidden = false
- }
- }
- }
- let stackView: UIStackView = {
- let stackView = UIStackView()
- stackView.spacing = 15.0
- stackView.axis = .horizontal
- stackView.alignment = .center
- stackView.translatesAutoresizingMaskIntoConstraints = false
- return stackView
- }()
- override init(frame: CGRect) {
- super.init(frame: frame)
- setupViews()
- }
- required init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- setupViews()
- }
-
- convenience init(withCellModel model: ActionSheetCellModel) {
- self.init()
- configure(withModel: model)
- setupViews()
- }
- private func updateColors() {
- let shouldUpdateColors = delegate?.actionSheetCellShouldUpdateColors() ?? true
- let colors = PresentationTheme.current.colors
- if shouldUpdateColors {
- name.textColor = isSelected ? colors.orangeUI : colors.cellTextColor
- tintColor = isSelected ? colors.orangeUI : colors.cellDetailTextColor
- }
- if accessoryType != .toggleSwitch {
- accessoryView.tintColor = isSelected && accessoryType == .checkmark ? colors.orangeUI : colors.cellDetailTextColor
- }
- }
- @objc private func switchToggled(_ sender: UISwitch) {
- delegate?.actionSheetCellDidToggleSwitch(for: self, state: sender.isOn)
- }
- override func prepareForReuse() {
- super.prepareForReuse()
- toggleSwitch.removeFromSuperview()
- accessoryType = .checkmark
- updateColors()
- }
- private func add(view: UIView, to parentView: UIView) {
- view.translatesAutoresizingMaskIntoConstraints = false
- parentView.subviews.forEach { $0.removeFromSuperview() }
- parentView.addSubview(view)
- NSLayoutConstraint.activate([
- view.topAnchor.constraint(equalTo: parentView.topAnchor),
- view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
- view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
- view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor)
- ])
- }
-
- func configure(withModel model: ActionSheetCellModel) {
- if model.accessoryType == .disclosureChevron {
- assert(model.viewToPresent != nil, "ActionSheetCell: Cell with disclosure chevron must have accompanying presentable UIView")
- }
- name.text = model.title
- icon.image = model.iconImage
- viewToPresent = model.viewToPresent
- identifier = model.cellIdentifier
- // disclosure chevron is set as the default accessoryView if a viewController is present
- accessoryType = model.viewToPresent != nil ? .disclosureChevron : model.accessoryType
- }
- func setToggleSwitch(state: Bool) {
- if accessoryType == .toggleSwitch {
- toggleSwitch.isOn = state
- }
- }
- private func setupViews() {
- backgroundColor = PresentationTheme.current.colors.background
- stackView.addArrangedSubview(icon)
- stackView.addArrangedSubview(name)
- stackView.addArrangedSubview(accessoryView)
- addSubview(stackView)
- // property observers only trigger after the first time the values are set.
- // allow the didSet to set the checkmark image
- accessoryType = .checkmark
- var guide: LayoutAnchorContainer = self
- if #available(iOS 11.0, *) {
- guide = safeAreaLayoutGuide
- }
- NSLayoutConstraint.activate([
- stackView.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 20),
- stackView.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: -20),
- stackView.heightAnchor.constraint(equalTo: heightAnchor),
- stackView.topAnchor.constraint(equalTo: topAnchor)
- ])
- }
- }
|