Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code input control improvements. #88

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion DP3TApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@
8E81CCCF241FD813006F2437 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8E81CCCE241FD813006F2437 /* SnapKit */; };
8EB23D78245AF2C30073E83A /* RandomGenerators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EB23D77245AF2C30073E83A /* RandomGenerators.swift */; };
A6BBA110246BFCBC00E42EE7 /* UIDevice+ScreenType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6BBA10F246BFCBC00E42EE7 /* UIDevice+ScreenType.swift */; };
A6C03418248788E100B425ED /* CodeSingleControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C03417248788E100B425ED /* CodeSingleControl.swift */; };
A6C03419248788E100B425ED /* CodeSingleControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C03417248788E100B425ED /* CodeSingleControl.swift */; };
A6C0341B2487890B00B425ED /* CodeTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C0341A2487890B00B425ED /* CodeTextField.swift */; };
A6C0341C2487890B00B425ED /* CodeTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C0341A2487890B00B425ED /* CodeTextField.swift */; };
AACFA6A3243088A4005595E6 /* NSAnimatedGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AACFA6A2243088A4005595E6 /* NSAnimatedGraphView.swift */; };
AAF73663242F2DC90051E34A /* NSModuleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF73662242F2DC90051E34A /* NSModuleHeaderView.swift */; };
AAF73666242F2EA00051E34A /* NSBegegnungenModuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF73665242F2EA00051E34A /* NSBegegnungenModuleView.swift */; };
Expand Down Expand Up @@ -449,6 +453,8 @@
8E81CCB1241FCC7F006F2437 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8EB23D77245AF2C30073E83A /* RandomGenerators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomGenerators.swift; sourceTree = "<group>"; };
A6BBA10F246BFCBC00E42EE7 /* UIDevice+ScreenType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+ScreenType.swift"; sourceTree = "<group>"; };
A6C03417248788E100B425ED /* CodeSingleControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeSingleControl.swift; sourceTree = "<group>"; };
A6C0341A2487890B00B425ED /* CodeTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeTextField.swift; sourceTree = "<group>"; };
AACFA6A2243088A4005595E6 /* NSAnimatedGraphView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAnimatedGraphView.swift; sourceTree = "<group>"; };
AAF73662242F2DC90051E34A /* NSModuleHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSModuleHeaderView.swift; sourceTree = "<group>"; };
AAF73665242F2EA00051E34A /* NSBegegnungenModuleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSBegegnungenModuleView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -591,8 +597,10 @@
isa = PBXGroup;
children = (
6E1771532440B09A0008D73D /* NSCodeInputViewController.swift */,
6E1771552440B5140008D73D /* NSCodeInputControl.swift */,
6EA49E2A2444410D009EFCAB /* NSNoCodeInformationViewController.swift */,
6E1771552440B5140008D73D /* NSCodeInputControl.swift */,
A6C03417248788E100B425ED /* CodeSingleControl.swift */,
A6C0341A2487890B00B425ED /* CodeTextField.swift */,
);
path = CodeInput;
sourceTree = "<group>";
Expand Down Expand Up @@ -1482,6 +1490,7 @@
242D21FC245C4BD8005DAEA8 /* NSLabelType.swift in Sources */,
242D21FD245C4BD8005DAEA8 /* UBPListValue.swift in Sources */,
242D21FE245C4BD8005DAEA8 /* NSUnderlinedButton.swift in Sources */,
A6C03419248788E100B425ED /* CodeSingleControl.swift in Sources */,
242D21FF245C4BD8005DAEA8 /* AuthorizationModels.swift in Sources */,
242D2200245C4BD8005DAEA8 /* NSModuleHeaderView.swift in Sources */,
242D2201245C4BD8005DAEA8 /* NSModuleBaseView.swift in Sources */,
Expand Down Expand Up @@ -1546,6 +1555,7 @@
242D2238245C4BD8005DAEA8 /* NSHeaderArcView.swift in Sources */,
242D2239245C4BD8005DAEA8 /* ReportingManager.swift in Sources */,
242D223A245C4BD8005DAEA8 /* NSOnboardingContentViewController.swift in Sources */,
A6C0341C2487890B00B425ED /* CodeTextField.swift in Sources */,
242D223B245C4BD8005DAEA8 /* NSHeaderErrorView.swift in Sources */,
242D223C245C4BD8005DAEA8 /* NSInformBottomButtonViewController.swift in Sources */,
242D223D245C4BD8005DAEA8 /* NSDebugscreenViewController.swift in Sources */,
Expand Down Expand Up @@ -1614,6 +1624,7 @@
DCA3FFB424502D370003F5AD /* NSTracingErrorView.swift in Sources */,
6E7C0D1E242CE02D0017C4F9 /* NSLabelType.swift in Sources */,
DC702B0B243F6FAE0066C773 /* UBPListValue.swift in Sources */,
A6C03418248788E100B425ED /* CodeSingleControl.swift in Sources */,
DCA3FFB6245048A50003F5AD /* NSUnderlinedButton.swift in Sources */,
242D227D245D6581005DAEA8 /* Endpoint.swift in Sources */,
2462BA152451FD150046906D /* AuthorizationModels.swift in Sources */,
Expand Down Expand Up @@ -1678,6 +1689,7 @@
2443947B2445F062003ED582 /* NSHeaderArcView.swift in Sources */,
2462BA1B24521CE70046906D /* ReportingManager.swift in Sources */,
DC175E402430C4C700BD2AD6 /* NSOnboardingContentViewController.swift in Sources */,
A6C0341B2487890B00B425ED /* CodeTextField.swift in Sources */,
DC746D582451D50B009426B1 /* NSHeaderErrorView.swift in Sources */,
24780B30242F3EAF003BB26C /* NSInformBottomButtonViewController.swift in Sources */,
6E3F65F72449B61A00980A4E /* NSDebugscreenViewController.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright (c) 2020 Ubique Innovation AG <https://www.ubique.ch>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* SPDX-License-Identifier: MPL-2.0
*/

import UIKit

protocol CodeSingleControlDelegate: class {
func fillFieldsWith(_ text: String, for startControl: CodeSingleControl)
func change(_ control: CodeSingleControl)
func shouldJumpToNextField()
func shouldJumpToPreviousField()
func shouldCheckSendAllowed()
}

class CodeSingleControl: UIView {
weak var delegate: CodeSingleControlDelegate?
fileprivate let textField = CodeTextField()
private let emptyCharacter = "\u{200B}"

private var hadText: Bool = false
fileprivate var accessibilityIndex: Int

init(accessibilityIndex: Int) {
self.accessibilityIndex = accessibilityIndex
super.init(frame: .zero)
setup()

textField.text = UIAccessibility.isVoiceOverRunning ? "" : emptyCharacter
textField.accessibilityTraits = .staticText
accessibilityTraits = .staticText
isAccessibilityElement = true
}

override func accessibilityElementDidBecomeFocused() {
textField.becomeFirstResponder()
}

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

// MARK: - Checks/Code

public func characterIsSet() -> Bool {
(textField.text ?? "").replacingOccurrences(of: emptyCharacter, with: "").count > 0
}

public func character() -> String? {
textField.text?.replacingOccurrences(of: emptyCharacter, with: "")
}

public func clearInput() {
textField.resignFirstResponder()
textField.text = emptyCharacter
}

// MARK: - Copy&paste

public func setDigit(digit: String) {
textField.text = digit
}

// MARK: - First responder

override func becomeFirstResponder() -> Bool {
changeBorderStyle(isSelected: true)
return textField.becomeFirstResponder()
}

override func resignFirstResponder() -> Bool {
changeBorderStyle(isSelected: false)
return textField.resignFirstResponder()
}

func reset() {
textField.text = emptyCharacter
hadText = false
}

private func changeBorderStyle(isSelected: Bool) {
backgroundColor = UIColor.ns_backgroundSecondary

if isSelected {
layer.borderWidth = 2.0
layer.borderColor = UIColor.ns_purple.cgColor
} else {
layer.borderWidth = 1.0
layer.borderColor = UIColor(ub_hexString: "#e5e5e5")?.cgColor
}
}

// MARK: - Setup

private func setup() {
snp.makeConstraints { make in
make.height.equalTo(36)
}

addSubview(textField)

textField.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: -2, bottom: 0, right: -2))
}

changeBorderStyle(isSelected: false)

textField.font = NSLabelType.title.font
textField.textAlignment = .center
textField.textColor = .ns_text
textField.keyboardType = .numberPad

textField.addTarget(self, action: #selector(editingChanged(sender:)), for: .editingChanged)
textField.delegate = self
textField.codeTextFieldDelegate = self
}

@objc private func editingChanged(sender: UITextField) {
if let text = sender.text, text.count >= 1 {
sender.text = String(text.dropFirst(text.count - 1))
hadText = true
delegate?.shouldJumpToNextField()
} else if let text = sender.text, text.count == 0 {
sender.text = emptyCharacter
if !hadText {
delegate?.shouldJumpToPreviousField()
} else {
delegate?.shouldCheckSendAllowed()
}

hadText = false
}
}
}

// MARK: - UITextFieldDelegate

extension CodeSingleControl: UITextFieldDelegate {

func textField(_: UITextField, shouldChangeCharactersIn _: NSRange, replacementString string: String) -> Bool {
return string != " "
}

func textFieldDidBeginEditing(_: UITextField) {
delegate?.change(self)
changeBorderStyle(isSelected: true)
}

func textFieldDidEndEditing(_: UITextField) {
changeBorderStyle(isSelected: false)
}
}

// MARK: - CodeTextFieldDelegate

extension CodeSingleControl: CodeTextFieldDelegate {

func fillWith(_ text: String) {
delegate?.fillFieldsWith(text, for: self)
}

func getAccessibilityIndexIndex() -> Int {
return accessibilityIndex
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2020 Ubique Innovation AG <https://www.ubique.ch>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* SPDX-License-Identifier: MPL-2.0
*/

import UIKit

protocol CodeTextFieldDelegate: class {
func fillWith(_ text: String)
func getAccessibilityIndexIndex() -> Int
}

class CodeTextField: UITextField {

weak var codeTextFieldDelegate: CodeTextFieldDelegate?

override func paste(_: Any?) {
let pasteboard = UIPasteboard.general

if let text = pasteboard.string {
codeTextFieldDelegate?.fillWith(text)
}
}

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

override var accessibilityLabel: String? {
get {

guard let accessibilityIndex = codeTextFieldDelegate?.getAccessibilityIndexIndex() else {
return ""
}

if let text = text, !text.isEmpty {
return "accessibility_\(accessibilityIndex)nd".ub_localized + "accessibility_code_input_textfield".ub_localized
} else {
return "accessibility_\(accessibilityIndex)nd".ub_localized + "accessibility_code_input_textfield_empty".ub_localized
}
}
set {
super.accessibilityLabel = newValue
}
}

override var accessibilityHint: String? {
get {
return "accessibility_code_input_hint".ub_localized
}
set {
super.accessibilityHint = newValue
}
}
}
Loading