]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
KeyValueCells now share code
authorEric Kuck <eric@bluelinelabs.com>
Sat, 15 Dec 2018 02:02:37 +0000 (20:02 -0600)
committerEric Kuck <eric@bluelinelabs.com>
Sat, 15 Dec 2018 02:02:37 +0000 (20:02 -0600)
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
WireGuard/WireGuard.xcodeproj/project.pbxproj
WireGuard/WireGuard/UI/iOS/View/BorderedTextButton.swift
WireGuard/WireGuard/UI/iOS/View/CopyableLabelTableViewCell.swift [deleted file]
WireGuard/WireGuard/UI/iOS/View/EditableKeyValueCell.swift [deleted file]
WireGuard/WireGuard/UI/iOS/View/KeyValueCell.swift
WireGuard/WireGuard/UI/iOS/View/ScrollableLabel.swift [deleted file]
WireGuard/WireGuard/UI/iOS/View/TunnelEditKeyValueCell.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/View/TunnelEditReadOnlyKeyValueCell.swift [deleted file]
WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift

index dec77014ee62d67a4b0d16ad3f6deb49f07ed6a6..8120b38ec6a46fb416886fea0f98fc181547521c 100644 (file)
@@ -8,8 +8,7 @@
 
 /* Begin PBXBuildFile section */
                5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */; };
-               5F45418A21C2D45B00994C13 /* EditableKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418921C2D45B00994C13 /* EditableKeyValueCell.swift */; };
-               5F45418C21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */; };
+               5F45418C21C2D48200994C13 /* TunnelEditKeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418B21C2D48200994C13 /* TunnelEditKeyValueCell.swift */; };
                5F45419021C2D53800994C13 /* SwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45418F21C2D53800994C13 /* SwitchCell.swift */; };
                5F45419221C2D55800994C13 /* CheckmarkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419121C2D55800994C13 /* CheckmarkCell.swift */; };
                5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F45419721C2D60500994C13 /* KeyValueCell.swift */; };
@@ -17,8 +16,6 @@
                5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */; };
                5F4541A621C4449E00994C13 /* ButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A521C4449E00994C13 /* ButtonCell.swift */; };
                5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A821C451D100994C13 /* TunnelStatus.swift */; };
-               6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */; };
-               6F0068572191AFD200419BE9 /* ScrollableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0068562191AFD200419BE9 /* ScrollableLabel.swift */; };
                6F5A2B4621AFDED40081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
                6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
                6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; };
 
 /* Begin PBXFileReference section */
                5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+Reuse.swift"; sourceTree = "<group>"; };
-               5F45418921C2D45B00994C13 /* EditableKeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableKeyValueCell.swift; sourceTree = "<group>"; };
-               5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditReadOnlyKeyValueCell.swift; sourceTree = "<group>"; };
+               5F45418B21C2D48200994C13 /* TunnelEditKeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditKeyValueCell.swift; sourceTree = "<group>"; };
                5F45418F21C2D53800994C13 /* SwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchCell.swift; sourceTree = "<group>"; };
                5F45419121C2D55800994C13 /* CheckmarkCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckmarkCell.swift; sourceTree = "<group>"; };
                5F45419721C2D60500994C13 /* KeyValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueCell.swift; sourceTree = "<group>"; };
                5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorderedTextButton.swift; sourceTree = "<group>"; };
                5F4541A521C4449E00994C13 /* ButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCell.swift; sourceTree = "<group>"; };
                5F4541A821C451D100994C13 /* TunnelStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatus.swift; sourceTree = "<group>"; };
-               6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyableLabelTableViewCell.swift; sourceTree = "<group>"; };
-               6F0068562191AFD200419BE9 /* ScrollableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableLabel.swift; sourceTree = "<group>"; };
                6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = "<group>"; };
                6F5D0C1421832391000F85AD /* DNSResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSResolver.swift; sourceTree = "<group>"; };
                6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WireGuardNetworkExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
                        isa = PBXGroup;
                        children = (
                                5F45419F21C2D6B700994C13 /* TunnelListCell.swift */,
-                               5F45418B21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift */,
-                               6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */,
-                               6F0068562191AFD200419BE9 /* ScrollableLabel.swift */,
+                               5F45418B21C2D48200994C13 /* TunnelEditKeyValueCell.swift */,
                                5F45418F21C2D53800994C13 /* SwitchCell.swift */,
                                5F45419721C2D60500994C13 /* KeyValueCell.swift */,
                                5F4541A521C4449E00994C13 /* ButtonCell.swift */,
                                5F45419121C2D55800994C13 /* CheckmarkCell.swift */,
-                               5F45418921C2D45B00994C13 /* EditableKeyValueCell.swift */,
                                5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */,
                        );
                        path = View;
                                6F6899AC218099F00012E523 /* WgQuickConfigFileParser.swift in Sources */,
                                6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */,
                                6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */,
-                               6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */,
                                5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */,
                                5F45419221C2D55800994C13 /* CheckmarkCell.swift in Sources */,
                                6FE254FF219C60290028284D /* ZipExporter.swift in Sources */,
                                6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
-                               6F0068572191AFD200419BE9 /* ScrollableLabel.swift in Sources */,
                                6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
                                6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
                                5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
                                6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */,
-                               5F45418A21C2D45B00994C13 /* EditableKeyValueCell.swift in Sources */,
                                6F6899A62180447E0012E523 /* x25519.c in Sources */,
                                6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */,
                                6FDEF80021863C0100D8FBF6 /* ioapi.c in Sources */,
                                5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */,
                                6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
                                6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
-                               5F45418C21C2D48200994C13 /* TunnelEditReadOnlyKeyValueCell.swift in Sources */,
+                               5F45418C21C2D48200994C13 /* TunnelEditKeyValueCell.swift in Sources */,
                                6FDEF8082187442100D8FBF6 /* WgQuickConfigFileWriter.swift in Sources */,
                                6FE254FB219C10800028284D /* ZipImporter.swift in Sources */,
                                6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */,
index 94b76d62773ebe13bec4dc44f61f4b6ceade9734..ab6dcc50bd37866d58729f1f9bad0b3d9e88ce84 100644 (file)
@@ -40,11 +40,12 @@ class BorderedTextButton: UIView {
         button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
     }
     
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
     @objc func buttonTapped() {
         onTapped?()
     }
     
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
 }
diff --git a/WireGuard/WireGuard/UI/iOS/View/CopyableLabelTableViewCell.swift b/WireGuard/WireGuard/UI/iOS/View/CopyableLabelTableViewCell.swift
deleted file mode 100644 (file)
index 93a9ef7..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2018 WireGuard LLC. All Rights Reserved.
-
-import UIKit
-
-class CopyableLabelTableViewCell: UITableViewCell {
-    var copyableGesture = true
-
-    var textToCopy: String? {
-        fatalError("textToCopy must be implemented by subclass")
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-        let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
-        addGestureRecognizer(gestureRecognizer)
-        isUserInteractionEnabled = true
-    }
-
-    // MARK: - UIGestureRecognizer
-    @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 = textToCopy
-    }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        copyableGesture = true
-    }
-}
diff --git a/WireGuard/WireGuard/UI/iOS/View/EditableKeyValueCell.swift b/WireGuard/WireGuard/UI/iOS/View/EditableKeyValueCell.swift
deleted file mode 100644 (file)
index 48956eb..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2018 WireGuard LLC. All Rights Reserved.
-
-import UIKit
-
-class EditableKeyValueCell: 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 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(contentSizeBasedConstraints)
-            NSLayoutConstraint.activate(constraints)
-            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 EditableKeyValueCell: 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
-    }
-}
index 78026eab8492c764009e3195809258a3a1008931..a456de1ef6b132dcf6d656328cce889eae44977c 100644 (file)
 
 import UIKit
 
