.NET 8 の .NET MAUIでiOSの戻るスワイプを無効化する方法|CustomShellRendererがMauiProgram.csで見つからない原因と解決策

.NET 8 の新規 .NET MAUI アプリで iOS の戻るスワイプを無効化しようとして CustomShellRenderer を Platforms/iOS に追加したのに、MauiProgram.cs から参照できない…。この症状は namespace と条件付きコンパイルのズレが原因で起きやすいです。原因の切り分けから確実な直し方、代替案までまとめます。

目次

今回のテーマ:iOS の「どのスワイプ」を無効化したいのかを整理する

「iOS のスワイプを無効化」と一口に言っても、MAUI アプリでは対象がいくつかあります。まずは目的をはっきりさせると、実装の方向性がブレません。

無効化したいスワイプ典型例主な発生場所代表的な対処
戻るスワイプ(戻るジェスチャー)画面の左端から右へスワイプして前画面に戻るiOS の UINavigationController / InteractivePopGestureRecognizerShellSectionRenderer で InteractivePopGestureRecognizer を無効化
Shell の Flyout を開くスワイプ左端からスワイプしてメニュー(Flyout)を開くShell(Flyout)FlyoutBehavior の設定で制御
コントロール内のスワイプCarouselView / CollectionView のスワイプ、NavigationDrawer のスワイプ等各コントロールコントロール側のプロパティやイベントで制御

この記事で扱うのは、最も問い合わせが多い 「戻るスワイプ(Interactive Pop)」 と、それを実装する過程で発生しやすい 「MauiProgram.cs から CustomShellRenderer が見つからない」問題 です。

起きている症状:MauiProgram.cs で CustomShellRenderer が参照できない

.NET 8 の .NET MAUI(Shell 利用)で iOS の戻るスワイプを止めようとして、以下のような流れを踏むときに詰まりがちです。

  • Platforms/iOS に CustomShellRenderer.cs を追加
  • MauiProgram.cs にハンドラー登録(ConfigureMauiHandlers)を書いた
  • しかし MauiProgram.cs で CustomShellRenderer が解決できず、エラーまたは赤線が消えない

代表的なエラーは次のような形です。

CS0246: The type or namespace name 'CustomShellRenderer' could not be found
(are you missing a using directive or an assembly reference?)

結論から言うと、この問題は実装内容よりも 「名前空間(namespace)と参照(using / 条件付きコンパイル)」 の噛み合わせが原因であることがほとんどです。

結論:主因は「namespace と、iOS のときだけ参照する」という 2 点が揃っていないこと

MAUI の Single Project は、同一プロジェクトを複数の TargetFramework(iOS/Android/Windows…)でビルドします。

  • Platforms/iOS のコードは、基本的に iOS ターゲット(例:net8.0-ios)でのみコンパイルされる前提
  • MauiProgram.cs は全ターゲットでコンパイルされる前提(アプリの入り口だから)

この前提があるため、MauiProgram.cs から iOS 専用クラスを参照するには、次の 2 点がセットで必要になります。

  • CustomShellRenderer.cs 側の namespace が「プロジェクト名 + .Platforms.iOS」になっている
  • MauiProgram.cs 側では iOS のときだけ using/参照する(他プラットフォームでは存在しない前提)

最短で直すためのチェックリスト

いきなりコードを書き直す前に、ここを確認すると最短で解決します。

チェック項目ありがちな状態直し方
フォルダ名Plattforms/iOS など、Platforms のスペル違い・別階層Platforms/iOS 配下に置く(Solution Explorer 上でも確認)
Build Action誤って None になっているファイルのプロパティで Build Action = Compile を確認
namespacenamespace Platforms.iOS; のようにプロジェクト名が付いていないnamespace (プロジェクト名).Platforms.iOS; に修正
MauiProgram.cs の参照using が無い/別 namespace を using/無条件 using で他ターゲットが壊れる#if IOS 内で using する、または完全修飾名で参照

解決手順:namespace を揃え、iOS のときだけ using する

ここでは「プロジェクト名が MyApp」という前提で、コピペしやすい形にまとめます。実際のプロジェクト名に合わせて読み替えてください。

CustomShellRenderer.cs(Platforms/iOS)で namespace を正しくする

まず、Platforms/iOS/CustomShellRenderer.cs の namespace が MyApp.Platforms.iOS になっているか確認します。ここがズレていると MauiProgram.cs から参照できません。

using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Compatibility.Platform.iOS;

namespace MyApp.Platforms.iOS;

public class CustomShellRenderer : ShellRenderer
{
    protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection)
    {
        return new CustomSectionRenderer(this);
    }
}

public class CustomSectionRenderer : ShellSectionRenderer
{
    public CustomSectionRenderer(IShellContext context) : base(context)
    {
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        // iOS の戻るスワイプ(Interactive Pop)を無効化
        InteractivePopGestureRecognizer.Enabled = false;
    }
}

重要ポイント

  • namespace MyApp.Platforms.iOS; の MyApp がプロジェクトのルート namespace と一致していること
  • Shell を使う場合、単に Page 側で UINavigationController を触ろうとしても取得できないケースがあるため、ShellSectionRenderer 側で制御するのが安定しやすいこと

MauiProgram.cs で iOS のときだけ using して登録する

次に MauiProgram.cs を修正します。ポイントは using と登録コードの両方を iOS 条件にする ことです。

#if IOS
using MyApp.Platforms.iOS;
#endif

namespace MyApp;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();

        builder
            .UseMauiApp<App>()
            .ConfigureMauiHandlers(handlers =>
            {
#if IOS
                handlers.AddHandler(typeof(Shell), typeof(CustomShellRenderer));
#endif
            });

        return builder.Build();
    }
}

この形にすると、iOS では CustomShellRenderer を正しく登録でき、Android/Windows ビルドでは iOS 名前空間の参照をそもそもコンパイル対象から外せます。

「using を書きたくない」場合の小ワザ:完全修飾名で参照する

namespace の衝突を避けたい、using の管理を減らしたい、という場合は完全修飾名で書くのも手です。これなら using 自体が不要になります。

builder.ConfigureMauiHandlers(handlers =>
{
#if IOS
    handlers.AddHandler(typeof(Shell), typeof(MyApp.Platforms.iOS.CustomShellRenderer));
#endif
});

ただし、namespace を変えたときに書き換え漏れが起きやすいので、チーム開発では「using を #if IOS で囲う」運用のほうが読みやすいことが多いです。

なぜ「MauiProgram.cs から見えない」が起きるのか

ここを理解しておくと、今後 iOS/Android の依存コードを追加するときに同じ罠にハマりにくくなります。

  • MauiProgram.cs は複数ターゲットでビルドされる共通コード
  • Platforms/iOS は iOS ターゲットにだけ含まれる(=他ターゲットには存在しない)
  • よって MauiProgram.cs から iOS 専用 namespace を無条件に using すると、他ターゲットで参照エラーになる
  • 一方で、iOS 専用ファイルの namespace がプロジェクト名と一致していないと、iOS でビルドしていても解決できない

つまり「見つからない」の正体は、コンパイル条件の違い と 名前空間のズレ の合わせ技です。

よくある落とし穴と切り分け

症状原因の候補対処
iOS でビルドしても CustomShellRenderer が見つからないCustomShellRenderer.cs の namespace がズレている/ファイルが iOS コンパイルに入っていないnamespace を (プロジェクト名).Platforms.iOS に修正。フォルダを Platforms/iOS に置く
Android/Windows ビルドで「MyApp.Platforms.iOS が存在しない」using を無条件に書いている#if IOS で using を囲う
登録コードを #if IOS で囲ったのにエラーが残るusing を囲っていない/別 namespace を using しているusing も #if IOS に入れる。もしくは完全修飾名で参照する
IntelliSense 上は赤線だがビルドは通るIDE のマルチターゲット解決が追いついていないクリーン → 再ビルド、必要なら IDE 再起動

うまくいかない場合の代替案:ファイル名ベースのマルチターゲットで参照事故を減らす

プロジェクトが育ってくると、プラットフォーム別コードが増えて「どれが iOS 専用なのか」が見えづらくなります。そこで有効なのが ファイル名ベースのマルチターゲット です。

例:iOS 専用コードを CustomShellRenderer.iOS.cs のように命名し、iOS 以外のターゲットでは *.iOS.cs をコンパイル対象から外します。

<!-- iOS 以外では *.iOS.cs をコンパイルしない -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-ios')) != true">
  <Compile Remove="**\\*.iOS.cs" />
  <None Include="**\\*.iOS.cs"
        Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

この方式のメリットは次のとおりです。

  • 「iOS 専用ファイル」が一目で分かり、レビューが楽になる
  • 誤って共通コードから参照してしまう事故を減らせる
  • Platforms フォルダに置く/置かないの運用をチームで統一しやすい

もう一つの代替案:MauiProgram.cs に #if IOS でクラスごと直書きする

小規模アプリや検証用途で「今すぐ動けばOK」というときは、iOS 用クラス定義を #if IOS の中に丸ごと書いてしまう方法もあります。namespace のズレ問題が起きにくい反面、ファイルが肥大化しやすいので恒久対応には向きません。

#if IOS
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Compatibility.Platform.iOS;

namespace MyApp;

public class CustomShellRenderer : ShellRenderer
{
    protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection)
        => new CustomSectionRenderer(this);
}

public class CustomSectionRenderer : ShellSectionRenderer
{
    public CustomSectionRenderer(IShellContext context) : base(context) { }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();
        InteractivePopGestureRecognizer.Enabled = false;
    }
}
#endif

応用:特定ページだけ戻るスワイプを無効化したい場合

アプリ全体で戻るスワイプを止めると、iOS の標準操作を大きく変えてしまいます。実務では、次のような画面だけピンポイントで止めたいケースも多いはずです。

  • 決済・本人確認など、途中で戻られると状態が壊れるフロー
  • 初期設定ウィザードなど、順序が重要な画面
  • 戻るとクラッシュする既知問題の一時回避

ページ単位で調整したい場合、表示タイミングで iOS の NavigationController を辿って InteractivePopGestureRecognizer を切り替えるアプローチがあります。以下は「入ったら無効化、離れたら元に戻す」例です。

public partial class PaymentPage : ContentPage
{
    bool? _prevEnabled;

    public PaymentPage()
    {
        InitializeComponent();
        Loaded += OnLoaded;
        Unloaded += OnUnloaded;
    }

    void OnLoaded(object? sender, EventArgs e)
    {
#if IOS
        if (Handler is IPlatformViewHandler h)
        {
            var nav = h.ViewController?.NavigationController;
            var gr = nav?.InteractivePopGestureRecognizer;
            if (gr != null)
            {
                _prevEnabled = gr.Enabled;
                gr.Enabled = false;
            }
        }
#endif
    }

    void OnUnloaded(object? sender, EventArgs e)
    {
#if IOS
        if (_prevEnabled is bool enabled && Handler is IPlatformViewHandler h)
        {
            var nav = h.ViewController?.NavigationController;
            var gr = nav?.InteractivePopGestureRecognizer;
            if (gr != null) gr.Enabled = enabled;
        }
#endif
    }
}

この方法は柔軟ですが、アプリの Shell 構造や遷移タイミングによって NavigationController が取得できないこともあるため、実装はプロジェクト構成に合わせて調整してください。安定性を最優先するなら、まずは ShellRenderer で全体無効化 → 必要が出たらページ単位へ、という段階的アプローチが現実的です。

実運用での注意点

注意点なぜ重要かおすすめの対策
戻るスワイプ無効化は UX に直結iOS では標準操作として定着しているため代替の戻る/キャンセルボタンを用意し、ユーザーが迷わない導線にする
Shell と NavigationPage は取得できるネイティブが違うShell では「現在の ViewController」が UINavigationController とは限らないShell を使うなら ShellSectionRenderer で制御する
iOS と MacCatalyst の分岐ターゲットやシンボルが異なる(挙動も異なる可能性)必要に応じて #if IOS || MACCATALYST などで条件を調整する

まとめ:見つからない問題は「名前空間」と「iOS のときだけ参照」でほぼ解決できる

MauiProgram.cs で CustomShellRenderer が見つからないときは、ほとんどの場合、次の 2 点に戻ると解決できます。

  • CustomShellRenderer.cs の namespace を (プロジェクト名).Platforms.iOS に揃える
  • MauiProgram.cs では #if IOS 内で using と登録コードを行う(または完全修飾名で参照する)

この土台が整えば、.NET 8 の .NET MAUI でも iOS の戻るスワイプ(戻るジェスチャー)を意図どおりに制御できます。

この記事を書いた人

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

コメント

コメントする

目次