aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Kuck <eric@bluelinelabs.com>2018-12-14 20:02:37 -0600
committerEric Kuck <eric@bluelinelabs.com>2018-12-14 20:02:37 -0600
commitccd8cfe4788c102165a4671ef57339dfbd68d0a7 (patch)
tree033ae014e14993e4ad506b73260fe66901826df0
parentReorganized project structure (diff)
downloadwireguard-apple-ccd8cfe4788c102165a4671ef57339dfbd68d0a7.tar.xz
wireguard-apple-ccd8cfe4788c102165a4671ef57339dfbd68d0a7.zip
KeyValueCells now share code
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
-rw-r--r--WireGuard/WireGuard.xcodeproj/project.pbxproj20
-rw-r--r--WireGuard/WireGuard/UI/iOS/View/BorderedTextButton.swift7
-rw-r--r--WireGuard/WireGuard/UI/iOS/View/CopyableLabelTableViewCell.swift55
-rw-r--r--WireGuard/WireGuard/UI/iOS/View/EditableKeyValueCell.swift158
-rw-r--r--WireGuard/WireGuard/UI/iOS/View/KeyValueCell.swift172
-rw-r--r--WireGuard/WireGuard/UI/iOS/View/ScrollableLabel.swift48
-rw-r--r--WireGuard/WireGuard/UI/iOS/View/TunnelEditKeyValueCell.swift42
-rw-r--r--WireGuard/WireGuard/UI/iOS/View/TunnelEditReadOnlyKeyValueCell.swift74
-rw-r--r--WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift10
9 files changed, 196 insertions, 390 deletions
diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj
index dec7701..8120b38 100644
--- a/WireGuard/WireGuard.xcodeproj/project.pbxproj
+++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj
@@ -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 */; };
@@ -105,8 +102,7 @@
/* 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>"; };
@@ -114,8 +110,6 @@
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; };
@@ -198,14 +192,11 @@
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;
@@ -662,17 +653,14 @@
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 */,
@@ -683,7 +671,7 @@
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 */,
diff --git a/WireGuard/WireGuard/UI/iOS/View/BorderedTextButton.swift b/WireGuard/WireGuard/UI/iOS/View/BorderedTextButton.swift
index 94b76d6..ab6dcc5 100644
--- a/WireGuard/WireGuard/UI/iOS/View/BorderedTextButton.swift
+++ b/WireGuard/WireGuard/UI/iOS/View/BorderedTextButton.swift
@@ -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
index 93a9ef7..0000000
--- a/WireGuard/WireGuard/UI/iOS/View/CopyableLabelTableViewCell.swift
+++ /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
index 48956eb..0000000
--- a/WireGuard/WireGuard/UI/iOS/View/EditableKeyValueCell.swift
+++ /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
- }
-}
diff --git a/WireGuard/WireGuard/UI/iOS/View/KeyValueCell.swift b/WireGuard/WireGuard/UI/iOS/View/KeyValueCell.swift
index 78026ea..a456de1 100644
--- a/WireGuard/WireGuard/UI/iOS/View/KeyValueCell.swift
+++ b/WireGuard/WireGuard/UI/iOS/View/KeyValueCell.swift
@@ -3,73 +3,132 @@
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
index bd6f547..0000000
--- a/WireGuard/WireGuard/UI/iOS/View/ScrollableLabel.swift
+++ /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
index 0000000..3e18670
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/View/TunnelEditKeyValueCell.swift
@@ -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
index 15d58d6..0000000
--- a/WireGuard/WireGuard/UI/iOS/View/TunnelEditReadOnlyKeyValueCell.swift
+++ /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 = ""
- }
-}
diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
index 393294e..a9e2139 100644
--- a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
@@ -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 {