.NET MAUI(Mac Catalyst)でウィンドウを最前面に出す方法|最前面固定(Always On Top)が難しい理由と代替案

Windows の .NET MAUI では AppWindow / OverlappedPresenter.IsAlwaysOnTop でウィンドウを前面に出せても、Mac Catalyst では同じ感覚で最前面化・最前面固定ができません。本記事では理由と、現実的な回避策を整理します。

目次

今回の「最前面」問題を先に結論だけ整理

同じ「最前面」と言っても、実は求めている挙動がいくつか混ざりがちです。まずは混線をほどくため、結論を端的にまとめます。

  • 最前面固定(Always On Top)を Mac Catalyst で確実に実現することは難しい:macOS ネイティブ(AppKit)で使える NSWindow の “ウィンドウレベル操作” を、Mac Catalyst では同等に扱えないためです。
  • 「前面に出す(アクティブ化)」も万能ではない:OS 側の設計上、ユーザー操作なしに既存ウィンドウを前面化できないケースがあります。
  • .NET MAUI 9 以降は Application.Current.ActivateWindow が追加:Mac Catalyst と Windows で「特定ウィンドウを前に出す」ための API が用意されました。ただし、これは “最前面固定” とは別物です。

「どうしても最前面固定が必須」なら、Mac Catalyst に固執せず macOS(AppKit)側のアプリを別途用意する、あるいは仕様を UX で置き換える、という判断が現実的になります。

用語の整理:最前面化と最前面固定は別物

後から設計をやり直す羽目にならないように、「どの最前面」を求めているのかを最初に切り分けておくのが重要です。

やりたいことユーザーの体感代表キーワード難易度(Mac Catalyst)
アプリ(または特定ウィンドウ)を前面に出す隠れていたウィンドウが目の前に出てくるBring to front / Activate / Focus条件付き(OS 制約あり)
ウィンドウを常に最前面に固定する他アプリの上に常に居座るAlways On Top / Floating困難(実質不可)
同一アプリ内の複数ウィンドウを切り替える「2枚目のウィンドウ」が前に出るMulti-window / Scene activation.NET MAUI 9+ で改善

Windows(WinUI)では AppWindow / IsAlwaysOnTop で解決しやすい

Windows 側は WinUI の AppWindow と OverlappedPresenter.IsAlwaysOnTop が使えるため、「一瞬だけ AlwaysOnTop を true → false に切り替えて前面化する」などのトリックが成立します。

代表的な実装イメージは次のようになります(※概念例です)。

#if WINDOWS
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;

var winuiWindow = (Window)Application.Current.Windows[0].Handler.PlatformView!;
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(winuiWindow);
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd);

var appWindow = AppWindow.GetFromWindowId(windowId);
var presenter = (OverlappedPresenter)appWindow.Presenter;

// 前面に出すトリック(環境により挙動が異なることがあります)
presenter.IsAlwaysOnTop = true;
presenter.IsAlwaysOnTop = false;
#endif

この発想をそのまま Mac Catalyst に持ち込むと、ほぼ確実に壁にぶつかります。理由は「MAUI だから」ではなく、Mac Catalyst が提供するウィンドウ制御 API の範囲にあります。

Mac Catalyst で同じことが難しい理由

Mac Catalyst は “macOS アプリ” ではなく、UIKit ベースの移植環境

.NET MAUI の Mac 向けターゲットは macOS(AppKit)ではなく、Mac Catalyst です。Catalyst は iPad アプリ(UIKit)を macOS へ持っていくための仕組みで、AppKit のフル機能を前提にしません。

その結果、macOS ネイティブ(AppKit)なら素直にできる NSWindow 操作(例:ウィンドウレベルを上げて浮かせる、前面に並べる、といった制御)を、Mac Catalyst では同じ形で扱えません。

「ユーザー操作なしで前面を奪う」こと自体が制限される

