summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2021-02-22 12:45:28 +0100
committerAndrej Mihajlov <and@mullvad.net>2021-02-24 11:19:07 +0100
commitd3816ce772bf1a050f8a33d73e25ed3efec7c885 (patch)
tree42a5d4c004baf0a396d8da27099fa6a604f99e65 /ios
parent1cf9b1e0a2c4f319eccdc7ca05ee2134f0ee13d9 (diff)
downloadmullvadvpn-d3816ce772bf1a050f8a33d73e25ed3efec7c885.tar.xz
mullvadvpn-d3816ce772bf1a050f8a33d73e25ed3efec7c885.zip
Add custom text field & text view
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj8
-rw-r--r--ios/MullvadVPN/CustomTextField.swift64
-rw-r--r--ios/MullvadVPN/CustomTextView.swift124
-rw-r--r--ios/MullvadVPN/UIColor+Palette.swift5
4 files changed, 201 insertions, 0 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index d3c509d4b6..4ccdcc0649 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -51,6 +51,8 @@
581503A624D6F4AE00C9C50E /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581503A524D6F4AE00C9C50E /* Logging.swift */; };
581503A724D6F4AE00C9C50E /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581503A524D6F4AE00C9C50E /* Logging.swift */; };
581CBCEE229826FD00727D7F /* StaticTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581CBCED229826FD00727D7F /* StaticTableViewDataSource.swift */; };
+ 58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB025124117005D0BB5 /* CustomTextField.swift */; };
+ 58293FB3251241B4005D0BB5 /* CustomTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB2251241B3005D0BB5 /* CustomTextView.swift */; };
582AE3102440A6CA00E6733A /* AccountTokenInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */; };
582AE3122440CA0D00E6733A /* AccountTokenInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE3112440CA0D00E6733A /* AccountTokenInputTests.swift */; };
582AE3132440CA2700E6733A /* AccountTokenInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */; };
@@ -284,6 +286,8 @@
581503A224D6F1EC00C9C50E /* ChainedError+Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChainedError+Logger.swift"; sourceTree = "<group>"; };
581503A524D6F4AE00C9C50E /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
581CBCED229826FD00727D7F /* StaticTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticTableViewDataSource.swift; sourceTree = "<group>"; };
+ 58293FB025124117005D0BB5 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; };
+ 58293FB2251241B3005D0BB5 /* CustomTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextView.swift; sourceTree = "<group>"; };
582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountTokenInput.swift; sourceTree = "<group>"; };
582AE3112440CA0D00E6733A /* AccountTokenInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTokenInputTests.swift; sourceTree = "<group>"; };
582BB1AE229566420055B6EF /* SettingsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = "<group>"; };
@@ -547,6 +551,8 @@
58AB9DEB2501040C006C5526 /* ConsentViewController.xib */,
5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */,
582BB1B0229569620055B6EF /* CustomNavigationBar.swift */,
+ 58293FB025124117005D0BB5 /* CustomTextField.swift */,
+ 58293FB2251241B3005D0BB5 /* CustomTextView.swift */,
58C6B35D22BBBFE3003C19AD /* Data+HexCoding.swift */,
58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */,
58B9EB142489139B00095626 /* DisplayChainedError.swift */,
@@ -981,11 +987,13 @@
58FAEDEF245069C700CB0F5B /* KeychainAttributes.swift in Sources */,
58CB0EE024B86751001EF0D8 /* MullvadRest.swift in Sources */,
580EE20924B3224200F9D8A1 /* RetryOperation.swift in Sources */,
+ 58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */,
582AE3102440A6CA00E6733A /* AccountTokenInput.swift in Sources */,
58FAEDF7245088E100CB0F5B /* Keychain.swift in Sources */,
5871FB8325498CA20051A0A4 /* Swizzle.swift in Sources */,
58907D9524D17B4E00CFC3F5 /* DisconnectSplitButton.swift in Sources */,
5888AD87227B17950051EB06 /* SelectLocationViewController.swift in Sources */,
+ 58293FB3251241B4005D0BB5 /* CustomTextView.swift in Sources */,
580EE20424B321EC00F9D8A1 /* OperationObserver.swift in Sources */,
58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */,
58A99ED3240014A0006599E9 /* ConsentViewController.swift in Sources */,
diff --git a/ios/MullvadVPN/CustomTextField.swift b/ios/MullvadVPN/CustomTextField.swift
new file mode 100644
index 0000000000..378dde07aa
--- /dev/null
+++ b/ios/MullvadVPN/CustomTextField.swift
@@ -0,0 +1,64 @@
+//
+// CustomTextField.swift
+// MullvadVPN
+//
+// Created by pronebird on 16/09/2020.
+// Copyright © 2020 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+private let kTextFieldCornerRadius = CGFloat(4)
+
+class CustomTextField: UITextField {
+
+ var placeholderTextColor: UIColor = UIColor.TextField.placeholderTextColor {
+ didSet {
+ updatePlaceholderTextColor()
+ }
+ }
+
+ override var placeholder: String? {
+ didSet {
+ updatePlaceholderTextColor()
+ }
+ }
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ textColor = UIColor.TextField.textColor
+ layer.cornerRadius = kTextFieldCornerRadius
+ clipsToBounds = true
+ }
+
+ override func didAddSubview(_ subview: UIView) {
+ super.didAddSubview(subview)
+
+ // Internally `UITextField` adds the placeholder label to its view hierarchy.
+ // Intercept it here and update the text color.
+ if let placeholderLabel = subview as? UILabel {
+ placeholderLabel.textColor = placeholderTextColor
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func textRect(forBounds bounds: CGRect) -> CGRect {
+ return bounds.insetBy(dx: 14, dy: 12)
+ }
+
+ override func editingRect(forBounds bounds: CGRect) -> CGRect {
+ return textRect(forBounds: bounds)
+ }
+
+ private func updatePlaceholderTextColor() {
+ for case let placeholderLabel as UILabel in subviews {
+ placeholderLabel.textColor = placeholderTextColor
+ break
+ }
+ }
+}
diff --git a/ios/MullvadVPN/CustomTextView.swift b/ios/MullvadVPN/CustomTextView.swift
new file mode 100644
index 0000000000..3ce1fa9466
--- /dev/null
+++ b/ios/MullvadVPN/CustomTextView.swift
@@ -0,0 +1,124 @@
+//
+// CustomTextView.swift
+// MullvadVPN
+//
+// Created by pronebird on 16/09/2020.
+// Copyright © 2020 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+private let kTextViewCornerRadius = CGFloat(4)
+
+class CustomTextView: UITextView {
+
+ var roundCorners: Bool = true {
+ didSet {
+ layer.cornerRadius = roundCorners ? kTextViewCornerRadius : 0
+ }
+ }
+
+ /// Placeholder string
+ var placeholder: String? {
+ set {
+ placeholderTextLabel.text = newValue
+ }
+ get {
+ return placeholderTextLabel.text
+ }
+ }
+
+ /// Placeholder text label
+ private let placeholderTextLabel = UILabel()
+
+ /// Placeholder label constraints
+ private var placeholderConstraints = [NSLayoutConstraint]()
+
+ override var textContainerInset: UIEdgeInsets {
+ didSet {
+ setNeedsUpdateConstraints()
+ }
+ }
+
+ override var font: UIFont? {
+ didSet {
+ placeholderTextLabel.font = self.font ?? UIFont.preferredFont(forTextStyle: .body)
+ }
+ }
+
+ /// Placeholder text inset derived from `textContainerInset`
+ private var placeholderTextInset: UIEdgeInsets {
+ var placeholderInset = textContainerInset
+
+ // Offset the placeholder label to match with text view rendering.
+ placeholderInset.top += 0.5
+
+ return placeholderInset
+ }
+
+ override init(frame: CGRect, textContainer: NSTextContainer?) {
+ super.init(frame: frame, textContainer: textContainer)
+
+ placeholderTextLabel.textColor = UIColor.TextField.placeholderTextColor
+ placeholderTextLabel.highlightedTextColor = UIColor.TextField.placeholderTextColor
+ placeholderTextLabel.translatesAutoresizingMaskIntoConstraints = false
+ addSubview(placeholderTextLabel)
+
+ // Create placeholder constraints
+ placeholderConstraints = [
+ placeholderTextLabel.topAnchor.constraint(equalTo: topAnchor),
+ placeholderTextLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
+ placeholderTextLabel.trailingAnchor.constraint(equalTo: trailingAnchor)
+ ]
+ NSLayoutConstraint.activate(placeholderConstraints)
+
+ // Set visual appearance
+ textColor = UIColor.TextField.textColor
+ layer.cornerRadius = kTextViewCornerRadius
+ clipsToBounds = true
+
+ // Set content padding
+ contentInset = .zero
+ textContainerInset = UIEdgeInsets(top: 12, left: 14, bottom: 12, right: 14)
+ self.textContainer.lineFragmentPadding = 0
+
+ // Handle placeholder visibility
+ NotificationCenter.default.addObserver(
+ forName: NSTextStorage.didProcessEditingNotification,
+ object: textStorage,
+ queue: OperationQueue.main) { [weak self] (note) in
+ self?.updatePlaceholderVisibility()
+ }
+
+ updatePlaceholderVisibility()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func updateConstraints() {
+ let textInset = placeholderTextInset
+
+ for constraint in placeholderConstraints {
+ switch constraint.firstAttribute {
+ case .top:
+ constraint.constant = textInset.top
+ case .leading:
+ constraint.constant = textInset.left
+ case .trailing:
+ constraint.constant = textInset.right
+ default:
+ break
+ }
+ }
+
+ super.updateConstraints()
+ }
+
+ private func updatePlaceholderVisibility() {
+ placeholderTextLabel.isHidden = textStorage.length > 0
+ }
+
+}
diff --git a/ios/MullvadVPN/UIColor+Palette.swift b/ios/MullvadVPN/UIColor+Palette.swift
index 9f9e64f27f..f31f67c9fb 100644
--- a/ios/MullvadVPN/UIColor+Palette.swift
+++ b/ios/MullvadVPN/UIColor+Palette.swift
@@ -30,6 +30,11 @@ extension UIColor {
}
}
+ enum TextField {
+ static let placeholderTextColor = UIColor(red: 0.16, green: 0.30, blue: 0.45, alpha: 0.40)
+ static let textColor = UIColor(red: 0.16, green: 0.30, blue: 0.45, alpha: 1.00)
+ }
+
enum AppButton {
static let normalTitleColor = UIColor.white
static let highlightedTitleColor = UIColor.lightGray