-class KeyValueCell: CopyableLabelTableViewCell {
-    var key: String {
-        get { return keyLabel.text ?? "" }
-        set(value) { keyLabel.text = value }
-    }
-    var value: String {
-        get { return valueLabel.text }
-        set(value) { valueLabel.text = value }
-    }
-    
-    override var textToCopy: String? {
-        return valueLabel.text
-    }
+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 valueLabel: ScrollableLabel = {
-        let valueLabel = ScrollableLabel()
-        valueLabel.label.font = UIFont.preferredFont(forTextStyle: .body)
-        valueLabel.label.adjustsFontForContentSizeCategory = true
-        valueLabel.textColor = .gray
-        return valueLabel
+    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
-        keyLabel.textAlignment = .left
         NSLayoutConstraint.activate([
             keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
             keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
         ])
         
-        contentView.addSubview(valueLabel)
-        valueLabel.translatesAutoresizingMaskIntoConstraints = false
+        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([
-            valueLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
-            contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueLabel.bottomAnchor, multiplier: 0.5)
+            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)
-        valueLabel.setContentHuggingPriority(.defaultLow, 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 = [
-                    valueLabel.topAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
-                    valueLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
+                    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
@@ -80,8 +139,8 @@ class KeyValueCell: CopyableLabelTableViewCell {
             if !isStackedHorizontally {
                 constraints = [
                     contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
-                    valueLabel.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
-                    valueLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, 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
@@ -94,14 +153,65 @@ class KeyValueCell: CopyableLabelTableViewCell {
         }
     }
     
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
+    @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
+    }
+    
+}
diff --git a/WireGuard/WireGuard/UI/iOS/View/ScrollableLabel.swift b/WireGuard/WireGuard/UI/iOS/View/ScrollableLabel.swift
deleted file mode 100644 (file)
index bd6f547..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2018 WireGuard LLC. All Rights Reserved.
-
-import UIKit
-
-class ScrollableLabel: UIScrollView {
-    var text: String {
-        get { return label.text ?? "" }
-        set(value) { label.text = value }
-    }
-    var textColor: UIColor {
-        get { return label.textColor }
-        set(value) { label.textColor = value }
-    }
-
-    let label: UILabel = {
-        let label = UILabel()
-        label.translatesAutoresizingMaskIntoConstraints = false
-        label.textAlignment = .right
-        return label
-    }()
-
-    init() {
-        super.init(frame: CGRect.zero)
-
-        isDirectionalLockEnabled = true
-        showsHorizontalScrollIndicator = false
-        showsVerticalScrollIndicator = false
-
-        addSubview(label)
-        label.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            label.leftAnchor.constraint(equalTo: contentLayoutGuide.leftAnchor),
-            label.topAnchor.constraint(equalTo: contentLayoutGuide.topAnchor),
-            label.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor),
-            label.rightAnchor.constraint(equalTo: contentLayoutGuide.rightAnchor),
-            label.heightAnchor.constraint(equalTo: heightAnchor)
-        ])
-
-        let expandToFitValueLabelConstraint = NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
-        expandToFitValueLabelConstraint.priority = .defaultLow + 1
-        expandToFitValueLabelConstraint.isActive = true
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-}
diff --git a/WireGuard/WireGuard/UI/iOS/View/TunnelEditKeyValueCell.swift b/WireGuard/WireGuard/UI/iOS/View/TunnelEditKeyValueCell.swift
new file mode 100644 (file)
index 0000000..3e18670
--- /dev/null
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TunnelEditKeyValueCell: KeyValueCell {
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        keyLabel.textAlignment = .right
+        valueTextField.textAlignment = .left
+        
+        let widthRatioConstraint = NSLayoutConstraint(item: keyLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 0.4, constant: 0)
+        // In case the key doesn't fit into 0.4 * width,
+        // so set a CR priority > the 0.4-constraint's priority.
+        widthRatioConstraint.priority = .defaultHigh + 1
+        widthRatioConstraint.isActive = true
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+}
+
+class TunnelEditEditableKeyValueCell: TunnelEditKeyValueCell {
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        valueTextField.textColor = .black
+        valueTextField.isEnabled = true
+        valueLabelScrollView.isScrollEnabled = false
+        valueTextField.widthAnchor.constraint(equalTo: valueLabelScrollView.widthAnchor).isActive = true
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+}
diff --git a/WireGuard/WireGuard/UI/iOS/View/TunnelEditReadOnlyKeyValueCell.swift b/WireGuard/WireGuard/UI/iOS/View/TunnelEditReadOnlyKeyValueCell.swift
deleted file mode 100644 (file)
index 15d58d6..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2018 WireGuard LLC. All Rights Reserved.
-
-import UIKit
-
-class TunnelEditReadOnlyKeyValueCell: CopyableLabelTableViewCell {
-    var key: String {
-        get { return keyLabel.text ?? "" }
-        set(value) { keyLabel.text = value }
-    }
-    var value: String {
-        get { return valueLabel.text }
-        set(value) { valueLabel.text = value }
-    }
-    
-    override var textToCopy: String? {
-        return valueLabel.text
-    }
-    
-    let keyLabel: UILabel = {
-        let keyLabel = UILabel()
-        keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
-        keyLabel.adjustsFontForContentSizeCategory = true
-        keyLabel.textColor = .gray
-        return keyLabel
-    }()
-    
-    let valueLabel: ScrollableLabel = {
-        let valueLabel = ScrollableLabel()
-        valueLabel.label.font = UIFont.preferredFont(forTextStyle: .body)
-        valueLabel.label.adjustsFontForContentSizeCategory = true
-        valueLabel.textColor = .gray
-        return valueLabel
-    }()
-    
-    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)
-        // In case the key doesn't fit into 0.4 * width,
-        // so set a CR priority > the 0.4-constraint's priority.
-        widthRatioConstraint.priority = .defaultHigh + 1
-        keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
-        NSLayoutConstraint.activate([
-            keyLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
-            keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
-            widthRatioConstraint
-        ])
-        
-        contentView.addSubview(valueLabel)
-        valueLabel.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            valueLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
-            valueLabel.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
-            valueLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor)
-        ])
-    }
-    
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-    
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        key = ""
-        value = ""
-    }
-}
index 393294e5eaba310c1a7603efb0b8b7ac3ed8fd5b..a9e213920e62e5a53a3902fefccdd471167068cd 100644 (file)
@@ -79,8 +79,8 @@ class TunnelEditTableViewController: UITableViewController {
         tableView.estimatedRowHeight = 44
         tableView.rowHeight = UITableView.automaticDimension
 
-        tableView.register(EditableKeyValueCell.self)
-        tableView.register(TunnelEditReadOnlyKeyValueCell.self)
+        tableView.register(TunnelEditKeyValueCell.self)
+        tableView.register(TunnelEditEditableKeyValueCell.self)
         tableView.register(ButtonCell.self)
         tableView.register(SwitchCell.self)
         tableView.register(CheckmarkCell.self)
@@ -218,14 +218,14 @@ extension TunnelEditTableViewController {
     }
 
     private func publicKeyCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
-        let cell: TunnelEditReadOnlyKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelEditKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
         cell.key = field.rawValue
         cell.value = tunnelViewModel.interfaceData[field]
         return cell
     }
 
     private func interfaceFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
-        let cell: EditableKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelEditEditableKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
         cell.key = field.rawValue
 
         switch field {
@@ -328,7 +328,7 @@ extension TunnelEditTableViewController {
     }
 
     private func peerFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
-        let cell: EditableKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
+        let cell: TunnelEditEditableKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
         cell.key = field.rawValue
 
         switch field {