"클릭은 전부 먹혔어. 드래그만 죽었고, 포인터 밑에서 창이 움직였어."
큰 Mac 앱이 AppKit에 남는 이유
가족의 편집기, 터미널, 파일 작업대가 AppKit을 주인으로 두는 건 향수 때문이 아니라 구체적인 이유가 있어서야. 텍스트 시스템(NSTextView랑 TextKit)엔 입력기, 조합 중인 글자, 맞춤법 검사, 서비스, 손쉬운 사용 지원이 다 들어 있어. 처음부터 새로 만든 글자 화면엔 그런 게 없어. 분할 뷰, 직접 만든 탭 줄, 제목 막대 보조 뷰에는 SwiftUI로는 손댈 수 없는 배치와 마우스 추적 제어가 필요해. 그리고 앱 사이 끌어다 놓기는 AppKit 객체에 붙은 플래그가 정해. 대신 AppKit의 힘에는 날카로운 모서리가 따라와. 아래는 가족이 실제로 세션을 통째로 날린 사례들이야.
마우스 추적
- 제목 막대의 뷰는
mouseDragged를 한 번도 못 받아. 제목 막대 보조 영역에 둔 탭 줄이 클릭, 더블클릭, 오른쪽 클릭은 다 처리하는데, 끌어서 순서 바꾸기는 한 번도 안 불렸어. 창 전체가 대신 미끄러졌지.NSView의mouseDownCanMoveWindow기본값은!isOpaque야. 그래서 투명한 평범한 뷰는 "응"이라고 대답하고, 창 이동이 이벤트 흐름을 가로채.false를 돌려주게 오버라이드해. 제목 막대를 끌 때의 느낌을 살리려면, 탭 줄의 빈 곳에서는 직접window.performDrag(with:)를 부르면 돼. (버튼은 이 문제가 없어.NSControl은 이미false를 돌려줘.) - 드래그 중에 뷰를 다시 만들면 드래그가 끝나. 탭을 맞바꿀 때마다 모델에 알렸더니, 부모가 탭 줄의 하위 뷰를 다시 만들었어. 추적 중이던 뷰가 빠지면서
mouseDragged와mouseUp흐름이 조용히 끊겨 버렸어. 드래그 중에는 탭 줄 안의 순서만 바꾸고, 모델 변경은 마우스를 뗄 때 한 번만 해. - 다른 앱으로 파일을 끌어가려면 비로컬 마스크가 필요해. 작업대 안에선 행을 놓을 수 있었는데, Finder나 다른 앱 위에선 먹통이었어. 테이블 뷰랑 컬렉션 뷰는 비로컬 드래그의 원본 작업 마스크가 기본으로 비어 있어. 뷰마다 호출 한 줄이면 고쳐져.
분할 뷰
배치 버그가 가장 많이 나온 곳은 중첩 NSSplitView였어. 부모가 프레임을 손으로 정하는 분할 뷰는 보통 분할 뷰가 받는 오토레이아웃 크기 조정을 못 받아. 높이가 처음으로 0이 아니게 됐을 때 setPosition을 딱 한 번 불렀더니, 한쪽이 패널을 다 차지하고 다른 쪽은 안 보였어. 고치려면 layout()에서 추적 중인 높이가 바뀔 때마다 구분선 위치를 다시 잡아. 사용자가 구분선을 끌면 비율을 기록하고, 코드에서 부른 호출이 되먹임되지 않게 막아. 제약을 켜기 전에는 하위 뷰에 0이 아닌 프레임을 줘. 확인은 실행 직후가 아니라 창 크기를 실제로 바꿔 가면서 하고, 통합 로그도 봐. 회복된 제약 충돌은 화면엔 안 드러나거든.
창과 탭
네이티브 창 탭은 탭 하나하나가 NSWindow야. 탭마다 색을 입힐 수도 없어. 탭 보조 뷰는 자기 크기 제약을 걸어두지 않으면 탭 너비 전체로 늘어나. 터미널은 네이티브 탭을 꺼버리고 탭 줄을 직접 그렸어. 탭 그룹 단위로 창을 스냅숏하는 코드가 돌기 전에, 새 탭을 먼저 그룹에 넣어야 해(addTabbedWindow(_:ordered:)). 안 그러면 따로 떨어진 창으로 저장돼.