(Cocoa) NSUserInterfaceValidations – UI 컨트롤의 활성화 여부 결정

NSUserInterfaceValidations

특정한 메뉴 항목이나 버튼, 테이블뷰, 텍스트뷰 및 텍스트 필드등이 특정한 조건에 따라 활성화/비활성화되어야 하는 경우에 이를 처리하는 전략으로는 두 가지 방법이 있다.

  1. 특정 조건값이 변경될 때 (변경지점에서, 혹은 옵저빙을 통해서) 관련된 컨트롤의 활성화 여부를 변경해주는 방법
  2. 특정 조건값을 계산 프로퍼티로 만든 후, 이를 코코아 바인딩으로 컨트롤의 enabled 키와 연결하는 방법

코코아에서는 이 외에도 컨트롤에 대한 유효성 검사 매커니즘을 별도로 가지고 있다. 컨트롤이 화면에 표시될 때, 해당 컨트롤의 타깃이 NSUserInterfaceValidations 프로토콜을 따르고 있다면 해당 프로토콜의 메소드를 호출하여 자신의 유효성 여부를 판단하게하고, 그에 따라 자동으로 활성/비활성 여부를 결정해줄 수 있는 것이다.

이 매커니즘은 다음과 같이 구성된다.

  1. 컨트롤 자체는 NSValidatedUserInterfaceItem 프로토콜을 따른다. 이는 tagaction 프로퍼티로만 구성되는 매우 간단한 프로토콜이며, 대부분의 코코아 컨트롤이 이를 따른다고 보면 된다.
  2. 컨트롤의 타깃이 되는 객체는 NSUserInterfaceValidations 프로토콜을 따르게 한다. 여기에는 validateUserInterfaceItem(:)이라는 메소드가 하나 정의되는데, 이메소드를 구현하면 된다.

타깃이 되는 객체는 다음 메소드를 구현하면서, NSUserInterfaceValidations를 따르도록한다.

func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool

예를 들어 어떤 이미지를 붙여넣을 수 있는 앱의 툴바를 추가한다고 가정하자. 이 때 문서객체 혹은 뷰 컨트롤러는 클립보드에서 이미지를 붙여넣을 수 있는 상황에서만 활성화되어야 한다. 그리고 이를 결정하는 상태값은 해당 객체 내부가 아닌 외부(클립보드)의 상태에 의해 결정되므로 옵저빙이나 바인딩을 쓰기가 어렵다. 따라서 NSUserInterfaceValidations를 이용한다.

extension ViewController: NSUserInterfaceValidations {
  func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
    if item.action == #selector(self.paste(:)) {
      let pb = NSPasteboard.general
      return pb.canReadObject(forClasses: [NSImage.self])
    }
    return true
  }
}

만약 복사 버튼에 대해서도 이를 구현하고자 한다면 다음과 같은 if구문을 삽입하면 될 것이다.

... 
   else if item.action == #selector(self.copy(:)) {
     return self.imageView.image != nil
   }
...