// // 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 textFiled.placeholderString ?? "" } else { return secureTextField.placeholderString ?? "" } } set { if (mode == .plaintext) { textFiled.placeholderString = newValue } else { 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 secureTextField.isHidden = false secureTextField.isBezeled = false secureTextField.focusRingType = .none secureTextField.delegate = self textFiled.isHidden = true textFiled.isBezeled = false textFiled.focusRingType = .none 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() { addSubview(backgroundView) addSubview(secureTextField) addSubview(textFiled) } override func layout() { super.layout() let width: CGFloat = NSWidth(self.bounds) let height: CGFloat = NSHeight(self.bounds) 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) secureTextField.frame = textFieldFrame textFiled.frame = textFieldFrame } else { let textFieldFrame = NSMakeRect(textFieldX, (height-textFieldHeight)*0.5, width-textFieldX*2, textFieldHeight) secureTextField.frame = textFieldFrame textFiled.frame = textFieldFrame } } func switchMode(mode: KMSecureTextFiledMode) { self.mode = mode if (mode == .ciphertext) { /// 密文 secureTextField.isHidden = false secureTextField.stringValue = textFiled.stringValue self.window?.makeFirstResponder(secureTextField) textFiled.isHidden = true } else { /// 明文 textFiled.isHidden = false textFiled.stringValue = secureTextField.stringValue self.window?.makeFirstResponder(textFiled) secureTextField.isHidden = true } } func clear() { secureTextField.stringValue = "" textFiled.stringValue = "" guard let callback = valueDidChange else { return } callback(self, "") } func password() -> String { return self.mode == .ciphertext ? secureTextField.stringValue : textFiled.stringValue } override func becomeFirstResponder() -> Bool { self.window?.makeFirstResponder(self.mode == .ciphertext ? secureTextField : textFiled) return super.becomeFirstResponder() } internal func updateRightViewStateIfNeed(editing: Bool) { guard let view = rightView else { return } if (rightViewMode == .always) { view.isHidden = false return } if (rightViewMode == .never) { view.isHidden = true return } if (editing) { // 开始编辑 view.isHidden = rightViewMode != .whileEditing } else { // 结束编辑 view.isHidden = rightViewMode == .whileEditing } } } extension KMSecureTextFiled: NSTextFieldDelegate { func controlTextDidChange(_ obj: Notification) { if (self.mode == .ciphertext) { if (secureTextField.isEqual(to: obj.object)) { guard let callback = valueDidChange else { return } callback(self, secureTextField.stringValue) } } else { if (self.textFiled.isEqual(to: obj.object)) { guard let callback = valueDidChange else { return } callback(self, secureTextField.stringValue) } } } func controlTextDidEndEditing(_ obj: Notification) { if (!secureTextField.isEqual(to: obj.object) && !self.textFiled.isEqual(to: obj.object)) { return } updateRightViewStateIfNeed(editing: false) guard let callback = 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 == textFiled || inputView == secureTextField { guard let callBack = enterAction else { return false} callBack() } } return true default: return false } } }