Skip to content

Instantly share code, notes, and snippets.

@st235
Last active May 4, 2023 20:28
Show Gist options
  • Save st235/2620413193080866e90ae2c0c1f225d7 to your computer and use it in GitHub Desktop.
Save st235/2620413193080866e90ae2c0c1f225d7 to your computer and use it in GitHub Desktop.
PinView for iOS
import Foundation
extension NSAttributedString {
func height(containerWidth: CGFloat) -> CGFloat {
let rect = self.boundingRect(with: CGSize.init(width: containerWidth, height: CGFloat.greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
return ceil(rect.size.height)
}
func width(containerHeight: CGFloat) -> CGFloat {
let rect = self.boundingRect(with: CGSize.init(width: CGFloat.greatestFiniteMagnitude, height: containerHeight),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
return ceil(rect.size.width)
}
}
import Foundation
import UIKit
@IBDesignable class PinView: UIControl, UIKeyInput {
@IBInspectable var digitCellsSelectionColor: UIColor = UIColor.black {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var digitCellsSelectionStroke: UIColor = UIColor.clear {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var digitCellsFillColor: UIColor = UIColor.black {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var digitsCellStrokeColor: UIColor = UIColor.clear {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var textColor: UIColor = UIColor.white {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var font: UIFont = UIFont.systemFont(ofSize: 32.0).bold() {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var hideDigits: Bool = false {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var digitsCount: Int = 5 {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var digitsCellsInset: Float = 10 {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var digitsCellsStrokeWidth: CGFloat = 0 {
didSet {
setNeedsDisplay()
}
}
private var index = 0
private var digits: [String?] = []
override var canBecomeFirstResponder: Bool {
true
}
override init(frame: CGRect) {
super.init(frame: frame)
self.digits = [String?](repeating: nil, count: digitsCount)
addTarget(self, action: #selector(onTap), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.digits = [String?](repeating: nil, count: digitsCount)
addTarget(self, action: #selector(onTap), for: .touchUpInside)
}
@objc private func onTap(_: AnyObject) {
// When the view is tapped, it becomes the first responder. If the keyboard is not already on screen, it will appear.
becomeFirstResponder()
}
var hasText: Bool {
var empty = true
for digit in digits {
if digit != nil {
empty = false
break
}
}
return empty
}
func insertText(_ text: String) {
if index < digitsCount {
digits[index] = text
index = min(index + 1, digitsCount)
}
setNeedsDisplay()
}
func deleteBackward() {
index = max(index - 1, 0)
digits[index] = nil
setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let digitCellWidth = (bounds.width - CGFloat(digitsCellsInset) * CGFloat(digitsCount - 1)) / CGFloat(digitsCount)
let digitCellHeight = bounds.height
var currentX = CGFloat(0)
for i in 0..<digitsCount {
if i == index {
drawCell(withWidth: digitCellWidth, height: digitCellHeight, atX: currentX, y: 0, withRadius: 12, fillColor: digitCellsSelectionColor, strokeColor: digitCellsSelectionStroke, and: context)
} else {
drawCell(withWidth: digitCellWidth, height: digitCellHeight, atX: currentX, y: 0, withRadius: 12, fillColor: digitCellsFillColor, strokeColor: digitsCellStrokeColor, and: context)
}
if let digit = digits[i] {
if hideDigits {
drawHiddenCharacter(withWidth: digitCellWidth, height: digitCellHeight, atX: currentX, y: 0, and: context)
} else {
draw(text: digit, withWidth: digitCellWidth, height: digitCellHeight, atX: currentX, y: 0, and: context)
}
}
currentX += digitCellWidth + CGFloat(digitsCellsInset)
}
}
private func drawCell(withWidth width: CGFloat, height: CGFloat, atX x: CGFloat, y: CGFloat, withRadius radius: CGFloat, fillColor: UIColor, strokeColor: UIColor, and context: CGContext) {
context.saveGState()
let rect = CGRect(x: x, y: y, width: width, height: height)
let clipPath: CGPath = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath
context.addPath(clipPath)
context.setFillColor(fillColor.cgColor)
context.setStrokeColor(strokeColor.cgColor)
context.setLineWidth(digitsCellsStrokeWidth)
context.drawPath(using: .fillStroke)
context.restoreGState()
}
private func draw(text text: String, withWidth width: CGFloat, height: CGFloat, atX x: CGFloat, y: CGFloat, and context: CGContext) {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attributes: [NSAttributedString.Key : Any] = [
.paragraphStyle: paragraphStyle,
.font: font,
.foregroundColor: textColor
]
let attributedString = NSAttributedString(string: text, attributes: attributes)
let stringRect = CGRect(x: x, y: y + height / 2 - attributedString.height(containerWidth: width) / 2, width: width, height: height)
attributedString.draw(in: stringRect)
}
private func drawHiddenCharacter(withWidth width: CGFloat, height: CGFloat, atX x: CGFloat, y: CGFloat, and context: CGContext) {
context.saveGState()
context.setFillColor(textColor.cgColor)
context.setStrokeColor(UIColor.clear.cgColor)
let radius = CGFloat(6)
context.addEllipse(in: CGRect(x: x + width / 2 - radius, y: y + height / 2 - radius, width: radius * 2, height: radius * 2))
context.drawPath(using: .fillStroke)
context.restoreGState()
}
}
import UIKit
extension UIFont {
func withTraits(_ traits: UIFontDescriptor.SymbolicTraits) -> UIFont {
// create a new font descriptor with the given traits
guard let fd = fontDescriptor.withSymbolicTraits(traits) else {
// the given traits couldn't be applied, return self
return self
}
// return a new font with the created font descriptor
return UIFont(descriptor: fd, size: pointSize)
}
func italics() -> UIFont {
return withTraits(.traitItalic)
}
func bold() -> UIFont {
return withTraits(.traitBold)
}
func boldItalics() -> UIFont {
return withTraits([ .traitBold, .traitItalic ])
}
}
@st235
Copy link
Author

st235 commented May 4, 2023

Screens

Public Pin + Selection Public Pin Hidden Pin
Screen1 Screen2 Screen3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment