// // SettingsHeaderView.swift // MullvadVPN // // Created by Jon Petersson on 2023-04-06. // Copyright © 2025 Mullvad VPN AB. All rights reserved. // import UIKit class SettingsHeaderView: UITableViewHeaderFooterView { typealias InfoButtonHandler = () -> Void typealias CollapseHandler = (SettingsHeaderView) -> Void let titleLabel: UILabel = { let titleLabel = UILabel() titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.font = .mullvadSmallSemiBold titleLabel.adjustsFontForContentSizeCategory = true titleLabel.textColor = UIColor.Cell.titleTextColor titleLabel.numberOfLines = 0 return titleLabel }() let infoButton: UIButton = { let button = UIButton(type: .custom) button.setAccessibilityIdentifier(.infoButton) button.adjustsImageSizeForAccessibilityContentSizeCategory = true button.tintColor = .white button.setImage(UIImage.Buttons.info, for: .normal) return button }() let collapseButton: UIButton = { let button = UIButton(type: .custom) button.adjustsImageSizeForAccessibilityContentSizeCategory = true button.setAccessibilityIdentifier(.expandButton) button.tintColor = .white return button }() var isExpanded = false { didSet { updateCollapseImage() updateAccessibilityCustomActions() } } var accessibilityCustomActionName = "" { didSet { updateAccessibilityCustomActions() } } var didCollapseHandler: CollapseHandler? { didSet { collapseButton.isHidden = didCollapseHandler == nil } } var infoButtonHandler: InfoButtonHandler? { didSet { infoButton.isHidden = infoButtonHandler == nil } } private let chevronDown = UIImage(named: "IconChevronDown") private let chevronUp = UIImage(named: "IconChevronUp") private let buttonWidth: CGFloat = 24 override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) infoButton.isHidden = true infoButton.addTarget( self, action: #selector(handleInfoButton(_:)), for: .touchUpInside ) collapseButton.isHidden = true collapseButton.addTarget( self, action: #selector(handleCollapseButton(_:)), for: .touchUpInside ) contentView.directionalLayoutMargins = UIMetrics.SettingsCell.defaultLayoutMargins contentView.backgroundColor = UIColor.Cell.Background.normal let buttonAreaWidth = UIMetrics.contentLayoutMargins.leading + UIMetrics .contentLayoutMargins.trailing + buttonWidth contentView.addConstrainedSubviews([titleLabel, infoButton, collapseButton]) { titleLabel.pinEdgesToSuperview(.init([.top(contentView.layoutMargins.top), .leading(16)])) titleLabel.bottomAnchor.constraint( equalTo: contentView.bottomAnchor, constant: -contentView.layoutMargins.bottom ).withPriority(.defaultHigh) infoButton.pinEdgesToSuperview(.init([.top(0), .bottom(0)])) titleLabel.trailingAnchor.constraint( equalTo: infoButton.leadingAnchor, constant: UIMetrics.TableView.infoButtonSpacing ) infoButton.widthAnchor.constraint(equalToConstant: buttonAreaWidth) collapseButton.pinEdgesToSuperview(.all().excluding(.leading)) collapseButton.leadingAnchor.constraint(greaterThanOrEqualTo: infoButton.trailingAnchor) collapseButton.widthAnchor.constraint(equalToConstant: buttonAreaWidth) } updateCollapseImage() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func handleInfoButton(_ sender: UIControl) { infoButtonHandler?() } @objc private func handleCollapseButton(_ sender: UIControl) { didCollapseHandler?(self) } @objc private func toggleCollapseAccessibilityAction() -> Bool { didCollapseHandler?(self) return true } private func updateCollapseImage() { let image = isExpanded ? chevronUp : chevronDown collapseButton.setImage(image, for: .normal) collapseButton.setAccessibilityIdentifier(isExpanded ? .collapseButton : .expandButton) } private func updateAccessibilityCustomActions() { let actionName = isExpanded ? String(format: NSLocalizedString("Collapse %@", comment: ""), accessibilityCustomActionName) : String(format: NSLocalizedString("Expand %@", comment: ""), accessibilityCustomActionName) accessibilityCustomActions = [ UIAccessibilityCustomAction( name: actionName, target: self, selector: #selector(toggleCollapseAccessibilityAction) ) ] } }