Skip to content

Instantly share code, notes, and snippets.

@Dev-MJ
Last active November 11, 2017 07:28
Show Gist options
  • Save Dev-MJ/41215db9fe6cace865b9ab7f5b6eae95 to your computer and use it in GitHub Desktop.
Save Dev-MJ/41215db9fe6cace865b9ab7f5b6eae95 to your computer and use it in GitHub Desktop.
UIWindow

Window의 역할

  • 앱의 보이는 콘텐츠가 포함된다.
  • View 및 기타 앱 객체에 터치 이벤트를 전달하는 데 중요한 역할을 한다.
  • ViewController와 함께 방향 변경을 처리한다.
  • 대부분의 경우, 앱의 Window가 이러한 책임들을 충족시킬 수 있도록하기 위해 따로 조치를 취할 필요는 없다.
  • 스토리 보드를 사용하여 앱의 사용자 인터페이스를 만드는 경우, 외부 디스플레이를 지원할 때에나 Window 객체를 명시적으로 만든다.
  • Window에는 다른 모든 view를 포함하는 single root view 개체가 있다. 따라서 이 root view에 addSubvew를 하는 방법도 있으나, rootViewController를 제공하는 것이 window의 contents들을 관리하는데 더 효과적이다.
    ( -> window 객체와 contents 사이의 구분이 유지되기 때문) (앱이 background로 진입시 민감한 데이터를 빠르게 감추기 위해 사용할 수도 있다. Bacground로 진입시 UIWindow를 생성하여 스크린을 덮으면 됨.)

Window 생성하기

  • 앱 실행시에 window를 만들고, 이를 유지하고, app delegate 객체에 참조를 저장해야 한다.
  • 추가 window를 만드는 경우, 필요시에 지연생성(lazily) 해야 한다.(외부 디스플레이에 내용을 표시할 수 있으면, 해당 window가 만들어지기 전에 디스플레이가 연결될 때 까지 기다려야 한다.)
  • UIWindow를 생성할 때는 항상 window의 초기 size와, 해당 window가 보여질 screen을 지정해야 하며, 보여질 contents를 제공할 rootViewController를 지정해야 한다.
  • Window의 root view에는 원하는 view를 사용할 수 있는데, 이 root view를 구성할 때 초기 크기 및 위치를 지정해줘야 한다. (Root view를 TabbarController, Navigation Controller, Split-View Controller와 같이 container viewController에 의해 제공되는 경우는 초기 크기를 설정할 필요 없이 자동으로 조절된다.)
  • 만약 custom window를 제공하고 싶다면 AppDelegate 내의 UIWindow: window 라는 property의 getter method를 구현해야 하고, custom window를 return 해야 한다.

1. Programically

  • AppDelegate 내의 application:didFinishLaunchingWithOptions: 메서드 내에서 window를 생성하고 makeKeyAndVisible을 호출한다. makeKeyAndVisible 은 해당 window를 보여주고 다른 모든 window 앞에 배치하기 위한 메서드이다.
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    UIViewController *rootViewController = [[UIViewController alloc] init];
    rootViewController.view.backgroundColor = UIColor.greenColor;
    [_window setRootViewController:rootViewController];
    [_window makeKeyAndVisible];
    
    return YES;
}

2. Interface Builder

nib 파일 이용 방법

  • RunTime시에 window에 접근하기 위해선, UIApplication의 delegate 또는 nib 파일의 File’s Owner에 정의된 outlet에 해당 window를 연결해야 한다.
  • Nib 파일로부터 window의 rootViewController 속성을 구성할 수 있는데, 이 속성은 window의 root view를 구성한다.
  • Window가 로드될 때 이 속성이 설정되면 UIKit은 연결된 viewcontroller의 view를 window의 root view로 자동 설정한다.
  • 또한 Info.plist 내의 NSMainNibFile 키를 nib 파일의 이름으로 설정해야 한다.
  • Interface builder에서 window를 생성할 때, attributes inspector에서 launch option을 full screen으로 하는 것이 좋다. 이 옵션을 사용하지 않은 상태에서 window의 크기가 device의 화면보다 작으면 일부 view에서 터치 이벤트가 수신되지 않는다. 이는 window가 자신의 bounds 바깥의 터치이벤트는 받지 않기 때문이다.

