// SPDX-License-Identifier: MIT // Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. import UIKit class TunnelListCell: UITableViewCell { var tunnel: TunnelContainer? { didSet(value) { // Bind to the tunnel's name nameLabel.text = tunnel?.name ?? "" nameObservationToken = tunnel?.observe(\.name) { [weak self] tunnel, _ in self?.nameLabel.text = tunnel.name } // Bind to the tunnel's status update(from: tunnel?.status) statusObservationToken = tunnel?.observe(\.status) { [weak self] tunnel, _ in self?.update(from: tunnel.status) } } } var onSwitchToggled: ((Bool) -> Void)? let nameLabel: UILabel = { let nameLabel = UILabel() nameLabel.font = UIFont.preferredFont(forTextStyle: .body) nameLabel.adjustsFontForContentSizeCategory = true nameLabel.numberOfLines = 0 return nameLabel }() let busyIndicator: UIActivityIndicatorView = { let busyIndicator = UIActivityIndicatorView(style: .gray) busyIndicator.hidesWhenStopped = true return busyIndicator }() let statusSwitch = UISwitch() private var statusObservationToken: AnyObject? private var nameObservationToken: AnyObject? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(statusSwitch) statusSwitch.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ statusSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), contentView.trailingAnchor.constraint(equalTo: statusSwitch.trailingAnchor) ]) contentView.addSubview(busyIndicator) busyIndicator.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ busyIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), statusSwitch.leadingAnchor.constraint(equalToSystemSpacingAfter: busyIndicator.trailingAnchor, multiplier: 1) ]) contentView.addSubview(nameLabel) nameLabel.translatesAutoresizingMaskIntoConstraints = false nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) let bottomAnchorConstraint = contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: nameLabel.bottomAnchor, multiplier: 1) bottomAnchorConstraint.priority = .defaultLow NSLayoutConstraint.activate([ nameLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 1), nameLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: contentView.layoutMarginsGuide.leadingAnchor, multiplier: 1), busyIndicator.leadingAnchor.constraint(equalToSystemSpacingAfter: nameLabel.trailingAnchor, multiplier: 1), bottomAnchorConstraint ]) accessoryType = .disclosureIndicator statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged) } @objc func switchToggled() { onSwitchToggled?(statusSwitch.isOn) } private func update(from status: TunnelStatus?) { guard let status = status else { reset() return } DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { [weak statusSwitch, weak busyIndicator] in guard let statusSwitch = statusSwitch, let busyIndicator = busyIndicator else { return } statusSwitch.isOn = !(status == .deactivating || status == .inactive) statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active) if status == .inactive || status == .active { busyIndicator.stopAnimating() } else { busyIndicator.startAnimating() } } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func reset() { statusSwitch.isOn = false statusSwitch.isUserInteractionEnabled = false busyIndicator.stopAnimating() } override func prepareForReuse() { super.prepareForReuse() reset() } }