.NET 8 の新規 .NET MAUI アプリで iOS の戻るスワイプを無効化しようとして CustomShellRenderer を Platforms/iOS に追加したのに、MauiProgram.cs から参照できない…。この症状は namespace と条件付きコンパイルのズレが原因で起きやすいです。原因の切り分けから確実な直し方、代替案までまとめます。
今回のテーマ:iOS の「どのスワイプ」を無効化したいのかを整理する
「iOS のスワイプを無効化」と一口に言っても、MAUI アプリでは対象がいくつかあります。まずは目的をはっきりさせると、実装の方向性がブレません。
| 無効化したいスワイプ | 典型例 | 主な発生場所 | 代表的な対処 |
|---|---|---|---|
| 戻るスワイプ(戻るジェスチャー) | 画面の左端から右へスワイプして前画面に戻る | iOS の UINavigationController / InteractivePopGestureRecognizer | ShellSectionRenderer で 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 を確認 |
| namespace | namespace 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 の戻るスワイプ(戻るジェスチャー)を意図どおりに制御できます。

コメント