Skip to content

Instantly share code, notes, and snippets.

@kyungpyoda
Last active June 11, 2021 05:03
Show Gist options
  • Save kyungpyoda/00fa050c6aac1d34d595863aac01a038 to your computer and use it in GitHub Desktop.
Save kyungpyoda/00fa050c6aac1d34d595863aac01a038 to your computer and use it in GitHub Desktop.
[Swift] iOS awesome Coupon Code TextField
//
// CouponCodeTextField.swift
//
// Created by 홍경표 on 2021/06/10.
//
import UIKit
// Not necessary. Replacable with other ways.
import SnapKit // convenient to handle constraint
import Then // convenient to set properties right after initialized
// ------------
final class CouponCodeTextField: UITextField {
private enum Constants {
static let sectionSpacing: Double = 0.02 // proportional
static let cursorBlinkInterval: Double = 0.4
static let blinkColor: UIColor = .green // I think black is better, though. :)
static let filledLineColor: UIColor = .black
static let emptyLineColor: UIColor = .lightGray
static let textColor: UIColor = .gray
}
let slotCount: Int
let sectionCount: Int
private var sectionSizes: [Int] {
var temp = [Int](repeating: (slotCount / sectionCount), count: sectionCount)
temp[sectionCount - 1] += slotCount % sectionCount
return temp
}
private var digitLabels: [UILabel] = []
private var bottomLines: [UIView] = []
private var cursorColorFlag: Bool = true
private weak var timer: Timer?
init(slotCount: Int, sectionCount: Int = 0) {
self.slotCount = slotCount
self.sectionCount = sectionCount
super.init(frame: .zero)
timer = Timer.scheduledTimer(withTimeInterval: Constants.cursorBlinkInterval, repeats: true, block: { [weak self] timer in
guard let self = self,
let currentIndex = self.text?.count,
currentIndex < self.slotCount else { return }
self.bottomLines[currentIndex].backgroundColor = self.cursorColorFlag ? Constants.blinkColor : Constants.emptyLineColor
self.cursorColorFlag.toggle()
})
configure()
}
required init?(coder: NSCoder) {
fatalError("NO WAY")
}
deinit {
timer?.invalidate()
timer = nil
}
private func configure() {
self.tintColor = .clear
self.textColor = .clear
self.textAlignment = .center
self.keyboardType = .asciiCapable
self.autocapitalizationType = .allCharacters
self.addTarget(self, action: #selector(textDidChange), for: .editingChanged)
let tapRecognizer = UITapGestureRecognizer()
tapRecognizer.addTarget(self, action: #selector(becomeFirstResponder))
self.addGestureRecognizer(tapRecognizer)
let container = UIStackView().then {
$0.axis = .horizontal
$0.distribution = .equalSpacing
$0.alignment = .fill
}
sectionSizes.forEach { [unowned self] sectionSize in
let sectionStackView = UIStackView().then {
$0.axis = .horizontal
$0.distribution = .fillEqually
$0.alignment = .fill
}
for _ in 0..<sectionSize {
let label = UILabel().then {
$0.textAlignment = .center
$0.font = .systemFont(ofSize: UIFont.preferredFont(forTextStyle: .title3).pointSize, weight: .bold)
$0.adjustsFontSizeToFitWidth = true
$0.textColor = Constants.textColor
$0.text = " "
}
let bottomLine = UIView()
bottomLine.backgroundColor = Constants.emptyLineColor
label.addSubview(bottomLine)
bottomLine.snp.makeConstraints {
$0.height.equalTo(2)
$0.leading.trailing.bottom.equalToSuperview()
}
sectionStackView.addArrangedSubview(label)
digitLabels.append(label)
bottomLines.append(bottomLine)
}
container.addArrangedSubview(sectionStackView)
sectionStackView.snp.makeConstraints {
$0.width.equalToSuperview().multipliedBy(Double(sectionSize) / Double(slotCount) - Constants.sectionSpacing)
}
}
self.addSubview(container)
container.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
@objc private func textDidChange() {
text = text?.uppercased()
.filter {
$0.isNumber || $0.isUppercase
}
.prefix(slotCount).description
guard let text = text else { return }
var iter = text.makeIterator()
for i in 0..<slotCount {
if let c = iter.next() {
digitLabels[i].text = String(c)
} else {
digitLabels[i].text = " "
}
}
bottomLines[0..<text.count].forEach {
$0.backgroundColor = Constants.filledLineColor
}
if text.count < slotCount {
bottomLines[text.count..<slotCount].forEach {
$0.backgroundColor = Constants.emptyLineColor
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment