diff --git a/Vocable/Common/Views/GazeableButton.swift b/Vocable/Common/Views/GazeableButton.swift index 3185a8dd..6bdee36e 100644 --- a/Vocable/Common/Views/GazeableButton.swift +++ b/Vocable/Common/Views/GazeableButton.swift @@ -21,6 +21,9 @@ class GazeableButton: UIButton { private var cachedTitleColors = [UIControl.State: UIColor]() private let defaultIBStates = [UIControl.State.normal, .highlighted, .selected, .disabled] + private var trailingAccessoryViewLayoutGuide = UILayoutGuide() + private var trailingAccessoryView: UIView? + var shouldShrinkWhenTouched = true { didSet { updateSelectionAppearance() @@ -112,6 +115,14 @@ class GazeableButton: UIButton { private func commonInit() { setDefaultAppearance() updateContentViews() + addLayoutGuide(trailingAccessoryViewLayoutGuide) + NSLayoutConstraint.activate([ + trailingAccessoryViewLayoutGuide.topAnchor.constraint(equalTo: topAnchor), + trailingAccessoryViewLayoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor), + trailingAccessoryViewLayoutGuide.trailingAnchor.constraint(equalTo: trailingAnchor), + // 8 to match the minimum default content inset (until an independent UIControl subclass is authored) + trailingAccessoryViewLayoutGuide.widthAnchor.constraint(equalToConstant: 8).withPriority(.defaultLow) + ]) } private func setDefaultAppearance() { @@ -121,7 +132,7 @@ class GazeableButton: UIButton { setFillColor(.cellSelectionColor, for: [.selected, .highlighted]) setTitleColor(.collectionViewBackgroundColor, for: .selected) setTitleColor(.collectionViewBackgroundColor, for: [.selected, .highlighted]) - + titleLabel?.numberOfLines = 3 contentEdgeInsets = .init(top: 8, left: 8, bottom: 8, right: 8) layoutMargins = .zero for state in defaultIBStates { @@ -148,11 +159,43 @@ class GazeableButton: UIButton { super.setImage(image, for: state) } - func setTrailingImage(image: UIImage?, offset: CGFloat) { - setImage(image, for: .normal) - imageView?.translatesAutoresizingMaskIntoConstraints = false - imageView?.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true - imageView?.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -offset).isActive = true + func setTrailingAccessoryView(_ view: UIView?, insets: NSDirectionalEdgeInsets) { + + defer { + trailingAccessoryView = view + } + + if let trailingAccessoryView = trailingAccessoryView { + if view == trailingAccessoryView { + return + } + } + + trailingAccessoryView?.removeFromSuperview() + + guard let view = view else { + return + } + + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + + NSLayoutConstraint.activate([ + view.trailingAnchor.constraint(equalTo: trailingAccessoryViewLayoutGuide.trailingAnchor, constant: -insets.trailing), + view.centerYAnchor.constraint(equalTo: trailingAccessoryViewLayoutGuide.centerYAnchor), + view.leadingAnchor.constraint(equalTo: trailingAccessoryViewLayoutGuide.leadingAnchor, constant: insets.leading) + ]) + } + + override func layoutSubviews() { + + let layoutGuideWidth = trailingAccessoryViewLayoutGuide.layoutFrame.width + + if self.contentEdgeInsets.right != layoutGuideWidth { + self.contentEdgeInsets.right = layoutGuideWidth + } + + super.layoutSubviews() } func fillColor(for state: UIControl.State) -> UIColor? { diff --git a/Vocable/Common/VocableCellContentConfiguration.swift b/Vocable/Common/VocableCellContentConfiguration.swift index c809eeef..6645f4b0 100644 --- a/Vocable/Common/VocableCellContentConfiguration.swift +++ b/Vocable/Common/VocableCellContentConfiguration.swift @@ -8,11 +8,17 @@ import UIKit -enum CellDisclosureStyle: Equatable { - case none - case disclosure - case link - case toggle(Bool) +extension VocableListCellContentView.Configuration { + struct TrailingAccessory: Equatable { + + let customView: UIView + + private static func systemImage(_ imageName: String) -> TrailingAccessory { + TrailingAccessory(customView: UIImageView(image: UIImage(systemName: imageName)?.applyingSymbolConfiguration(.init(pointSize: 36, weight: .bold)))) + } + + static var disclosureIndicator: TrailingAccessory { .systemImage("chevron.right") } + } } extension VocableListCellContentView.Configuration { @@ -58,15 +64,15 @@ extension VocableListCellContentView { var attributedText: NSAttributedString var accessories: [AccessoryAction] - var disclosureStyle: CellDisclosureStyle? + var trailingAccessory: TrailingAccessory? var primaryAction: () -> Void - init(attributedText: NSAttributedString, accessories: [AccessoryAction] = [], disclosureStyle: CellDisclosureStyle = .none, cellAction: @escaping () -> Void) { + init(attributedText: NSAttributedString, accessories: [AccessoryAction] = [], trailingAccessory: TrailingAccessory? = nil, primaryAction: @escaping () -> Void) { self.attributedText = attributedText self.accessories = accessories - self.disclosureStyle = disclosureStyle - self.primaryAction = cellAction + self.trailingAccessory = trailingAccessory + self.primaryAction = primaryAction } func makeContentView() -> UIView & UIContentView { @@ -78,7 +84,7 @@ extension VocableListCellContentView { } static func == (lhs: VocableListCellContentView.Configuration, rhs: VocableListCellContentView.Configuration) -> Bool { - return lhs.attributedText.isEqual(to: rhs.attributedText) && lhs.accessories.elementsEqual(rhs.accessories) && lhs.disclosureStyle == rhs.disclosureStyle + return lhs.attributedText.isEqual(to: rhs.attributedText) && lhs.accessories.elementsEqual(rhs.accessories) && lhs.trailingAccessory == rhs.trailingAccessory } } } diff --git a/Vocable/Common/VocableListCellContentView.swift b/Vocable/Common/VocableListCellContentView.swift index f6f936ac..2172ae32 100644 --- a/Vocable/Common/VocableListCellContentView.swift +++ b/Vocable/Common/VocableListCellContentView.swift @@ -93,7 +93,7 @@ final class VocableListCellContentView: UIView, UIContentView { primaryLabelButton.contentHorizontalAlignment = .left primaryLabelButton.setAttributedTitle(configuration?.attributedText, for: .normal) primaryLabelButton.addTarget(self, action: #selector(handlePrimaryActionSelection(_:)), for: .primaryActionTriggered) - primaryLabelButton.setTrailingImage(image: UIImage(systemName: "chevron.right"), offset: 24) // Must be rectified + primaryLabelButton.setTrailingAccessoryView(configuration?.trailingAccessory?.customView, insets: .init(top: 0, leading: 12, bottom: 0, trailing: 12)) } private func updateAccessoryButtons(with configuration: Configuration?) { diff --git a/Vocable/Features/Settings/EditPhrases/EditPhrasesViewController.swift b/Vocable/Features/Settings/EditPhrases/EditPhrasesViewController.swift index e0c645b0..66332ffa 100644 --- a/Vocable/Features/Settings/EditPhrases/EditPhrasesViewController.swift +++ b/Vocable/Features/Settings/EditPhrases/EditPhrasesViewController.swift @@ -278,7 +278,7 @@ private extension EditPhrasesViewController { cell.contentConfiguration = VocableListCellContentView.Configuration(attributedText: attributedText, accessories: [deleteAction], - disclosureStyle: .none) { [weak self] in + trailingAccessory: .disclosureIndicator) { [weak self] in self?.presentEditorForPhrase(at: indexPath) } }