Swift/UIView

[SWIFT]커스텀 숫자키패드 한줄배열 및 두줄배열/ UITextField 커스텀 키보드

삽질중 2023. 6. 9. 10:41

프로젝트에서 사용해야되서 만든 숫자키패드 커스텀 UI 1줄 또는 2줄로 배열됨

import UIKit

let NUMBER_TAG  = 1
let BACK_TAG    = 99
let CLOSE_TAG   = 100

let VERTICAL_HEIGHT: CGFloat = 130//117      // 세로모드 키패드 사이즈 (2 줄)
let HORIZONTAL_HEIGHT: CGFloat = 70     // 가로모드 키패드 사이즈 (1 줄)

let BTN_WIDTH: CGFloat = 50.0
let BTN_HEIGHT: CGFloat = 45.0

let V_WIDTH_PADING: CGFloat = 10.0
let V_HEIGHT_PADING: CGFloat = 5.0

let H_WIDTH_PADING: CGFloat = 5.0
let H_HEIGHT_PADING: CGFloat = 5.0


enum PadType {
    case HORIZONTAL     // 1줄
    case VERTICAL       // 2줄
}

protocol CustomNumberKeyPadViewDelegate: class {
    func numberKey(number key: String)
    func backSpaceKey()
    func CloseKey()
}

@IBDesignable
class CustomNumberKeyPadView: UIView {
    
    weak var delegate: CustomNumberKeyPadViewDelegate?
    
    var numberPad = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
    var funcPad = ["Delete", "Close"]
    
    @IBInspectable
    public var fontSize: CGFloat = 12.0 {
        didSet {
            
        }
    }
    