storyboard 이용 방법

  • XCode의 기본 프로젝트 템플릿에서는 기본적으로 main window를 생성하며, AppDelegate 객체 내의 window property를 정의한다.
    앱의 기본 스토리 보드 파일을 만들고 Info.plist의 기본 스토리 보드로 설정하면 iOS에서 몇 가지 설정 작업을 수행한다.
    1. window를 인스턴스화 한다.
    2. 메인 스토리보드를 로드하고 initial ViewController를 인스턴스화 한다.
    3. 새 ViewController를 window의 rootViewController속성에 할당한 다음 window를 표시한다.
    4. 초기 ViewController가 표시되기 전에 ViewController를 구성할 수 있도록 AppDelegate가 호출된다.

Window Level

  • UIWindow 객체에는 해당 window가 다른 window를 기준으로 배치되는 방법을 결정하는 windowLevel 속성이 있다. Window가 다른 window에 대한 상대적인 z축 위치를 나타낸다.

@property(nonatomic) UIWindowLevel windowLevel; // default = 0.0

  • 새 window는 생성시 UIWindowLevelNormal 로 자동 지정된다. UIWindowLevelNormal 은 앱 관련 콘텐츠를 표시하는 window가 갖는다. 상위 레벨로는 UIWindowLevelAlertUIWindowLevelStatusBar 가 있는데, 상태표시줄이나 경고메세지와 같이 application 내용 위로 떠야 하는 정보를 위해 예약되어있다. (물론 이 레벨로 윈도우를 할당할 수 있지만 일반적으로는 시스템이 이 작업을 수행한다.)

Window의 변경 감지

  • Application이 window를 표시하거나 숨길 때, 그에 따라 UIWindowDidBecomeVisibleNotificationUIWindowDidBecomeHiddenNotification notification이 전달된다. 이는 application이 background로 전환될때는 전달되지 않는다 (applicataion의 context 내에서 보이는 것으로 간주 하기 때문)

  • UIWindowDidBecomeKeyNotification, UIWindowDidResignKeyNotification 이 두 notification의 역할은, application이 어떤 window가 key window인지 추적할 수 있게 해준다. 즉, 현재 키보드 이벤트 및 기타 비터치 관련 이벤트(좌표값이 없는 이벤트)를 받고 있는지 추적이 가능하다. 좌표값이 있는 터치 이벤트는 해당 터치가 발생한 window로 전달되는 반면, 좌표값이 없는 터치 이벤트는 application의 key window로 전달된다.

  • Custom Window를 만들 필요는 거의 없으나, 만들게 된다면 가장 대표적인 이유는 becomeKey() or resignKey() 메서드를 override하여 window의 key 상태가 변경될 때 사용자 정의 behavior를 구현하는 것이다.

  • key window

    • 가장 최근에 makeKeyAndVisible() 메세지를 받은 window.
    • 키보드 및 비터치(좌표 값이 없는) 이벤트를 받는 window.
    • 한 번에 하나의 window만 key window가 될 수 있다.

외부 디스플레이

  • UIWindow 클래스의 screen 속성(UIScreen의 인스턴스)는 window가 현재 표시되어있는 특정 Device의 디스플레이를 나타낸다. 이 property에는 screen의 bounds, mode, 밝기와 같은 device 디스플레이에 대한 정보가 포함된다.

  • 이 UIScreen 객체는 device의 디스플레이의 변경사항을 모니터링 할 수 있는 여러가지 noti들이 포함되어 있다. (디스플레이 연결 or 연결끊김, 디스플레이의 모드 or 밝기값 변경)

  • UIScreen 클래스는 사용 가능한 하드웨어 디스플레이를 나타내는 screen 객체 목록을 유지 관리한다.

  • 일반적으로 모든 iOS Device의 main display를 나타내는 screen 개체는 하나이지만, 외부 디스플레이 연결을 지원하는 Device는 추가 screen 개체를 사용할 수 있다

  • 외부 디스플레이에 컨텐츠를 표시하는 프로세스는 대략적으로 다음과 같다

    1. Application 시작시 Screen 연결 및 연결 해제 noti 등록.
    2. 외부 디스플레이에 내용을 표시할 시간이 되었을 때 Window를 작성하고 구성.
      1. UIScreen의 screen property를 사용하여 외부 디스플레이에 대한 screen 개체를 가져온다.
      2. UIWindow 객체를 만들고 screen에 맞게 크기 조정한다.
      3. 외부 디스플레이에 대한 UIScreen 객체를, window의 screen property에 지정한다.
      4. 컨텐츠에 맞게 screen의 해상도를 조정한다.
      5. 적당한 views를 window에 추가한다.
    3. Window를 표시하고 update한다.
  • 사용자가 디스플레이를 연결하거나 연결을 끊으면 시스템에서 응용 프로그램에 알맞은 알림을 전송하는데, 이러한 알림을 사용하여 응용 프로그램 상태를 업데이트하고 외부 디스플레이와 연결된 창을 만들거나 해제해야 한다.

  • 연결 및 연결 해제 noti는 application이 background에서 일시 중지 된 경우에도 언제든지 올 수 있다. 따라서 application delegate와 같이 application의 런타임동안 존재하게 될 개체로부터 noti를 관찰하는 것이 가장 좋다.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
    
    [center addObserver:self selector:@selector(handleScreenConnectNotification:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleScreenDisconnectNotification:)
                   name:UIScreenDidDisconnectNotification object:nil];
    
    return YES;
}

  • 외부 화면에 window를 표시하려면 올바른 screen 개체와 연관시켜야 한다. UIScreen의 screen 메서드에서 screen 객체 목록을 가져올 수 있는데, 이 메서드가 return하는 배열에는 적어도 메인 screen을 나타내는 객체가 1개 포함된다. 두 번째 객체가 있으면 이것은 연결된 외부 디스플레이를 의미한다.
  • window를 만들고 외부 디스플레이와 연결하고, window를 표시하기 전에 일부 placeholder 컨텐츠를 추가한다. 이 경우 placeholder content는 흰색 배경이고 표시할 컨텐츠가 없음을 나타내는 label이다. window를 표시하기 위해서는 makeKeyAndVisible을 호출하는 대신 hidden property 값을 변환한다.(새로운 window는 정적인 컨텐츠만 포함하고 이벤트 처리에는 사용되 않기 때문).
