Skip to content

Instantly share code, notes, and snippets.

@keitaoouchi
Created July 26, 2017 05:43
Show Gist options
  • Save keitaoouchi/e5f011adcc55c76bfb1804b0e2d5d03a to your computer and use it in GitHub Desktop.
Save keitaoouchi/e5f011adcc55c76bfb1804b0e2d5d03a to your computer and use it in GitHub Desktop.
SMSのPINコード認証したりする感じのViewController
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