    //for i in 0..<numberPad.count {
    lazy var numberButtons: [IBCusomButton] = { [weak self] in
        
        var padButtons: [IBCusomButton] = []
        
        for i in 0..<(self?.numberPad.count)! {
            let padBtn = IBCusomButton(frame: CGRect(x: 0, y: 0, width: BTN_WIDTH, height: BTN_HEIGHT))
            
            padBtn.cornerRadius = 6.0
            padBtn.setTitle(numberPad[i], for: .normal)

            padBtn.fontsize = 16.0
            padBtn.fontStyleNumber = 1
            
            padBtn.setTitleColor(UIColor.white, for: .normal)
            padBtn.setTitleColor(UIColor.white, for: .selected)
            padBtn.setTitleColor(UIColor.white, for: .highlighted)
            
            padBtn.isGradientUseHighlight = true
            padBtn.isGradientBackground = true
            padBtn.isGradientBorder = true
            
            padBtn.gradientStartPoint = CGPoint(x: 0.0, y: 0.29)
            padBtn.gradientEndPoint = CGPoint(x: 0.67,y: 1.0)
            
            padBtn.gradientBGColors = [UIColor.pinkishGrey,UIColor.brownishGreyTwo]
            padBtn.gradientBorderColors = [UIColor.greyish, UIColor.warmGrey]
            padBtn.gradientHightlightedColors = [UIColor.azureTwo, UIColor.aquamarine]
            
            padBtn.borderWidth = 2
            padBtn.gradientBorderColors = [UIColor.greyish, UIColor.warmGrey]
            
            padBtn.contentVerticalAlignment = .center
            padBtn.tag = i + NUMBER_TAG
            
            padBtn.addTarget(self, action: #selector(clickButton(_:)), for: .touchUpInside)
            
            padButtons.append(padBtn as! IBCusomButton)
            self?.addSubview(padBtn)
            
        }
        return padButtons
    }()
    
    lazy var deleteButton: UIButton = { [weak self] in
        let deleteBtn = UIButton(frame: CGRect(x: 0, y: 0, width: BTN_WIDTH, height: BTN_HEIGHT))
        
//        let deleteBtn = UIButton(frame: .zero)
        
        deleteBtn.layer.cornerRadius = 6.0
        deleteBtn.setImage(UIImage(named: "Keyboard_Back_Image"), for: .normal)
        deleteBtn.setTitle("Delete".localized, for: .normal)
        deleteBtn.titleLabel?.font = UIFont.notoSansFont(forFont: .NotoSansCJKkrRegular, size: 10.0)
        
        deleteBtn.setTitleColor(UIColor.white, for: .normal)
        deleteBtn.setTitleColor(UIColor.white, for: .selected)
        
        deleteBtn.backgroundColor = UIColor.hexStringToUIColor(hex: "#000000", alpha: 0.23)
        deleteBtn.borderColor(width: 1.0, borderColor: UIColor.white)
        
        deleteBtn.tag = BACK_TAG
        self?.alignTextBelow(button: deleteBtn, spacing: 2.0)
        
        deleteBtn.addTarget(self, action: #selector(clickButton(_:)), for: .touchUpInside)
        
        self?.addSubview(deleteBtn)
        return deleteBtn
    }()
    
    lazy var closeButton: UIButton = { [weak self] in
        let closeBtn = UIButton(frame: CGRect(x: 0, y: 0, width: BTN_WIDTH, height: BTN_HEIGHT))
//        let closeBtn = UIButton(frame: .zero)
        
        closeBtn.layer.cornerRadius = 6.0
        closeBtn.setImage(UIImage(named: "Keyboard_Close_Image"), for: .normal)
        closeBtn.setTitle("Close".localized, for: .normal)
        closeBtn.titleLabel?.font = UIFont.notoSansFont(forFont: .NotoSansCJKkrRegular, size: 10.0)
        
        closeBtn.setTitleColor(UIColor.white, for: .normal)
        closeBtn.setTitleColor(UIColor.white, for: .selected)
        
        closeBtn.backgroundColor = UIColor.hexStringToUIColor(hex: "#000000", alpha: 0.23)
        closeBtn.borderColor(width: 1.0, borderColor: UIColor.white)
        
        closeBtn.tag = CLOSE_TAG
        self?.alignTextBelow(button: closeBtn, spacing: 2.0)
        
        closeBtn.addTarget(self, action: #selector(clickButton(_:)), for: .touchUpInside)
        
        self?.addSubview(closeBtn)
        return closeBtn
    }()
    
    
    
    deinit {
//        log(direction: .ETC, ofType: self, datas: "CustomNumberKeyPadView deinit")
//        panelButtons.removeAll()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
//        setupView()
        backgroundColor = .clear
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
//        backgroundColor = UIColor.aquaBlue
//        addButtonView()
        
        setupView()
        
    }
    
    func setupView() {
        
        let blurEffect:UIBlurEffect = UIBlurEffect(style: .dark)
        let blurredEffectView = UIVisualEffectView(effect: blurEffect)
        blurredEffectView.alpha = 1.0
        self.insertSubview(blurredEffectView, at: 0)
        
        blurredEffectView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            blurredEffectView.topAnchor.constraint(equalTo: self.topAnchor),
            blurredEffectView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            blurredEffectView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            blurredEffectView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
        ])
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
//        self.backgroundColor = UIColor.clear
    }
    
    
    func alignTextBelow(button: UIButton, spacing: CGFloat) {
        
        //let spacing: CGFloat = 6.0
        if button.imageView != nil {
            if let imageView = button.imageView   {
                
                let imageSize = imageView.frame.size
                
                button.titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageSize.width, bottom: -(imageSize.height + spacing), right: 0)
                
                let titleSize = button.titleLabel!.frame.size
                
                button.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0, bottom: 0, right: -titleSize.width)
            }
        }
    }
    
    func getPadViewFit(type: PadType) -> CGFloat {
        
        if type == .VERTICAL {
            // 세로모드 2라인
            let contentWidth = BTN_WIDTH * (CGFloat(numberButtons.count/2) + 1)
            let padingWidth = V_WIDTH_PADING * (CGFloat(numberButtons.count/2) + 1 - 1)
            
            return contentWidth + padingWidth
        } else {
            //  가로모드 1라인
            let contentWidth = BTN_WIDTH * (CGFloat(numberButtons.count) + 2)
            let padingWidth = H_WIDTH_PADING * (CGFloat(numberButtons.count) + 2 - 1)
            
            return contentWidth + padingWidth
        }
    }
    
    func verticalLayerButtons() {
      
        let contentWidth = getPadViewFit(type: .VERTICAL)
        var start_x = (self.frame.width - contentWidth) / 2
//        var start_y: CGFloat = 5.0
        var start_y = (self.frame.height - (BTN_HEIGHT * 2 + V_WIDTH_PADING)) / 2
        let linebreak = start_x
        
//        print("<==> Portrait frame width \(self.frame.width) -  getPadViewFit \(getPadViewFit(type: .VERTICAL)) / start_x \(start_x)")
        
        for i in 0..<numberButtons.count {
            
            numberButtons[i].frame = CGRect(x: start_x, y: start_y, width: BTN_WIDTH, height: BTN_HEIGHT)
            
            start_x += numberButtons[i].frame.width + V_WIDTH_PADING
            
            
            if i == 4 {
                // 1Line : 1...5 Close 버튼 추가
                closeButton.frame = CGRect(x: start_x, y: start_y, width: BTN_WIDTH, height: BTN_HEIGHT)
                
                start_x = linebreak
                start_y += closeButton.frame.height + V_HEIGHT_PADING
            } else if i == 9{
                // 2Line : 6...10 Delete 버튼 추가
                deleteButton.frame = CGRect(x: start_x, y: start_y, width: BTN_WIDTH, height: BTN_HEIGHT)
                
            }
        }
    }
    
    func horizontalLayerButtons() {
        
        let leftPadding = UIApplication.getsafeAreaLeftMagin()
        let rightPadding = UIApplication.getsafeAreaRightMagin()

        let padding = CGFloat(leftPadding + rightPadding)
        
        let contentWidth = getPadViewFit(type: .HORIZONTAL)
        var start_x = (self.frame.width + padding - contentWidth) / 2
        let start_y = (self.frame.height - (BTN_HEIGHT * 1)) / 2
//        let start_y: CGFloat = 5.0
        
        for i in 0..<numberButtons.count {
            numberButtons[i].frame = CGRect(x: start_x, y: start_y, width: BTN_WIDTH, height: BTN_HEIGHT)
            
            start_x += numberButtons[i].frame.width + H_WIDTH_PADING
            
            if i == 9{
                // 1Line : 1...10  Delete,Close 버튼 추가
                deleteButton.frame = CGRect(x: start_x, y: start_y, width: BTN_WIDTH, height: BTN_HEIGHT)
                
                start_x += deleteButton.frame.width + H_WIDTH_PADING
                
                closeButton.frame = CGRect(x: start_x, y: start_y, width: BTN_WIDTH, height: BTN_HEIGHT)
            }
        }
    }
    
    @objc func clickButton(_ sender: UIButton) {
        
        guard  let delegate = self.delegate else {
            print("CustomNumberKeyPadView Delegate NULL!!!!")
            return
        }
        
        if sender.tag == CLOSE_TAG {
            delegate.CloseKey()
        }
        
        if sender.tag == BACK_TAG {
            delegate.backSpaceKey()
        }
    
        if let number = self.numberPad[safe: sender.tag - 1] {
            delegate.numberKey(number: number)
        }
    }
}