- (void)checkForExistingScreenAndInitializeIfPresent
{
    if ([[UIScreen screens] count] > 1)
    {
        // Associate the window with the second screen.
        // The main screen is always at index 0.
        UIScreen*    secondScreen = [[UIScreen screens] objectAtIndex:1];
        CGRect        screenBounds = secondScreen.bounds;
        
        _secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
        _secondWindow.screen = secondScreen;
        
        // Add a white background to the window.
        UIView*            whiteField = [[UIView alloc] initWithFrame:screenBounds];
        whiteField.backgroundColor = [UIColor blueColor];
        
        [_secondWindow addSubview:whiteField];
        
        // Go ahead and show the window.
        _secondWindow.hidden = NO;        //makeKeyAndVisible 대신.
    }
}

window rotation

  • 전반적 프로세스

    1. 장치 방향 변경
    2. System의 supportedInterfaceOrientation 호출 (Window 내의 topmost presentedVC나 rootVC) (supportedInterfaceOrientation 메서드는 shouldAutorotate가 YES인 경우에만 호출된다.)
    3. 해당 메서드에서 지원하는 방향과 장치의 방향이 같은지 판별
    4. window와 vc의 방향 전환
  • Rotation은 ViewController의 view size가 변경된 것으로 간주되기 때문에, viewWillTransition(to:with) 메서드에 의해 보고(?)된다. Interface의 방향이 변경되면 UIKit은 window의 rootViewController에서 viewWillTransition(to:with)를 호출한다. 그리고 childViewController에 알리고 ViewController 계층 전체에 전파한다.(어떤 식으로 전파하는거지?) 만약 viewController가 navigation Controller에 종속되어 있다면 navigation Controller의 룰을 따른다.

  • ViewController는 supportedInterfaceOrientation메서드를 override하여 지원하는 방향을 제한할 수 있다. 일반적으로 System은 이 메서드를 window의 rootViewController, 또는 화면 전체를 채우는 ViewController에서만 호출한다. 하위 ViewController는 상위 ViewController가 제공하는 window의 포지션을 사용한다.

  • 보이는 ViewController에 rotation이 발생하면 willRotate(to:duration:), willAnimateRotation(to:duration), didRotate(from:)이 호출되고, viewWillLayoutSubviews()는 view의 크기와 배치가 조정된 후에 호출된다. ViewController가 보이지 않으면 rotate관련 메서드는 호출되지 않지만, view가 보이게 되면 viewWillLayoutSubviews()가 호출된다.

  • Rotation handling은 key window에서 이루어지며(시스템적으로), 다른 window에서도 handling하기 위해서는 직접 구현해야 한다고 한다(정확한지 확인 필요) (https://stackoverflow.com/a/28696091/7072456) (http://shaune.com.au/using-multiple-uiwindows-in-ios-applications/)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment