(Cocoa) NSAlert 팝업 사용하기

NSAlert는 프로그램 실행 중 발생한 어떤 예외 상황을 사용자에게 알리고 가능한 경우 그에 대한 적절한 처리 방법을 사용자로 하여금 선택할 수 있게 하는 대화상자이다. 자바 스크립트의 alert 창과 비슷한데, NSAlert는 훨씬 더 다양한 속성과 하부 기능을 사용해서 confirm 이나 prompt와 같은 기능도 동시에 수행할 수 있게 한다.

몇 가지 설정 가능한 속성은 다음과 같다.

  • type : 예외 상황의 종류나 심각성을 나타낼 수 있다. alertStyle 속성을 통해 지정가능하다.
  • message: 사용자에게 표시할 내용. messageText 속성을 통해 지정한다.
  • informative text: 사용자가 추가적인 액션을 요구할 때, 부가적으로 안내하는 텍스트. informativeText를 통해 지정한다.
  • icon : 팝업 화면 좌측에 표시되는 아이콘 이미지. icon 을 통해 지정한다.
  • help : alert 창은 사용자에게 도움말을 제공할 수 있다. showsHelp, helpAnchor 가 이와 관련된 속성이다.
  • buttons : 버튼은 1개 이상이 표시될 수 있다. 기본적으로 확인 버튼이 달려 있을 것이며, addButton(withTitle:)을 통해서 버튼을 추가할 수 있다.
  • suppressionCheck : 흔히 “더이상 보지 않기”로 알려진 체크 박스를 표시한다. showsSuppressionButton 속성으로 제어할 수 있다.
  • accessoryView: 추가적인 액세서리 뷰. 만약 사용자로부터 텍스트 입력을 받는 등의 동작을 한다면 여기에 텍스트 필드를 지정해줄 수 있다. accessoryView 속성 및 layout() 메소드가 관련된다.

Alert의 실행과 응답 처리

Alert 창이 실행되는 방법에는 크게 두 가지가 있는데 하나는 모달 팝업과 다른 하나는 시트이다. 모달 팝업은 보통 크롬이나 파이어폭스 같은 브라우저에서 alert 창을 띄울 때의 방식인데, 대화 상자가 형태로 팝업되고 이 상자 바깥의 앱 창은 사용자 입력 이벤트를 받지 않는 것이다.

OSX에서는 보통 모달 팝업보다는 시트(sheet) UI를 권장한다. 시트는 모달과 원리상으로는 동일한데, 창의 타이틀 바 영역에서 빠져나오는 식으로 표현된다. 보통 인쇄나 열기… 같이 대화상자를 필요로 하는 동작에서 많이 보일 것이다.

  • 모달 팝업 – 모달 팝업은 runModal() 메소드를 호출하여 표시를 시작한다. 이 메소드는 사용자가 어떤 버튼이든 (혹은 앱을 종료하든) 클릭하여 alert 팝업이 닫힐 때 리턴한다. 공교롭게도 이 때 리턴값은 .alertFirstButton, .alertSecondButton, .alertThirdButton 으로 되어 있다. (버튼은 오른쪽부터 첫번째, 두번째, 세번째로 구분한다.)
  • 시트 – 시트는 beginSheetModal(for:,completionHandler:) 메소드를 통해서 실행할 수 있다. 이쪽이 좀 더 모던한 방식으로, 시트를 닫으면 완료 핸들러가 호출된다. 완료 핸들러는 NSApplication.ModalResponse 값을 인자로 받게 된다.

NSAlert는 이제 NSError/Error 값을 가지고 만들 수 있으며, 이전에 존재하던 alertWithMessageText:defaultButton:alternateButton:otherButton:informativeMessageText: 는 deprcated되었다. (Swift API에서는 아예 제거되었다.)

블럭/클로저를 넘겨서 팝업을 닫았을 때 처리를 할 수 있으므로 모달 델리게이트는 필요가 없어져서 API에서 제거되었다.

실제 사용시에는 다음과 같이 쓰면 된다.

let alert = NSAlert(error: currentError)
alert.messageText = "An error occurred : \(error.localizedDescription)"
... # alert 에 대한 커스터마이징
if let window = view.window {
  alert.beginSheetModal(for: self.view.window!){ res in 
    ...
  }
}