예제) 스토리보드에 CustomNumberKeyPadView 설정 후 사용 (IBDesignable 사용)

extension KeyboardViewController: CustomNumberKeyPadViewDelegate {
    func numberKey(number key: String) {
        print("KeyboardSendBarViewController numberKey : \(key)")
        //숫자 8자리까지 허용
        if giftQuantityTextField.text!.count >= 8 {
            return
        }
        
        
        let currentString = giftQuantityTextField.text! + key
        
        giftQuantityTextField.text = currentString
    }
    
    func backSpaceKey() {
        giftQuantityTextField.text = String(giftQuantityTextField.text!.dropLast(1))
    }
    
    func CloseKey() {
        // UI 숨김처리
        keypadCloseClosure?()
        
    }
}

class KeyboardViewController: UIViewController {
	@IBOutlet weak var giftQuantityTextField: UITextField! {
        didSet {
            giftQuantityTextField.isEnabled = false
            giftQuantityTextField.text = "100"
        }
    }
    
	@IBOutlet weak var numberKeypadView: CustomNumberKeyPadView!
    var keypadCloseClosure: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        numberKeypadView.delegate = self
    }
}

 

[TextField 에 커스텀 키보드 적용하기]

텍스트 필드 포커스 되면 기본 키보드가 아닌 커스텀 키보드가 올라오게 된다.

lazy var numberPadView: CustomNumberKeyPadView = { [weak self] in
        let padView = CustomNumberKeyPadView(frame: .zero)
        
        padView.delegate = self
        giftQuantityTextField.inputView = padView
//        giftQuantityTextField.inputView = UIView(frame: .zero)
//        giftQuantityTextField.inputAccessoryView = padView
        
        return padView
}()