// // KMSecureTextFiled.swift // PDF Reader Pro // // Created by tangchao on 2022/11/28. // import Cocoa enum KMSecureTextFiledMode: Int { case ciphertext = 0 /// 密文 case plaintext = 1 /// 明文 } enum KMSecureTextFiledViewMode: Int { case never = 0 case whileEditing case unlessEditing case always } typealias KMSecureTextFiledFirstResponderHandler = (Bool)->() private class KMSecureTextFiled_TextFiled: NSTextField { var firstResponderHandler: KMSecureTextFiledFirstResponderHandler? override func becomeFirstResponder() -> Bool { let result = super.becomeFirstResponder() guard let callback = firstResponderHandler else { return result } callback(result) return result } } private class KMSecureTextFiled_SecureTextFiled: NSSecureTextField { var firstResponderHandler: KMSecureTextFiledFirstResponderHandler? override func becomeFirstResponder() -> Bool { let result = super.becomeFirstResponder() guard let callback = firstResponderHandler else { return result } callback(result) return result } } typealias KMSecureTextFiledValueDidChange = (_ view: KMSecureTextFiled, _ string: String) -> Void typealias KMSecureTextFiledBecomeFirstResponderHandler = (NSView)->() typealias KMSecureTextFiledEnterAction = () -> () class KMSecureTextFiled: NSView { var enterAction: KMSecureTextFiledEnterAction? var backgroundView = NSView() private var textFiled = KMSecureTextFiled_TextFiled() private var secureTextField = KMSecureTextFiled_SecureTextFiled() var mode: KMSecureTextFiledMode = .ciphertext private var _rightView: NSView? var rightView: NSView? { get { return self._rightView } set { if self._rightView != newValue { if let view = self._rightView { view.removeFromSuperview() } self._rightView = newValue } if let view = self._rightView { self.addSubview(view) self.layoutSubtreeIfNeeded() } self.updateRightViewStateIfNeed(editing: self.kmEnabled) } } private var _rightViewMode = KMSecureTextFiledViewMode.whileEditing var rightViewMode: KMSecureTextFiledViewMode { get { return self._rightViewMode } set { self._rightViewMode = newValue self.updateRightViewStateIfNeed(editing: self.kmEnabled) } } private var _enabled = true var kmEnabled: Bool { get { return self._enabled } set { self._enabled = newValue textFiled.isEnabled = newValue secureTextField.isEnabled = newValue self.updateRightViewStateIfNeed(editing: newValue) } } var placeholderString: String { get { if (self.mode == .plaintext) { return self.textFiled.placeholderString ?? "" } else { return self.secureTextField.placeholderString ?? "" } } set { if (self.mode == .plaintext) { self.textFiled.placeholderString = newValue } else { self.secureTextField.placeholderString = newValue } } } var valueDidChange: KMSecureTextFiledValueDidChange? var becomeFirstResponderHandler: KMSecureTextFiledBecomeFirstResponderHandler? var didEndEditHandler: ((String?)->())? override init(frame frameRect: NSRect) { super.init(frame: frameRect) initDefaultValue() initSubViews() } required init?(coder: NSCoder) { super.init(coder: coder) } override func awakeFromNib() { super.awakeFromNib() initDefaultValue() initSubViews() } func initDefaultValue() { mode = .ciphertext self.secureTextField.isHidden = false self.secureTextField.isBezeled = false self.secureTextField.focusRingType = .none self.secureTextField.delegate = self self.textFiled.isHidden = true self.textFiled.isBezeled = false self.textFiled.focusRingType = .none self.textFiled.delegate = self let cell = self.textFiled.cell as? NSTextFieldCell cell?.allowedInputSourceLocales = [NSAllRomanInputSourcesLocaleIdentifier] self.textFiled.firstResponderHandler = { [unowned self] result in self.updateRightViewStateIfNeed(editing: result) guard let callback = self.becomeFirstResponderHandler else { return } callback(self) } self.secureTextField.firstResponderHandler = { [unowned self] result in self.updateRightViewStateIfNeed(editing: result) guard let callback = self.becomeFirstResponderHandler else { return } callback(self) } } func initSubViews() { self.addSubview(self.backgroundView) self.addSubview(self.secureTextField) self.addSubview(self.textFiled) } override func layout() { super.layout() let width: CGFloat = NSWidth(self.bounds) let height: CGFloat = NSHeight(self.bounds) self.backgroundView.frame = self.bounds var textFieldHeight: CGFloat = 22 if let font = self.textFiled.font { textFieldHeight = font.pointSize * 1.5 } let textFieldX: CGFloat = 12 if let rightView = self.rightView { let rightWidth = NSWidth(rightView.frame) rightView.frame = NSMakeRect(width-rightWidth, 0, rightWidth, height) let textFieldFrame = NSMakeRect(textFieldX, (height-textFieldHeight)*0.5, width-textFieldX*2-rightWidth-5, textFieldHeight) self.secureTextField.frame = textFieldFrame self.textFiled.frame = textFieldFrame } else { let textFieldFrame = NSMakeRect(textFieldX, (height-textFieldHeight)*0.5, width-textFieldX*2, textFieldHeight) self.secureTextField.frame = textFieldFrame self.textFiled.frame = textFieldFrame } } func switchMode(mode: KMSecureTextFiledMode) { self.mode = mode if (mode == .ciphertext) { /// 密文 self.secureTextField.isHidden = false self.secureTextField.stringValue = self.textFiled.stringValue self.window?.makeFirstResponder(self.secureTextField) self.textFiled.isHidden = true } else { /// 明文 self.textFiled.isHidden = false self.textFiled.stringValue = self.secureTextField.stringValue self.window?.makeFirstResponder(self.textFiled) self.secureTextField.isHidden = true } } func clear() { self.secureTextField.stringValue = "" self.textFiled.stringValue = "" guard let callback = valueDidChange else { return } callback(self, "") } func password() -> String { return self.mode == .ciphertext ? self.secureTextField.stringValue : self.textFiled.stringValue } override func becomeFirstResponder() -> Bool { self.window?.makeFirstResponder(self.mode == .ciphertext ? self.secureTextField : self.textFiled) return super.becomeFirstResponder() } internal func updateRightViewStateIfNeed(editing: Bool) { guard let view = self.rightView else { return } if (self.rightViewMode == .always) { view.isHidden = false return } if (self.rightViewMode == .never) { view.isHidden = true return } if (editing) { // 开始编辑 view.isHidden = self.rightViewMode != .whileEditing } else { // 结束编辑 view.isHidden = self.rightViewMode == .whileEditing } } } extension KMSecureTextFiled: NSTextFieldDelegate { func controlTextDidChange(_ obj: Notification) { if (self.mode == .ciphertext) { if (self.secureTextField.isEqual(to: obj.object)) { guard let callback = valueDidChange else { return } callback(self, self.secureTextField.stringValue) } } else { if (self.textFiled.isEqual(to: obj.object)) { guard let callback = valueDidChange else { return } callback(self, self.secureTextField.stringValue) } } } func controlTextDidEndEditing(_ obj: Notification) { if (!self.secureTextField.isEqual(to: obj.object) && !self.textFiled.isEqual(to: obj.object)) { return } self.updateRightViewStateIfNeed(editing: false) guard let callback = self.didEndEditHandler else { return } callback(self.password()) } func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { switch commandSelector { case #selector(NSResponder.insertNewline(_:)): if let inputView = control as? NSTextField { // //当当前TextField按下enter if inputView == self.textFiled || inputView == self.secureTextField { guard let callBack = enterAction else { return false} callBack() } } return true default: return false } } }