UIWindow 공부
UIWindow
객체에 대해 가장 최상단에 위치한 화면을 표시하는 객체 정도로만 어렴풋이 알고 있었다. 최근 스터디를 통해 더 자세히 알고 싶어 Apple Developer Documentation와 View Programming Guide for iOS를 조사해보았다.
역할
UIWindow
의 역할은 다음과 같다.
- 앱의 시각적 콘텐츠를 담는다.
- 뷰들과 다른 어플리케이션 객체들에게 터치 이벤트를 전달하는 중요한 역할을 한다.
- 오리엔테이션 변화를 쉽게 하기 위해 앱의 뷰 컨트롤러들과 협력한다.
window
객체는 뷰들을 담는 컨테이너라고 생각하면 된다. 스크린에 표시되는 뷰의 계층 구조에서 최상위 뷰의 역할을 할 고정적인 객체의 역할을 하는 것이다. 그래서 앱의 생명 주기 동안 특별한 경우(밑에서 설명)를 제외하면 단 하나의 window
객체만 생성하고 사용하며 표시한 뷰가 바뀌어야 되는 경우 가장 앞단에 있는 rootViewController
의 교체를 통해 뷰를 바꾸는 방법을 사용한다. 그래서 보통 Xcode는 프로젝트가 생성될 경우 자동으로 AppDelegate
에 window
객체를 생성을 하여 개발자가 생성 또는 상속받는 일은 잘 없다.
특별한 경우
다음은 window
객체를 새로 만들거나 만들어지는 경우이다.
- 스토리보드를 쓰지 않을 경우에는 메인 window를 직접 생성해야 한다.
- 외부 디스플레이를 지원하는 앱의 경우 새로운 Window를 만들어 보여줄 수 있다.
- 보통의 경우 새로운 window는 시스템에 의해 만들어지며 특정 이벤트에 대한 반응으로 만들어진다.(예: 전화가 오는 경우)
메인 Window 객체를 만드는 방법
메인 window
객체는 프로그래밍 / 인터페이스 빌더 두 가지 방법으로 생성과 설정을 할 수 있고 AppDelegate
객체가 이것을 잡고 있어야 한다. 그리고 이 객체는 백그라운드나 포그라운드에 상관없이 앱이 실행될 때는 항상 생성해야 한다. 그러나 추가적인 window 객체는 그것이 필요할 때 생성되도록 해야 한다(lazily created
).
스토리보드로는 자동으로 생성이 되니 스토리 보드 없이 프로그래밍적으로 메인 Window 객체를 생성하고 뷰 컨트롤러에 연결해보겠다.
먼저 원래는 Main으로 되어 있었던 Info.plist
에 항목을 지워준다. 프로젝트를 생성할 경우 Xcode는 자동으로 Main.storyboard
를 통해 메인 window
객체를 만들고 rootViewController
를 결정한다. 그러나 우리는 스토리보드를 사용하지 않을 것이기 때문에 지워준다.
1 |
|
그 다음 AppDelegate
로 가보면 AppDelegate
객체가 strong으로 잡고 있는 옵셔널한 window
객체가 있다. 자동으로 storyboard가 연결이 되어 있는 경우라면 스토리보드와 연결되어 window
객체가 생성되어 AppDelegate
객체가 잡고 있는 이 window
에 넣어진다. 그러나 스토리보드를 사용하지 않는 경우기 때문에 직접 넣어 줄 것이다. 이 작업은 UIApplicationDelegate
메소드인 didFinishLaunchingWithOptions
에서 해줄 것이다.
1 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { |
이렇게 window
객체를 생성하고 넣어준 뒤 실행을 해보면…
이러한 에러가 뜨면서 앱이 죽는 것을 볼 수 있다. 위에서 언급했듯이 window
객체는 그저 뷰를 보여주는 컨테이너 역할이기 때문에 rootViewController
를 무조건 가져야 한다. 그래서…
1 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { |
UIViewController
를 생성해 윈도우의 rootViewController
를 지정해준 뒤 실행해보면 앱이 죽지는 않지만 역시나 검은 화면만 나오는 것을 볼 수 있다. 우리가 지정한 window가 보이지 않는다는 의미인데 이것은 우리의 window가 keyWindow가 아니라는 의미이다.(.isKeyWindow
로 확인 가능하다.)
여기서 keyWindow
란, ‘키보드 이벤트와 터치와 관련되지 않는 이벤트를 받는 window’를 말한다. 터치 이벤트는 터치가 일어난 바로 그 윈도우에게 전달되지만 좌표값이 없는 이벤트는 앱의 keyWindow
에 전달이 된다. (하지만 대부분의 경우 메인 윈도우가 keyWindow
일 것이다.)
1 | window.makeKeyAndVisible() |
우리의 window
객체가 keyWindow
가 되고 보이게 하기 위해 이것을 실행해주면 노란화면에 UILabel
하나가 나오는 것을 볼 수 있다.(window의 파란색은 당연히 나오지 않는다.) 만약 위에서 AppDelegate
가 strong으로 잡지 않는다면 어떻게 될까? 역시나 검은 화면이 나올 뿐이다.(실험을 통해서 확인해보기 바란다.)
스토리보드는 때에 따라서 버그도 많고 뷰가 많아질 경우 매우 느려지는 경우를 볼 수 있는데 이렇게 처음부터 코드로 하는 방법은 후에 가서도 쓰일 수 있을 것이다.
이밖에도 UIWindow
는 타입 프로퍼티로 window
가 keyWindow
가 되거나 되지 않는지, 화면에 보이는지 안 보이는지, 또는 키보드 관련 Notification
을 가지고 있어서 감지하고 싶을 경우 옵저버를 추가해 읽으면 된다. 그리고 원하는 윈도우의 좌표계로 바꿔주는 convert
인스턴스 메소드를 가지고 있어 윈도우 간 좌표 전환을 하고 싶을 때 사용할 수 있는데 이것을 쓰는 경우는 조금 더 연구를 해보아야겠다.