iOS / iPadOS と同様に、Mac Catalyst でも “ユーザーが操作していないのに勝手に前面に出る” 体験は嫌われます。公開情報でも、ユーザーの操作でセッションをアクティブにする必要があり、既存ウィンドウをプログラムだけで前面化できない旨が説明されています。

ここは「技術的に無理」というより、OS の UI ポリシー(勝手にフォーカスを奪わない)という観点で理解しておくと、設計の判断が早くなります。

.NET MAUI は現時点で “Mac Catalyst” を主ターゲットとしている

「Mac なのに AppKit が使えないのはつらい」という声は昔からありますが、少なくとも公開されている回答では、MAUI が macOS(非 Catalyst)を正式にターゲットする計画は明言されていません。

Mac Catalyst で“できること/できないこと”を表で整理

要件定義の時点でここを押さえると、無駄な調査コストを大幅に減らせます。

要件Windows(WinUI)Mac Catalyst(.NET MAUI)macOS(AppKit / NSWindow)
任意タイミングでウィンドウを前面に出す比較的やりやすい(API が豊富)制限あり(ユーザー操作や OS ポリシーの影響)可能(設計次第で柔軟)
常に最前面(Always On Top)IsAlwaysOnTop 等で対応可能困難(UIWindow のレベル操作も期待通り動かない例が報告)NSWindow の level 等で実現しやすい
複数ウィンドウを開き、特定ウィンドウを前に出す可能.NET MAUI 9+ の ActivateWindow で改善可能

Mac Catalyst で試せる実装と、なぜ“効かない”のか

手段A:UIWindow.WindowLevel を上げる(ただし期待通り動かない)

直感的には「UIWindow.WindowLevel を Alert 相当に上げれば最前面になるのでは?」と考えがちです。実際にその方針でコードを書けます。

#if MACCATALYST
using UIKit;

var uiWindow = Application.Current?.Windows[0].Handler?.PlatformView as UIWindow;
if (uiWindow != null)
{
    uiWindow.WindowLevel = UIWindowLevel.Alert;
}
#endif

しかし、公開されている Q&A では 「Mac Catalyst では UIWindow の level を設定しても動かない」と明言されています。つまり、この方向で深追いしても “最前面固定” は実現しづらい、というのが現実です。

手段B:.NET MAUI 9+ の Application.Current.ActivateWindow を使う

.NET MAUI 9 では、Mac Catalyst と Windows で Application.Current.ActivateWindow が追加され、「特定ウィンドウを前に出す」ための API が用意されました。

基本の使い方はシンプルです。

「既に開いているウィンドウを再利用したい」場合は、Window インスタンスを保持しておき、無ければ OpenWindow、あれば ActivateWindow という分岐にすると分かりやすいです。

private Window? _secondWindow;

void ShowOrActivateSecondWindow()
{
    if (_secondWindow == null)
    {
        _secondWindow = new Window(new SecondWindowPage());
        _secondWindow.Destroying += (_, __) => _secondWindow = null;

        Application.Current?.OpenWindow(_secondWindow);
        return;
    }

    // .NET MAUI 9+(Mac Catalyst / Windows)
    Application.Current?.ActivateWindow(_secondWindow);
}
var targetWindow = /* 既に開いている Window インスタンス */;
Application.Current?.ActivateWindow(targetWindow);

ただし重要なのは、これは “最前面固定(Always On Top)” を保証する API ではないという点です。狙いはあくまで「複数ウィンドウがあるとき、ユーザーが意図したウィンドウを前に出す」ことで、他アプリを押しのけて常に最前面に居座る用途とは目的が違います。

また、Mac Catalyst 側は OS のポリシーや状態(アプリがバックグラウンドなのか、ユーザー操作の流れの中なのか等)によって期待通りにならない場面があり得ます。要件として「必ず前面に出る」を置くと、テストで苦しみやすいポイントです。

手段C:RequestSceneSessionActivation(UserActivity)で“既存ウィンドウを開き直す”発想

「同じウィンドウを何枚も開くのではなく、既にある 2 枚目を表示したい」という要望は多いです。このケースでは、Mac Catalyst では UserActivity と RequestSceneSessionActivation を組み合わせて “該当シーンをアクティブ化する” 例が紹介されています。

#if MACCATALYST
using UIKit;
using Microsoft.Maui.Platform;

void ActivateSecondWindow(ContentPage secondPage)
{
    // secondPage の Handler が生成済みである前提(作り方次第で null 対応が必要)
    var nativeView = secondPage.Handler?.PlatformView as UIView;
    var userActivity = nativeView?.Window?.WindowScene?.UserActivity;
    if (userActivity == null) return;

    UIApplication.SharedApplication.RequestSceneSessionActivation(
        sceneSession: null,
        userActivity: userActivity,
        options: null,
        errorHandler: null
    );
}
#endif

注意点として、紹介されている情報では RequestSceneSessionActivation が iOS 17 / Mac Catalyst 17 で非推奨になったこと、そして MAUI 側で新しい API が完全には追従していない可能性が示されています。つまり、この実装は “今後も長期で安心して使える” とは限りません。

“最前面固定”が必須要件なら、Mac Catalyst 以外の道を検討する

ここまで見てきた通り、Mac Catalyst 単体で「常に最前面」を成立させるのは難しく、仮に一時的にできても OS や SDK 更新で崩れやすい領域です。

最前面固定がビジネス要件として絶対に必要なら、選択肢は大きく 2 つになります。

選択肢メリットデメリット向いているケース
macOS(AppKit)アプリを別で用意するNSWindow レベルで柔軟な制御が可能プロジェクト分割・UI 二重実装になりやすいユーティリティ/常駐ツール/フローティング必須
要件を UX で置き換えるMac Catalyst のまま進めやすい「必ず前面」を捨てる必要がある認証フロー・通知・復帰導線で十分なアプリ

また、MAUI 自体は macOS(非 Catalyst)を正式ターゲットとしていない旨が公開回答で触れられているため、「将来 MAUI が AppKit を直接触れるようになるまで待つ」戦略は読みづらいです。

UX で“最前面固定”を代替する現実的な設計アイデア

認証・外部ブラウザ連携なら「戻り導線」を強くする

「ブラウザで OAuth ログイン → コールバック受信 → アプリを前面に出したい」は、最前面要求が出やすい典型例です。このケースは “アプリが勝手に前面に出る” よりも、ユーザーが自然に戻れる導線を作る方が、審査・UX 両面で安全です。

  • コールバック受信後、アプリ内で完了画面を用意し、次にやるべき操作を明確に表示する
  • 「Dock のアイコンをクリックして戻ってください」など、行動を具体的に書く
  • 処理が完了したら画面上部にトーストやバナーで通知し、ユーザーのクリックで前面化を促す

ローカル通知で「ユーザーがクリックして前面に戻す」

Mac Catalyst でも通知を使った “ユーザー主導の復帰” は設計として自然です。プログラムが勝手にフォーカスを奪うのではなく、ユーザーが通知をクリックして戻るため、OS のポリシーとも整合します。

実装方針としては UNUserNotificationCenter を使い、イベント(認証完了・同期完了など)でローカル通知を出すのが定番です。通知のタップでアプリが前面に戻れば、目的を達成できます(※細部はアプリ構成や権限設定に依存します)。

「最前面で見せたい情報」をウィンドウではなく UI として設計する

常に最前面にしたい情報が “小さなステータス表示” 程度なら、最前面固定ウィンドウではなく、次のような代替も検討できます。

  • アプリ内の常時表示領域(サイドバー・ステータス領域)に寄せる
  • 別ウィンドウではなく、モーダル/ポップアップで済ませる(ユーザー操作が必要になるが安全)
  • 通知+アプリ内履歴(「いつでも後から見返せる」)でストレスを減らす

クロスプラットフォームで破綻しない実装パターン

「Windows は最前面化できるが、Mac Catalyst はできない」という状況は、アプリの設計で受け止めるのが最も安全です。おすすめは プラットフォームサービス化して、Mac Catalyst 側は “何もしない(または代替導線を出す)” 実装に落とすことです。

インターフェースを切り、OS ごとに実装を分ける

public interface IWindowAttentionService
{
    void TryBringToFront();
}

Windows 実装例:

#if WINDOWS
public class WindowsWindowAttentionService : IWindowAttentionService
{
    public void TryBringToFront()
    {
        var winuiWindow = (Microsoft.UI.Xaml.Window)Application.Current.Windows[0].Handler.PlatformView!;
        var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(winuiWindow);
        var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd);


    var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);
    var presenter = (Microsoft.UI.Windowing.OverlappedPresenter)appWindow.Presenter;

    presenter.IsAlwaysOnTop = true;
    presenter.IsAlwaysOnTop = false;
}


}
#endif

Mac Catalyst 実装例(できないことを前提にした no-op):

#if MACCATALYST
public class MacCatalystWindowAttentionService : IWindowAttentionService
{
    public void TryBringToFront()
    {
        // OS 制約により「常に前面」を保証できないため、ここでは何もしない。
        // 代替として、アプリ内バナー表示や通知のトリガーに寄せるのが現実的。
    }
}
#endif

この形にしておくと、仕様変更(「Mac では通知にする」「Mac では ActivateWindow を使える範囲だけ使う」など)を後から差し替えやすくなります。

複数ウィンドウを扱う場合の注意点(Mac Catalyst)

Mac Catalyst で複数ウィンドウ(マルチシーン)を扱うには、追加設定が必要です。具体的には SceneDelegate を追加し、Info.plist に Scene 設定を入れる手順がドキュメントに記載されています。

「ActivateWindow を呼んでいるのに動かない」「Window インスタンスが想定と違う」といったトラブルの多くは、そもそもマルチウィンドウの前提設定が揃っていないケースです。Mac Catalyst 側のウィンドウ設計をするなら、まずはこの前提を整えたうえで検証するのが近道です。

よくある質問

Mac Catalyst で “常に最前面” をどうしてもやりたい。抜け道はある?

AppKit の非公開 API や Swift ライブラリ経由で無理やり触る、といった話は見かけますが、長期運用・審査・保守の観点でリスクが高くなります。公開回答でも「Catalyst は AppKit の一部しか触れない」ことが前提として述べられています。まずは要件を UX に寄せるか、AppKit アプリを別途用意する判断が堅実です。

.NET MAUI 9 の ActivateWindow があれば解決する?

「自アプリの複数ウィンドウのうち、特定のウィンドウを前に出す」には有効な場面があります。一方で、他アプリを押しのけて前面に出す、あるいは常に最前面に固定する、といった要件を満たす万能 API ではありません。

Window の位置やサイズをコードで変えれば前面に出せない?

Mac Catalyst では、Window のリサイズやリポジションをプログラムで行うこと自体が制限される旨がドキュメントに明記されています。前面化の代替として位置調整でごまかす、というアプローチも取りづらいのが実情です。

まとめ:Mac Catalyst の制約を前提に設計すると迷いが減る

Mac Catalyst で「ウィンドウを最前面に出す/最前面固定する」を Windows と同じ発想で実現しようとすると、API の壁と OS ポリシーの壁に当たりやすいです。公開されている情報でも、最前面固定は難しく、UIWindow の WindowLevel を上げる方法も期待通り動かない例が示されています。

そのうえで、アプリの目的に応じて次のように切り分けるのが現実解です。

  • 「同一アプリ内の別ウィンドウを前に出す」→ ActivateWindow や Scene の仕組みを使って実現を検討する
  • 「他アプリより常に上に表示したい」→ Mac Catalyst では諦め、AppKit アプリ化 or UX 代替へ寄せる

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次