[Cocoa] Alert 팝업 사용하기

코코아의 Alert 팝업은 의외로(?) 사용하기 쉽다. 자바스크립트의 alert과 비슷한데, 차이가 있다면 NSAlert이라는 객체를 만들어서 사용한다는 점이다. (생각해보니 차이가 크네!) 코코아의 alert 창은 그 자체로 하나의 작은 윈도이며, 메소드를 사용해 여러 구성요소들을 커스터 마이징할 수 있다.

NSAlert 생성하기

NSAlert객체는 -alertWithMessageText:defaultButton:alternateButton:otherButton:informativeTextWithFormat:이라는 아주 긴 이름의 메소드를 사용하여 생성한다. 그냥 alloc, init으로 만들어서 각각의 속성값을 지정해줘도 된다. 만약 에러 메시지를 표시하기 위해 alert 창을 띄우는 용도라면 alertWithError: 메소드를 사용하여 생성할 수 있다.

Alert 창은 기본적으로 하나의 버튼(OK나 닫기)을 갖는데, 그외의 버튼이 있는 경우가 있을 수도 있다. 왜, 문서를 저장하지 않고 창을 닫으면 “삭제, 취소, 저장” 이런 메시지들이 함께 나오는 게 그 예라 할 수 있다. 버튼을 추가하려면 -addButtonWithTitle:을 통해 버튼을 추가할 수 있다.

alert 창 띄우기

기본적으로 alert 창은 기존 창에 대해 모달하게 동작한다. 가장 단순하게는 만들어진 alert 창 객체에 -runModal을 보내서 창을 띄울 수 있다. alert이 떠 있는 동안에는 창의 다른 부분은 사용자와 인터렉션 하지 못하고 닫힌 상태가 된다.

-(NSInteger) runModal

사용자가 alert 창의 버튼 중 하나를 클릭하면 alert이 닫히고 -runModal은 정수값을 반환한다. 이 정수값을 바탕으로 어떤 버튼이 눌러졌는지 알 수 있다. 디폴트 버튼을 클릭하면 1이 리턴되고, 두 번째 버튼은 0, 세번째는 -1이 반환된다.

- (void)showAlert {
    NSAlert *alert = [NSAlert alertWithMessageText:... ];
    NSInteger r = [alert runModal];
    if(r != 1) {
        /* OK가 아닌 다른 버튼이 눌러졌을 때 처리 ... */
    }
}

alert을 sheet로 띄우기

sheet는 alert이 별도의 창이 아니라, 메인 창의 상단에 맞물려 붙어 있는 형태로 표현된다. 만약 문서 기반 앱과 같이 복수의 창을 표시하는 앱이라면 특정 창에 대한 경고를 sheet로 표시할 것을 추천한다. (sheet는 자신이 붙어 있는 윈도에 대해서만 모달하게 동작한다.) 이는 별도의 객체를 만들지 않고 NSAlert 객체를 바로 사용할 수 있다.

- (void)ShowSheet {
    NSAlert *alert = [NSAlert alertWithMessageText:... ];
    [alert beginSheetModalForWindow:self.window modalDelegate:self didEndSelector:@selector(sheet:didEndWithRunCode:contextInfo:) contextInfo:nil];
}

beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:를 사용하면 되는데, 이 메소드는 바로 값을 반환하지 않는다. 대신 모달 델리게이트오 지정한 객체에 대해 특정한 메시지를 호출한다. 위 예제에서는 sheet:didEndWithRunCode:contextInfo:라고 이름 붙였는데, 이름은 살짝 달라도 되지만 메소드의 시그니처는 다음의 포맷과 같아야 한다.

- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;

Objective-C에서 시그니처는 함수의 리턴타입과 파라미터의 수, 각 파라미터의 타입을 조합한 것이다. 델레게이트 프로토콜이 따로 정의되어 있지 않기 때문에 위 메소드와 시그니처만 맞춘 형식이면 어떤 셀렉터를 넘겨도 상관없다. 예를 들어 다음과 같은 식으로 이름을 바꾸어도 된다. -(void)sheetDidEnd:(NSAlert *)alert rCode:(NSInteger)rCode contextInfo:(void *)context로 메소드를 정의해도 시그니처 문자열이 같으므로 호출된다. 아 메소드의 시그니처 문자열은 v@:@L^v이므로 아예 -(void)sheet:(id)sheet didEndWithReturnCode:(long)code context:(void *)context로 해도 된다.

따라서 다음과 같이 써도 되더라.

- (void)a:(id)a b:(long)b c:(void*)c
{ NSLog(@"sheet did end"); }

- (void)showSheet {
    NSAlert *alert = [NSAlert alertWithMessageText:@"This is alert sheet." ...];
    [alert beginModalForWindow:self.window modalDelegate:self didEndSelector:@selector(a:b:c:) contextInfo:nil];
}