// SPDX-License-Identifier: MIT // Copyright © 2018 WireGuard LLC. All Rights Reserved. import UIKit class TunnelEditKeyValueCell: UITableViewCell { var key: String { get { return keyLabel.text ?? "" } set(value) {keyLabel.text = value } } var value: String { get { return valueTextField.text ?? "" } set(value) { valueTextField.text = value } } var placeholderText: String { get { return valueTextField.placeholder ?? "" } set(value) { valueTextField.placeholder = value } } var isValueValid = true { didSet { if isValueValid { keyLabel.textColor = .black } else { keyLabel.textColor = .red } } } var keyboardType: UIKeyboardType { get { return valueTextField.keyboardType } set(value) { valueTextField.keyboardType = value } } var onValueChanged: ((String) -> Void)? var onValueBeingEdited: ((String) -> Void)? let keyLabel: UILabel = { let keyLabel = UILabel() keyLabel.font = UIFont.preferredFont(forTextStyle: .body) keyLabel.adjustsFontForContentSizeCategory = true return keyLabel }() let valueTextField: UITextField = { let valueTextField = UITextField() valueTextField.font = UIFont.preferredFont(forTextStyle: .body) valueTextField.adjustsFontForContentSizeCategory = true valueTextField.autocapitalizationType = .none valueTextField.autocorrectionType = .no valueTextField.spellCheckingType = .no return valueTextField }() var isStackedHorizontally = false var isStackedVertically = false var contentSizeBasedConstraints = [NSLayoutConstraint]() private var textFieldValueOnBeginEditing: String = "" override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(keyLabel) keyLabel.translatesAutoresizingMaskIntoConstraints = false keyLabel.textAlignment = .right let widthRatioConstraint = NSLayoutConstraint(item: keyLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 0.4, constant: 0) // The "Persistent Keepalive" key doesn't fit into 0.4 * width on the iPhone SE, // so set a CR priority > the 0.4-constraint's priority. widthRatioConstraint.priority = .defaultHigh + 1 keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal) NSLayoutConstraint.activate([ keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor), keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5), widthRatioConstraint ]) contentView.addSubview(valueTextField) valueTextField.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ valueTextField.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor), contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueTextField.bottomAnchor, multiplier: 0.5) ]) valueTextField.delegate = self configureForContentSize() } func configureForContentSize() { var constraints = [NSLayoutConstraint]() if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory { // Stack vertically if !isStackedVertically { constraints = [ valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5), valueTextField.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor), keyLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor) ] isStackedVertically = true isStackedHorizontally = false } } else { // Stack horizontally if !isStackedHorizontally { constraints = [ contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5), valueTextField.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1), valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5) ] isStackedHorizontally = true isStackedVertically = false } } if !constraints.isEmpty { NSLayoutConstraint.deactivate(self.contentSizeBasedConstraints) NSLayoutConstraint.activate(constraints) self.contentSizeBasedConstraints = constraints } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func prepareForReuse() { super.prepareForReuse() key = "" value = "" placeholderText = "" isValueValid = true keyboardType = .default onValueChanged = nil onValueBeingEdited = nil configureForContentSize() } } extension TunnelEditKeyValueCell: UITextFieldDelegate { func textFieldDidBeginEditing(_ textField: UITextField) { textFieldValueOnBeginEditing = textField.text ?? "" isValueValid = true } func textFieldDidEndEditing(_ textField: UITextField) { let isModified = (textField.text ?? "" != textFieldValueOnBeginEditing) guard isModified else { return } if let onValueChanged = onValueChanged { onValueChanged(textField.text ?? "") } } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if let onValueBeingEdited = onValueBeingEdited { let modifiedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string) onValueBeingEdited(modifiedText) } return true } }