aboutsummaryrefslogblamecommitdiffstats
path: root/WireGuard/WireGuard/UI/iOS/View/KeyValueCell.swift
blob: 81a171298a3ad4d0ecb081b14b643816aed1f7e8 (plain) (tree)
1
2
3
4
5
6
7




                                                        
                                     
 




                                                                 
                                      

                       
 






                                                         
 










                                                                       
       
 

















                                                          
 








                                           
 


                                                            
 

                                               
 
                                                         
 

                                                                               
 

                                                                  


                                                                                                                               
          
 












                                                                                                                                                                                                         
 
                                                    
 

                                                                              
                                     

                                                                                                                                                 
          
 

                                                                                            
                                                                                     
 


                                                                                                             
 

                                 
 


                                                           
 

                                                
                                                                                 


                                     

                                                                                                                                 









                                                                                                                                              

                                                                                                                                                   





                                            
                                                                      
                                                    
                                                     

         
 




                                                                    
 






                                                                                                                                               
 


                                                
 


                                                                                         
 

                                                         
     
 

                                     





                                




                                 

                                             
 



                                                             
 




                                                                             
 






                                                                                                                                 
 
 
// SPDX-License-Identifier: MIT
// Copyright © 2018 WireGuard LLC. All Rights Reserved.

import UIKit

class KeyValueCell: UITableViewCell {

    let keyLabel: UILabel = {
        let keyLabel = UILabel()
        keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
        keyLabel.adjustsFontForContentSizeCategory = true
        keyLabel.textColor = .black
        keyLabel.textAlignment = .left
        return keyLabel
    }()

    let valueLabelScrollView: UIScrollView = {
        let scrollView = UIScrollView(frame: .zero)
        scrollView.isDirectionalLockEnabled = true
        scrollView.showsHorizontalScrollIndicator = false
        scrollView.showsVerticalScrollIndicator = false
        return scrollView
    }()

    let valueTextField: UITextField = {
        let valueTextField = UITextField()
        valueTextField.textAlignment = .right
        valueTextField.isEnabled = false
        valueTextField.font = UIFont.preferredFont(forTextStyle: .body)
        valueTextField.adjustsFontForContentSizeCategory = true
        valueTextField.autocapitalizationType = .none
        valueTextField.autocorrectionType = .no
        valueTextField.spellCheckingType = .no
        valueTextField.textColor = .gray
        return valueTextField
    }()

    var copyableGesture = true

    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 keyboardType: UIKeyboardType {
        get { return valueTextField.keyboardType }
        set(value) { valueTextField.keyboardType = value }
    }

    var isValueValid = true {
        didSet {
            if isValueValid {
                keyLabel.textColor = .black
            } else {
                keyLabel.textColor = .red
            }
        }
    }

    var isStackedHorizontally = false
    var isStackedVertically = false
    var contentSizeBasedConstraints = [NSLayoutConstraint]()

    var onValueChanged: ((String) -> Void)?
    var onValueBeingEdited: ((String) -> Void)?

    private var textFieldValueOnBeginEditing: String = ""

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        contentView.addSubview(keyLabel)
        keyLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
            keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
        ])

        valueTextField.delegate = self
        valueLabelScrollView.addSubview(valueTextField)
        valueTextField.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            valueTextField.leftAnchor.constraint(equalTo: valueLabelScrollView.contentLayoutGuide.leftAnchor),
            valueTextField.topAnchor.constraint(equalTo: valueLabelScrollView.contentLayoutGuide.topAnchor),
            valueTextField.bottomAnchor.constraint(equalTo: valueLabelScrollView.contentLayoutGuide.bottomAnchor),
            valueTextField.rightAnchor.constraint(equalTo: valueLabelScrollView.contentLayoutGuide.rightAnchor),
            valueTextField.heightAnchor.constraint(equalTo: valueLabelScrollView.heightAnchor)
        ])
        let expandToFitValueLabelConstraint = NSLayoutConstraint(item: valueTextField, attribute: .width, relatedBy: .equal, toItem: valueLabelScrollView, attribute: .width, multiplier: 1, constant: 0)
        expandToFitValueLabelConstraint.priority = .defaultLow + 1
        expandToFitValueLabelConstraint.isActive = true

        contentView.addSubview(valueLabelScrollView)

        contentView.addSubview(valueLabelScrollView)
        valueLabelScrollView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            valueLabelScrollView.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
            contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueLabelScrollView.bottomAnchor, multiplier: 0.5)
        ])

        keyLabel.setContentCompressionResistancePriority(.defaultHigh + 1, for: .horizontal)
        keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        valueLabelScrollView.setContentHuggingPriority(.defaultLow, for: .horizontal)

        let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
        addGestureRecognizer(gestureRecognizer)
        isUserInteractionEnabled = true

        configureForContentSize()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func configureForContentSize() {
        var constraints = [NSLayoutConstraint]()
        if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
            // Stack vertically
            if !isStackedVertically {
                constraints = [
                    valueLabelScrollView.topAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
                    valueLabelScrollView.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),
                    valueLabelScrollView.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
                    valueLabelScrollView.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
                ]
                isStackedHorizontally = true
                isStackedVertically = false
            }
        }
        if !constraints.isEmpty {
            NSLayoutConstraint.deactivate(contentSizeBasedConstraints)
            NSLayoutConstraint.activate(constraints)
            contentSizeBasedConstraints = constraints
        }
    }

    @objc func handleTapGesture(_ recognizer: UIGestureRecognizer) {
        if !copyableGesture {
            return
        }
        guard recognizer.state == .recognized else { return }

        if let recognizerView = recognizer.view,
            let recognizerSuperView = recognizerView.superview, recognizerView.becomeFirstResponder() {
            let menuController = UIMenuController.shared
            menuController.setTargetRect(detailTextLabel?.frame ?? recognizerView.frame, in: detailTextLabel?.superview ?? recognizerSuperView)
            menuController.setMenuVisible(true, animated: true)
        }
    }

    override var canBecomeFirstResponder: Bool {
        return true
    }

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        return (action == #selector(UIResponderStandardEditActions.copy(_:)))
    }

    override func copy(_ sender: Any?) {
        UIPasteboard.general.string = valueTextField.text
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        copyableGesture = true
        placeholderText = ""
        isValueValid = true
        keyboardType = .default
        onValueChanged = nil
        onValueBeingEdited = nil
        key = ""
        value = ""
        configureForContentSize()
    }
}

extension KeyValueCell: UITextFieldDelegate {

    func textFieldDidBeginEditing(_ textField: UITextField) {
        textFieldValueOnBeginEditing = textField.text ?? ""
        isValueValid = true
    }

    func textFieldDidEndEditing(_ textField: UITextField) {
        let isModified = textField.text ?? "" != textFieldValueOnBeginEditing
        guard isModified else { return }
        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
    }

}