aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2018-10-31 01:00:27 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2018-10-31 02:17:47 +0100
commit2e78aecd6854f63fdaa8bc8a5e591ea6db1759e7 (patch)
treed389821dd9e5f6d8f65bba2ae700fbdeef3f4d72
parentwireguard-go: pull in recent change (diff)
downloadwireguard-apple-2e78aecd6854f63fdaa8bc8a5e591ea6db1759e7.tar.xz
wireguard-apple-2e78aecd6854f63fdaa8bc8a5e591ea6db1759e7.zip
UI: More elegant copy-to-clipboard behavior
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--WireGuard/WireGuard.xcodeproj/project.pbxproj4
-rw-r--r--WireGuard/WireGuard/UI/TunnelViewModel.swift3
-rw-r--r--WireGuard/WireGuard/UI/iOS/CopyableLabelTableViewCell.swift51
-rw-r--r--WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift84
-rw-r--r--WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift4
5 files changed, 87 insertions, 59 deletions
diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj
index 27d1137..efdf1dd 100644
--- a/WireGuard/WireGuard.xcodeproj/project.pbxproj
+++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */; };
6F5D0C1521832391000F85AD /* DNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1421832391000F85AD /* DNSResolver.swift */; };
6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; };
6F5D0C22218352EF000F85AD /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -72,6 +73,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyableLabelTableViewCell.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; };
6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
@@ -186,6 +188,7 @@
6F7774DE217181B1006A79B3 /* iOS */ = {
isa = PBXGroup;
children = (
+ 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */,
6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */,
6F7774E0217181B1006A79B3 /* AppDelegate.swift */,
6F7774DF217181B1006A79B3 /* MainViewController.swift */,
@@ -439,6 +442,7 @@
6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */,
6F5D0C1521832391000F85AD /* DNSResolver.swift in Sources */,
6F5D0C482183C6A3000F85AD /* PacketTunnelOptionsGenerator.swift in Sources */,
+ 6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */,
6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
6F6899A62180447E0012E523 /* x25519.c in Sources */,
diff --git a/WireGuard/WireGuard/UI/TunnelViewModel.swift b/WireGuard/WireGuard/UI/TunnelViewModel.swift
index fe50cbc..56fa372 100644
--- a/WireGuard/WireGuard/UI/TunnelViewModel.swift
+++ b/WireGuard/WireGuard/UI/TunnelViewModel.swift
@@ -10,7 +10,6 @@ class TunnelViewModel {
case privateKey = "Private key"
case publicKey = "Public key"
case generateKeyPair = "Generate keypair"
- case copyPublicKey = "Copy public key"
case addresses = "Addresses"
case listenPort = "Listen port"
case mtu = "MTU"
@@ -18,7 +17,7 @@ class TunnelViewModel {
}
static let interfaceFieldsWithControl: Set<InterfaceField> = [
- .generateKeyPair, .copyPublicKey
+ .generateKeyPair
]
enum PeerField: String {
diff --git a/WireGuard/WireGuard/UI/iOS/CopyableLabelTableViewCell.swift b/WireGuard/WireGuard/UI/iOS/CopyableLabelTableViewCell.swift
new file mode 100644
index 0000000..779fe8f
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/CopyableLabelTableViewCell.swift
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class CopyableLabelTableViewCell: UITableViewCell {
+ var copyableGesture = true
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
+ self.addGestureRecognizer(gestureRecognizer)
+ self.isUserInteractionEnabled = true
+ }
+
+ // MARK: - UIGestureRecognizer
+ @objc func handleTapGesture(_ recognizer: UIGestureRecognizer) {
+ if !self.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(self.detailTextLabel?.frame ?? recognizerView.frame, in: self.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 = self.detailTextLabel?.text
+ }
+
+ override func prepareForReuse() {
+ super.prepareForReuse()
+ self.copyableGesture = true
+ }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift
index 0cc7806..fe413c9 100644
--- a/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift
@@ -7,10 +7,9 @@ import UIKit
class TunnelDetailTableViewController: UITableViewController {
- let interfaceFieldsBySection: [[TunnelViewModel.InterfaceField]] = [
- [.name],
- [.publicKey, .copyPublicKey],
- [.addresses, .listenPort, .mtu, .dns]
+ let interfaceFields: [TunnelViewModel.InterfaceField] = [
+ .name, .publicKey, .addresses,
+ .listenPort, .mtu, .dns
]
let peerFields: [TunnelViewModel.PeerField] = [
@@ -95,32 +94,22 @@ extension TunnelDetailTableViewController: TunnelEditTableViewControllerDelegate
extension TunnelDetailTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
- let interfaceData = tunnelViewModel.interfaceData
- let numberOfInterfaceSections = (0 ..< interfaceFieldsBySection.count).filter { section in
- (!interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFieldsBySection[section]).isEmpty)
- }.count
- let numberOfPeerSections = tunnelViewModel.peersData.count
-
- return 1 + numberOfInterfaceSections + numberOfPeerSections + 1
+ return 3 + tunnelViewModel.peersData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let interfaceData = tunnelViewModel.interfaceData
- let numberOfInterfaceSections = (0 ..< interfaceFieldsBySection.count).filter { section in
- (!interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFieldsBySection[section]).isEmpty)
- }.count
let numberOfPeerSections = tunnelViewModel.peersData.count
if (section == 0) {
// Status
return 1
- } else if (section < (1 + numberOfInterfaceSections)) {
+ } else if (section == 1) {
// Interface
- return interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFieldsBySection[section - 1]).count
- } else if ((numberOfPeerSections > 0) && (section < (1 + numberOfInterfaceSections + numberOfPeerSections))) {
+ return interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields).count
+ } else if ((numberOfPeerSections > 0) && (section < (2 + numberOfPeerSections))) {
// Peer
- let peerIndex = (section - numberOfInterfaceSections - 1)
- let peerData = tunnelViewModel.peersData[peerIndex]
+ let peerData = tunnelViewModel.peersData[section - 2]
return peerData.filterFieldsWithValueOrControl(peerFields: peerFields).count
} else {
// Delete tunnel
@@ -129,32 +118,25 @@ extension TunnelDetailTableViewController {
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
- let interfaceData = tunnelViewModel.interfaceData
- let numberOfInterfaceSections = (0 ..< interfaceFieldsBySection.count).filter { section in
- (!interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFieldsBySection[section]).isEmpty)
- }.count
let numberOfPeerSections = tunnelViewModel.peersData.count
if (section == 0) {
// Status
return "Status"
- } else if (section < 1 + numberOfInterfaceSections) {
+ } else if (section == 1) {
// Interface
- return (section == 1) ? "Interface" : nil
- } else if ((numberOfPeerSections > 0) && (section < (1 + numberOfInterfaceSections + numberOfPeerSections))) {
+ return "Interface"
+ } else if ((numberOfPeerSections > 0) && (section < (2 + numberOfPeerSections))) {
// Peer
return "Peer"
} else {
- // Add peer
+ // Delete tunnel
return nil
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let interfaceData = tunnelViewModel.interfaceData
- let numberOfInterfaceSections = (0 ..< interfaceFieldsBySection.count).filter { section in
- (!interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFieldsBySection[section]).isEmpty)
- }.count
let numberOfPeerSections = tunnelViewModel.peersData.count
let section = indexPath.section
@@ -186,32 +168,22 @@ extension TunnelDetailTableViewController {
}
}
return cell
- } else if (section < 1 + numberOfInterfaceSections) {
+ } else if (section == 1) {
// Interface
- let field = interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFieldsBySection[section - 1])[row]
- if (field == .copyPublicKey) {
- let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewButtonCell.id, for: indexPath) as! TunnelDetailTableViewButtonCell
- cell.buttonText = field.rawValue
- cell.onTapped = {
- UIPasteboard.general.string = interfaceData[.publicKey]
- }
- return cell
- } else {
- let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewKeyValueCell.id, for: indexPath) as! TunnelDetailTableViewKeyValueCell
- // Set key and value
- cell.key = field.rawValue
- cell.value = interfaceData[field]
- if (field != .publicKey) {
- cell.detailTextLabel?.allowsDefaultTighteningForTruncation = true
- cell.detailTextLabel?.adjustsFontSizeToFitWidth = true
- cell.detailTextLabel?.minimumScaleFactor = 0.85
- }
- return cell
+ let field = interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields)[row]
+ let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewKeyValueCell.id, for: indexPath) as! TunnelDetailTableViewKeyValueCell
+ // Set key and value
+ cell.key = field.rawValue
+ cell.value = interfaceData[field]
+ if (field != .publicKey) {
+ cell.detailTextLabel?.allowsDefaultTighteningForTruncation = true
+ cell.detailTextLabel?.adjustsFontSizeToFitWidth = true
+ cell.detailTextLabel?.minimumScaleFactor = 0.85
}
- } else if ((numberOfPeerSections > 0) && (section < (1 + numberOfInterfaceSections + numberOfPeerSections))) {
+ return cell
+ } else if ((numberOfPeerSections > 0) && (section < (2 + numberOfPeerSections))) {
// Peer
- let peerIndex = (section - numberOfInterfaceSections - 1)
- let peerData = tunnelViewModel.peersData[peerIndex]
+ let peerData = tunnelViewModel.peersData[section - 2]
let field = peerData.filterFieldsWithValueOrControl(peerFields: peerFields)[row]
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewKeyValueCell.id, for: indexPath) as! TunnelDetailTableViewKeyValueCell
@@ -226,7 +198,7 @@ extension TunnelDetailTableViewController {
return cell
} else {
- assert(section == (1 + numberOfInterfaceSections + numberOfPeerSections))
+ assert(section == (2 + numberOfPeerSections))
// Delete configuration
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewButtonCell.id, for: indexPath) as! TunnelDetailTableViewButtonCell
cell.buttonText = "Delete tunnel"
@@ -328,7 +300,7 @@ class TunnelDetailTableViewStatusCell: UITableViewCell {
}
}
-class TunnelDetailTableViewKeyValueCell: UITableViewCell {
+class TunnelDetailTableViewKeyValueCell: CopyableLabelTableViewCell {
static let id: String = "TunnelDetailTableViewKeyValueCell"
var key: String {
get { return textLabel?.text ?? "" }
@@ -355,7 +327,7 @@ class TunnelDetailTableViewKeyValueCell: UITableViewCell {
}
class TunnelDetailTableViewButtonCell: UITableViewCell {
- static let id: String = "TunnelsEditTableViewButtonCell"
+ static let id: String = "TunnelDetailTableViewButtonCell"
var buttonText: String {
get { return button.title(for: .normal) ?? "" }
set(value) { button.setTitle(value, for: .normal) }
diff --git a/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift
index 71187f8..e0b11e4 100644
--- a/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift
@@ -361,7 +361,7 @@ extension TunnelEditTableViewController {
}
}
-class TunnelEditTableViewKeyValueCell: UITableViewCell {
+class TunnelEditTableViewKeyValueCell: CopyableLabelTableViewCell {
static let id: String = "TunnelEditTableViewKeyValueCell"
var key: String {
get { return keyLabel.text ?? "" }
@@ -378,6 +378,7 @@ class TunnelEditTableViewKeyValueCell: UITableViewCell {
var isValueEditable: Bool {
get { return valueTextField.isEnabled }
set(value) {
+ super.copyableGesture = !value
valueTextField.isEnabled = value
keyLabel.textColor = value ? UIColor.black : UIColor.gray
valueTextField.textColor = value ? UIColor.black : UIColor.gray
@@ -409,6 +410,7 @@ class TunnelEditTableViewKeyValueCell: UITableViewCell {
keyLabel = UILabel()
valueTextField = UITextField()
super.init(style: style, reuseIdentifier: reuseIdentifier)
+ isValueEditable = true
contentView.addSubview(keyLabel)
keyLabel.translatesAutoresizingMaskIntoConstraints = false
keyLabel.textAlignment = .right