문제
iPad에서 캔버스 화면에 진입할 때 PKToolPicker가 처음 표시되면, 축소(collapsed/minimized) 상태의 원형 버튼에 선택된 도구 아이콘이 표시되지 않았습니다. 빈 검은 원으로만 보였습니다.
사용자가 직접 picker를 확장하여 도구를 선택하거나 변경한 후 다시 축소하면 그때서야 아이콘이 정상 표시되었습니다.
영향 범위
- 앱 내 여러 화면에서 다수의
PKCanvasView를 사용 - 모든 캔버스가 하나의
PKToolPicker인스턴스를 공유하는 구조
원인 분석
PKToolPicker의 내부 축소 뷰(collapsed view)가 프로그래밍 방식으로 표시될 때 초기 레이아웃 패스를 수행하지 않았습니다. Picker가 setVisible(_:forFirstResponder:)로 표시되더라도 축소 상태의 도구 아이콘 렌더링을 위한 레이아웃이 자동으로 트리거되지 않는 Apple 프레임워크 내부 동작 문제였습니다.
해결 방법
두 가지 변경을 조합하여 해결했습니다.
1. UIViewRepresentable → UIViewControllerRepresentable 전환
캔버스 래퍼 뷰를 UIViewRepresentable에서 UIViewControllerRepresentable로 변경하여 viewDidAppear 라이프사이클을 활용했습니다. Apple의 WWDC19 권장 패턴에 따라 viewDidAppear에서 tool picker를 설정했습니다.
이유: UIViewRepresentable의 makeUIView/updateUIView는 뷰가 윈도우에 완전히 추가되기 전에 호출될 수 있어 picker 초기화 타이밍이 불안정했습니다.
2. Window 계층 순회를 통한 강제 재렌더링
Picker가 표시된 후 다음 런 루프 사이클에서 윈도우 계층을 순회하여 Picker 관련 내부 뷰를 찾아 강제로 레이아웃/디스플레이를 트리거했습니다.
private static func forcePickerRedraw(in view: UIView) {
let className = String(describing: type(of: view))
if className.contains("Picker") || className.contains("Floating") || className.contains("Palette") {
view.setNeedsLayout()
view.layoutIfNeeded()
view.setNeedsDisplay()
for subview in view.subviews {
subview.setNeedsLayout()
subview.layoutIfNeeded()
subview.setNeedsDisplay()
}
}
for subview in view.subviews {
forcePickerRedraw(in: subview)
}
}
호출 시점: becomeFirstResponder() 이후 DispatchQueue.main.async로 한 런 루프 사이클 지연시켜 호출했습니다. Picker가 윈도우에 추가된 후 레이아웃을 강제하기 위함입니다.
picker.setVisible(isToolPickerVisible, forFirstResponder: canvasView)
if isToolPickerVisible {
canvasView.becomeFirstResponder()
DispatchQueue.main.async { [weak self] in
guard let window = self?.canvasView.window else { return }
Self.forcePickerRedraw(in: window)
}
}
시도했으나 실패한 접근법
| 접근법 | 결과 |
|---|---|
selectedToolItemIdentifier 설정 | 축소 아이콘에 반영되지 않음 |
selectedToolItem에 PKToolPickerInkingItem 할당 | 내부 도구는 선택되지만 축소 아이콘 렌더링 안 됨 |
stateAutosaveName = nil | 축소 상태 복원 방지만 되고 아이콘 문제 미해결 |
PKCanvasView 서브클래스 + didMoveToWindow() | 동작 불안정 |
| 토글 버튼으로 Picker on/off | 근본 해결 아님, 토글 시에도 빈 원 표시 |
becomeFirstResponder() → setVisible() 순서 | Apple 권장은 반대 순서. 순서 수정만으로는 해결 안 됨 |
UIViewControllerRepresentable + viewDidAppear만 적용 | 타이밍은 개선되었으나 축소 아이콘 여전히 미표시 |
수정 내용 요약
- 캔버스 래퍼를
UIViewControllerRepresentable로 전환하면서 전용UIViewController서브클래스 생성 forcePickerRedraw(in:)메서드 추가
핵심 정리
PKToolPicker의 축소 뷰는 프로그래밍 방식 표시 시 자체적으로 초기 레이아웃을 수행하지 않습니다viewDidAppear에서 picker를 설정하는 것이 올바른 타이밍이지만 그것만으로는 부족합니다- 윈도우 계층에서 Picker 관련 뷰를 찾아
setNeedsLayout/layoutIfNeeded/setNeedsDisplay를 강제 호출해야 축소 아이콘이 렌더링됩니다 - 이 강제 레이아웃은
becomeFirstResponder()이후 다음 런 루프에서 실행되어야 효과적입니다