Xcode, Objective-C / 親ウィンドウの中央に移動させる

macOSの標準のアラートダイアログを使用しないで,しかし同じような目的のダイアログボックスなどを作ると,親ウィンドウの場所にかかわらず,その中央に出したくなります.
しかし,比較的古いOSでも大丈夫なようにしようとして,とても苦労しました.

座標系のこと

macOSでは,画面表示関係で3種類の座標系もあったりして,NSScreenにまつわる画面左下原点の座標系でコードを書いていたら,どうも出現位置がおかしい.今回書いた関数では,CGWindowの座標系で動くメソッドを使用していて,それらが混ざり合ってめんどくさいことになっていました.

(そもそもNSScreenの左下原点座標系の時点で,各ウィンドウの背景に独自のビットマップを配置したりすると,その画像は左上原点だったりしてとてもめんどくさかった.)

親ウィンドウの位置とサイズがわからないとき

また,親ウィンドウそのもののサイズが取得しづらい場所で,この独自アラートを表示しようとすると,どの位置に表示すればいいかわかりません・・・

というわけで今回作成したメソッドを共有します

入力は,動かしたいウィンドウ(NSWindow *)と,親ウィンドウのタイトル(NSString *)としました.

ウィンドウのタイトルが色々変わるような場合は・・・適当にあしらってください.

/// Move specified window to the center of parent window by the parent's window title text.
/// Note Y coordinate because the NSScreen and CGWindow coordinate systems are opposite.
/// @param targetWindow (NSWindow *) the window to move
/// @param parentWindowName (NSString *) the parent window title to be identified
- (void)moveWindow:(NSWindow *)targetWindow ToCenterOfWindowNamed:(NSString *)parentWindowName{

    // Get all the windows
    CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
    NSArray* arr = CFBridgingRelease(windowList);

    // Loop through the windows
    for (NSMutableDictionary* entry in arr)
    {
        if (nil == [entry objectForKey:(id)kCGWindowName])
            continue;

        NSString *windowName = [[NSString alloc] initWithString:[entry objectForKey:(id)kCGWindowName]];
        NSLog(@"Window title = %@", windowName);
        if (![parentWindowName isEqualToString:windowName])
            continue;

        // Screen Height
        float sh = [[NSScreen mainScreen] frame].size.height;
        // Parent window frame
        CGRect pf;
        CGRectMakeWithDictionaryRepresentation((CFDictionaryRef)[entry objectForKey:(id)kCGWindowBounds], &pf);
        // Child window frame to be moved
        CGRect cf = [targetWindow frame];
        // Compute destination
        float x = pf.origin.x + (pf.size.width - cf.size.width) / 2.0;
        float y = sh - (pf.origin.y + pf.size.height) + (pf.size.height - cf.size.height) / 2.0;
        // Then move the target window
        NSRect frame = [targetWindow frame];
        frame.origin = CGPointMake(x, y);
        [targetWindow setFrame:frame display:YES];
    }
}