Created
July 26, 2017 05:43
-
-
Save keitaoouchi/e5f011adcc55c76bfb1804b0e2d5d03a to your computer and use it in GitHub Desktop.
SMSのPINコード認証したりする感じのViewController
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import RxSwift | |
final class SMSVerificationViewController: UIViewController { | |
@IBOutlet weak var digits1TextField: UITextField! | |
@IBOutlet weak var digits2TextField: UITextField! | |
@IBOutlet weak var digits3TextField: UITextField! | |
@IBOutlet weak var digits4TextField: UITextField! | |
@IBOutlet weak var resendButton: UIButton! | |
@IBOutlet weak var submitButton: UIButton! | |
// swiftlint:disable implicitly_unwrapped_optional | |
var phoneNumber: String! | |
let pinCode: Variable<String?> = Variable<String?>(nil) | |
let disposeBag = DisposeBag() | |
static func make() -> SMSVerificationViewController { | |
// swiftlint:disable force_unwrapping | |
return R.storyboard.sMSVerification.instantiateInitialViewController()! | |
} | |
} | |
// MARK: - lifecycles | |
extension SMSVerificationViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
self.digits.forEach { $0.delegate = self } | |
pinCode.asDriver().distinctUntilChanged { a, b in | |
if let a = a, let b = b { | |
return a == b | |
} else if a == nil && b == nil { | |
return true | |
} | |
return false | |
}.drive(onNext: { [weak self] code in | |
if code != nil { | |
self?.submitButton.isEnabled = true | |
} else { | |
self?.submitButton.isEnabled = false | |
} | |
}).addDisposableTo(self.disposeBag) | |
} | |
override func viewDidAppear(_ animated: Bool) { | |
super.viewDidAppear(animated) | |
self.updateStyles() | |
self.firstEmptyTextField().becomeFirstResponder() | |
} | |
} | |
// MARK: - UITextFieldDelegate | |
extension SMSVerificationViewController: UITextFieldDelegate { | |
func textFieldDidBeginEditing(_ textField: UITextField) { | |
let firstEmptyField = firstEmptyTextField() | |
if let text = textField.text, textField != firstEmptyField && text.isEmpty { | |
firstEmptyField.becomeFirstResponder() | |
textField.resignFirstResponder() | |
} | |
} | |
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { | |
if textField.text == nil { return true } | |
if string.isEmpty { | |
self.setStyle(textField: textField, state: .empty) | |
let emptifiedField = lastFulfilledTextField() | |
emptifiedField.text = "" | |
lastFulfilledTextField().becomeFirstResponder() | |
self.pinCode.value = nil | |
} else { | |
let fulfilledField = firstEmptyTextField() | |
fulfilledField.text = string | |
if fulfilledField == digits4TextField { | |
view.endEditing(true) | |
fulfilledField.resignFirstResponder() | |
self.onFullFilled() | |
} | |
} | |
self.updateStyles() | |
return false | |
} | |
} | |
private extension SMSVerificationViewController { | |
var digits: [UITextField] { | |
return [ | |
digits1TextField, | |
digits2TextField, | |
digits3TextField, | |
digits4TextField | |
] | |
} | |
func firstEmptyTextField() -> UITextField { | |
for field in digits { | |
if let text = field.text, text.isEmpty { | |
return field | |
} | |
} | |
return digits4TextField | |
} | |
func lastFulfilledTextField() -> UITextField { | |
for field in digits.reversed() { | |
if let text = field.text, !text.isEmpty { | |
return field | |
} | |
} | |
return digits1TextField | |
} | |
func onFullFilled() { | |
let code = digits.map { $0.text ?? "" }.joined() | |
if code.characters.count == 4 { | |
pinCode.value = code | |
} | |
} | |
func setStyle(textField: UITextField, state: EditState) { | |
textField.textColor = .white | |
textField.layer.borderWidth = 0.0 | |
textField.layer.borderColor = UIColor.black.cgColor | |
textField.layer.backgroundColor = nil | |
textField.layer.removeAllAnimations() | |
textField.layer.cornerRadius = 5.0 | |
textField.layer.masksToBounds = false | |
textField.layer.shadowOpacity = 0.0 | |
textField.layer.shadowRadius = 2.0 | |
textField.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) | |
let bgAnimation = CABasicAnimation(keyPath: "borderColor") | |
let shadowColorAnimation = CABasicAnimation(keyPath: "shadowColor") | |
let shadowOpacityAnimation = CABasicAnimation(keyPath: "shadowOpacity") | |
bgAnimation.fromValue = textField.layer.backgroundColor | |
shadowColorAnimation.fromValue = textField.layer.shadowColor | |
shadowOpacityAnimation.fromValue = textField.layer.shadowOpacity | |
let animations = CAAnimationGroup() | |
animations.animations = [bgAnimation, shadowColorAnimation, shadowOpacityAnimation] | |
animations.duration = 0.33 | |
animations.isRemovedOnCompletion = true | |
animations.fillMode = kCAFillModeRemoved | |
animations.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) | |
switch state { | |
case .empty: | |
textField.layer.borderWidth = 0.0 | |
textField.layer.shadowColor = nil | |
textField.layer.shadowOpacity = 0.0 | |
case .fulfilled: | |
textField.layer.borderWidth = 1.0 | |
textField.layer.borderColor = UIColor.orange.cgColor | |
textField.layer.shadowColor = UIColor.orange.cgColor | |
textField.layer.shadowOpacity = 0.3 | |
bgAnimation.toValue = UIColor.orange.cgColor | |
shadowColorAnimation.toValue = UIColor.orange.cgColor | |
shadowOpacityAnimation.toValue = 0.3 | |
textField.layer.add(animations, forKey: "animations") | |
case .focus: | |
textField.layer.borderWidth = 1.0 | |
textField.layer.borderColor = UIColor.blue.cgColor | |
textField.layer.shadowColor = UIColor.black.cgColor | |
textField.layer.shadowOpacity = 0.3 | |
bgAnimation.toValue = UIColor.blue.cgColor | |
shadowColorAnimation.toValue = UIColor.black.cgColor | |
shadowOpacityAnimation.toValue = 0.3 | |
textField.layer.add(animations, forKey: "animations") | |
} | |
} | |
func updateStyles() { | |
digits.forEach { field in | |
guard let text = field.text else { return } | |
if text.isEmpty { | |
self.setStyle(textField: field, state: .empty) | |
} else { | |
self.setStyle(textField: field, state: .fulfilled) | |
} | |
} | |
let firstEmptyField = self.firstEmptyTextField() | |
if let text = firstEmptyField.text, text.isEmpty { | |
self.setStyle(textField: firstEmptyField, state: .focus) | |
} | |
} | |
enum EditState: Int { | |
case fulfilled = 0 | |
case empty | |
case focus | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment