macos - 在 OS X Cocoa 中实现 iOS 密码输入 View

标签 macos cocoa-touch cocoa common-lisp

我需要在 OS X Cocoa 中创建一个 View ,隐藏除最后输入的字符之外的所有字符(直到该字符在设定的时间后隐藏)。本质上,我想在 OS X Cocoa 中使用带有 textfield.secureTextEntry = YESUITextField

我找不到在 OS X Cocoa 中导入 UITextField 的方法。我认为无论如何这是不可能的,因为 NSTextFieldUITextField 来自两个不同的框架。

OS X Cocoa 中的

NSSecureTextField 不会在设定的时间内保持最后一个字符可见,并且我找不到可以设置来更改此行为的实例或类属性。

我尝试推出自己的实现,从 NSTextField 开始并从 keyUp: 触发事件,但我遇到了极端情况的问题。我当前的方法是将 NSTextField 中存储的文本更改为隐藏字符(除了最后一个字符之外的所有字符,在设定的时间内等)。然而,使用这种技术,当用户选择 7 个字符密码中的第二个到第五个字符并将其删除时会发生什么。那么我怎样才能找出哪些隐藏字符被删除,以便我可以检索正确输入的密码?

因此,我认为如果我需要推出自己的实现,我不需要更改存储在 NSTextField 中的实际文本,而是更改 View 在屏幕上显示的方式。但是我目前找不到办法做到这一点。在此提供帮助将不胜感激。

我还要说的是,我有强烈的偏见,不愿意推出自己的实现。如果有人知道我可以利用以前的工作或简单地将 UITextField 导入 OS X Cocoa 来解决这个问题,我会欢迎(并强烈支持)这些解决方案。

最佳答案

使用NSTextField,我无法成功地将文本的绘制与存储的文本分开。我尝试自定义 NSFormatter 并简要研究了自定义字段编辑器。我得到的最接近的是通过自定义drawInteriorWithFrame:inView:。这使我能够控制绘图,但仅限于字段编辑器无法控制对象时(即,仅当不编辑对象中的文本时)。对于我想要做的事情来说,所有方法似乎都过于复杂。

使用NSTextView我能够让它工作。方法是将字符的绘制与存储的字符分开,并将实际的文本存储在对象中。绘图方法只是在必要时使用星号来隐藏文本,这不会影响存储在对象中的文本值。

这里有一些使用这种方法的代码。它是在 Clozure Common Lisp 中实现的,通过 Clozure 的 Objective C 桥与 Cocoa 进行通信:

(defclass easygui::cocoa-password-entry-text-view (easygui::cocoa-text-view)
  ((pending-fun :accessor pending-fun :initform nil)
   (visible-char-time-secs :reader visible-char-time-secs :initform 1))
  (:metaclass ns:+ns-object))

(defclass easygui::cocoa-password-entry-layout-manager (ns:ns-layout-manager)
  ((last-char-vis-p :accessor last-char-vis-p :initform nil))
  (:metaclass ns:+ns-object))

(defclass password-entry-text-view (text-view)
  ()
  (:default-initargs :specifically 'easygui::cocoa-password-entry-text-view))

(objc:defmethod #/initWithFrame: ((self easygui::cocoa-password-entry-text-view) (frame #>NSRect))
  (unwind-protect (call-next-method frame)
    (#/replaceLayoutManager: (#/textContainer self)
     (#/init (#/alloc easygui::cocoa-password-entry-layout-manager)))
    (#/setFont: self
     (convert-font "Courier" 12))))

(objc:defmethod (#/drawGlyphsForGlyphRange:atPoint: :void) ((self easygui::cocoa-password-entry-layout-manager) (glyph-range #>NSRange) (at-point #>NSPoint))
  (let ((glyph-cnt (#/numberOfGlyphs self)))
    (let ((hide-until (if (last-char-vis-p self) (1- glyph-cnt) glyph-cnt)))
      (dotimes (i hide-until)
        (#/replaceGlyphAtIndex:withGlyph: self i 13))))
  (call-next-method glyph-range at-point))

(defmethod dialog-item-hidden-text ((view password-entry-text-view))
  (let ((text (dialog-item-text view)))
    (let ((layout-manager (#/layoutManager (cocoa-ref view))))
      (with-output-to-string (strm)
        (dotimes (i (1- (length text)))
          (format strm "*"))
        (format strm "~a" (if (last-char-vis-p layout-manager)
                            (char text (1- (length text)))
                            "*"))))))

(defmethod cursor-at-end-of-text-p ((cocoa-self easygui::cocoa-password-entry-text-view))
  (awhen (#/selectedRanges cocoa-self)
    (when (eq (#/count it) 1)
      (awhen (#/rangeValue (#/objectAtIndex: it 0))
        (let ((pos (ns:ns-range-location it)))
          (let ((length (ns:ns-range-length it)))
            (when (eq length 0)
              (when (eq pos (#/length (#/string cocoa-self)))
                t))))))))

(objc:defmethod (#/keyDown: :void) ((cocoa-self easygui::cocoa-password-entry-text-view) the-event)
  (call-next-method the-event)
  (labels ((get-keypress (the-event)
             (let* ((chars (#/characters the-event))
                    (str (objc:lisp-string-from-nsstring chars))
                    (char (char str 0)))
               char)))
    (handle-keypress-on-view
      (easygui::easygui-view-of cocoa-self)
      (get-keypress the-event))))

(defmethod handle-keypress-on-view ((view password-entry-text-view) keypress)
  (let ((cocoa-self (cocoa-ref view)))
    (cond ((or (eq keypress #\rubout)
               (not (cursor-at-end-of-text-p cocoa-self)))
           (setf (last-char-vis-p (#/layoutManager cocoa-self)) nil))
          (t
           (setf (last-char-vis-p (#/layoutManager cocoa-self)) t)
           (setf (pending-fun cocoa-self)
                 (alambda ()
                   (when (eq #'self (pending-fun cocoa-self))
                     (setf (last-char-vis-p (#/layoutManager cocoa-self)) nil)
                     (#/setNeedsDisplay: cocoa-self #$YES))))
           (schedule-for-event-process
             (pending-fun cocoa-self)
             (visible-char-time-secs cocoa-self))))))

关于macos - 在 OS X Cocoa 中实现 iOS 密码输入 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15076669/

相关文章:

c - LLVM和GCC,不同的输出相同的代码

linux - 如何将 linux sys/prctl.h 安装到 lion?

ios - 是否可以在设备方向更改时更改 Controller 内单个 View 的方向?

xcode - 有没有办法在没有 NSClassFromString 的情况下使用 GrowlApplicationBridge?

objective-c - Objective-C API 中只读属性值的安全突变和使用

cocoa - 如何在 Apple Cocoa 应用程序中获取设备 ID?

c++ - 创建 C++ 共享库时,是否只需要附加库所依赖的 header ?

java - 可以通过 afp 安装锁定用 java 打开的文件吗?

ios - AFNetworking 2.0//如何读取响应头

ios - 通过 CIFilter 传递 UIImage 会生成空白图像