View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

その他のビデオ

ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。

  • 概要
  • トランスクリプト
  • コード
  • SwiftUIでのウインドウの操作

    visionOS、macOS、iPadOSで、シングルウインドウとマルチウインドウの優れたアプリを構築する方法を学びましょう。ウインドウを開く/閉じる操作をプログラムで実行したり、位置やサイズを調整したり、ウインドウを別のウインドウと交換したりできるツールをご紹介します。また、ユーザーが各自のワークフロー内でアプリを使用しやすいウインドウを実現するための、デザインの原則についても確認します。

    関連する章

    • 0:00 - Introduction
    • 1:19 - Fundamentals
    • 6:15 - Placement
    • 9:43 - Sizing
    • 12:03 - Next steps

    リソース

    • BOT-anist
    • Forum: UI Frameworks
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC24

    • SwiftUIによるmacOSウインドウのカスタマイズ

    WWDC23

    • 空間コンピューティングのためのウインドウ表示型アプリの向上
    • 空間ユーザーインターフェイスのためのデザイン
  • このビデオを検索

    こんにちは SwiftUI担当のAndrewです 本セッションでは SwiftUIアプリの ウインドウについて説明します

    ウインドウとは アプリのコンテンツを 格納する入れ物です

    ウインドウにより 使い慣れた操作方法で アプリの様々な部分を管理できます

    位置の変更や

    サイズの変更

    閉じるなどの操作です

    今回の説明には BOT-anistを使います 私が同僚と開発している SwiftUIアプリです この画面は Simulatorでの BOT-anistのロボットエディタです ロボットをカスタマイズする画面です

    プレイヤーは このロボットがゲーム内で 植物の世話をするのを手伝います

    BOT-anistはiOS iPadOS visionOS macOSで パーソナライズされた体験を提供します

    今回説明する概念はマルチウインドウの プラットフォームにも適用可能ですが 本ビデオでは visionOSのみを対象とします

    まず ウインドウの定義と作成 および使用の方法について説明します

    次に ウインドウの初期位置を 制御する方法を解説します また ウインドウのサイズ変更の 各種方法をご紹介します

    まずは基本事項です

    独立した複数の ウインドウを使うと アプリの 様々な部分を同時に扱えます

    同じインターフェイスのインスタンスを 複数作成すると効果的な場合もあります

    システムコントロールを使えば 各ウインドウを個別に操作できます

    サイズの変更や位置の変更 拡大などの操作が可能です

    また ウインドウでは プラットフォーム固有の機能を利用できます 例えば visionOSでは ボリュメトリックウインドウスタイルにより ウインドウに3Dコンテンツを表示できます

    複数ウインドウの使用にも利点がありますが TabViewのように最上位メニューのみを 表示する単一のビューは ユーザー体験をシンプル化します

    「Elevate your windowed app for spatial computing」では TabViewやその他の最上位ビューの 詳細について解説しています visionOSで複数ウインドウの使用が 適している場合の詳細については 「Design for spatial user interfaces」をご覧ください visionOSのBOT-anistには 主要なシーンが2つあります エディタウインドウと ゲームのボリュームです

    各シーンはWindowGroupで 定義されています アプリでは ロボットエディタの WindowGroupの インスタンスが開きます このウインドウのボタンで ゲームのWindowGroupの インスタンスが開きます windowStyleを .volumetricにして ウインドウをvisionOSの ボリュームにします BOT-anistに新しい機能を 2つ追加しましょう

    1つ目は ロボットに関する動画を表示する 新しいウインドウを開く機能です

    この動画はポータルに含まれる 3Dシーンになります

    アプリのbodyに 3Dシーンビューを格納する 新しいWindowGroupを追加します このWindowGroupを識別できるように 「movie」というIDを指定しています このIDはウインドウを開く際に使用します

    このIDを Environmentアクションに渡します これらのアクションは SwiftUIの 階層構造のどこからでも使用できます ウインドウ管理には 複数の Environmentアクションを使用します

    ウインドウを開くには openWindowを使用します

    閉じるには dismissWindowを使用します

    pushWindowを使うと ウインドウを 開いて元のウインドウを非表示にできます

    ここではopenWindowを使って 新しい動画ウインドウを開きます ロボットエディタのビューで Environmentから openWindowアクションを 取得するために openWindowのキーパスを指定して Environmentプロパティを作成します 次に 新しいボタンで openWindowアクションを実行しますが ここで 先ほどWindowGroup用に 定義したIDである「movie」を指定します

    これで エディタのボタンをタップすると 動画のポータルが 別ウインドウで表示されるようになりました

    こうして見てみると 動画のビューと同時に エディタを 表示しておく必要はないと思えます そこで Environmentアクションの pushWindowを ウインドウ表示に使用します この方法では 新しいウインドウが 元のウインドウと入れ替わりに開きます

    新しいウインドウを閉じると 元のウインドウが再度表示されます

    動画ウインドウ表示時に エディタを非表示にするには Environmentプロパティの キーパスを openWindowから pushWindowに変更し 前と異なるアクションを呼び出すように ボタンを更新します

    これで TVのボタンをタップすると 動画ウインドウがプッシュされ エディタウインドウが消えるようになります

    これで デザインしたロボットが 演技を始めるのを 気を散らされることなく 見られるようになりました

    閉じるボタンをタップすると エディタに戻ります この動作を実現するために ロジックの追加は必要ありません ウインドウを開く時に 表示しておく 必要のないコンテンツがある場合は このアクションの使用を検討してください

    ウインドウを定義して開いたら プラットフォーム固有の機能を使って さらに快適になるように機能を強化できます 例えば フリーボードで ツールバーオーナメントを使って ウインドウの下端に沿って コントロールを表示する方法や ToolbarTitleMenuで 画面を混雑させずに ドキュメントの関連アクションを 表示する方法などです

    ウインドウバーと閉じるボタンは デフォルトで必ず表示されます しかし 動画ビューでは .persistentSystem Overlaysモディファイアを使って これらを非表示にしました ユーザーが 動画に集中しやすくなるためです これらのAPIは visionOSのウインドウの 機能を強化する優れた手段です

    macOSでの ウインドウの調整については 「Tailor macOS windows with SwiftUI」をご覧ください 動画ウインドウの表示を改善できたので 次に ゲームで使用するオプションの コントロールパネルを追加します このパネルには ロボットを動かすための コントロールと ジャンプや手を振るなどのアクションを 実行するボタンを表示します

    コントロールを表示する 新しいWindowGroupを追加しました

    また ゲームのボリュームに openWindowの呼び出しを追加します

    ゲームでボタンをタップすると 新しい ウインドウでコントロールが表示されます

    ゲームのボリュームから独立して 位置を変えられる点が優れています

    しかし 初めてウインドウを開くと 表示がボリュームに重なるか 位置が遠い場合もあります visionOSでは コントロールパネルなどの 新しいウインドウは 元のウインドウの前面に配置されます

    一方macOSでは 新しいウインドウは 画面の中央に表示されます

    この動作は defaultWindowPlacement モディファイアでカスタマイズでき ウインドウの初期位置とサイズを プログラムで設定できます

    ウインドウの位置やサイズの変更方法は プラットフォームごとに複数あります

    配置には 前面や背面など ほかのウインドウを基準とする相対配置や visionOSのutilityPanel のようにユーザーを基準とする 相対配置があります 後者ではウインドウを ユーザーの近くの 通常は直接タッチできる範囲に配置します

    または macOSでの右上エリアのように 画面に対する相対配置もあります

    visionOSでゲームコントロールを プレイヤーの近くに表示するには defaultWindowPlacement モディファイアをcontrollerの WindowGroupに適用します ここから .utilityPanelの 位置を指定して WindowPlacementを返します

    この戻り値をif条件でラップし この配置が visionOSの場合のみ 適用されるようにします

    これで ウインドウの初回起動時に コントロールが近くに表示されます また プレイヤーは必要に応じて ウインドウを初期位置から動かせます

    この新しいコントロールでは まったく新しいやり方で ロボットを操作できます

    例えば このボタンをタップすると BOT-anistが手を振ります

    visionOSでのコントローラウインドウ のデザインを改善できました 次はmacOSで このウインドウの位置を 手動で計算します

    defaultWindowPlacement モディファイアはcontextを提供します プラットフォームによって ここに含まれる情報は異なります macOSでは contextに格納されるのは デフォルトのディスプレイに関する情報です これにアクセスして .visibleRectを取得します これは コンテンツを 問題なく配置できる場所を表します

    sizeThatFitsメソッドを使用して ウインドウのコンテンツに基づき 必要となるサイズを参照します

    displayBounds変数と size変数を使用して ディスプレイの下端の少し上の 水平方向の中央に表示されるよう 位置を計算します

    これで 算出した位置とサイズを WindowPlacementとして返せます

    macOSでも コントロールが適切に 配置されるようになりました

    プレイヤーはプレイ中に ウインドウの位置を自由に変更でき 別の画面に配置することもできます

    優れたウインドウ配置にできました コンテンツが常に 最適に表示されるようにするために ウインドウサイズの変更方法にも 変更を加えましょう

    ウインドウには システムにより決定される 初期サイズがあります このデフォルトのサイズは いくつかの方法で変更できます

    画面のサイズやほかのウインドウに応じて サイズが決まる場合は defaultWindowPlacement APIを使用して初期サイズを指定できます これは macOSの コントローラウインドウの場合と同様です defaultSizeモディファイアで 初期サイズを変更する方法もあります

    なお このデフォルトのサイズは サイズが 別途制約されている場合は使用されません WindowPlacement APIで サイズが指定されている場合や シーンが復元された場合です 先ほど追加した動画ウインドウのように プッシュされるウインドウの場合 defaultSizeは 元のウインドウのサイズと同じになります この例では 元のウインドウは ロボットエディタです

    このデフォルトサイズに問題はありませんが プレイヤーは動画ウインドウのサイズを 変更したいかもしれません 動画が常に適切に表示されるように 一定の制限を設けましょう

    movieのWindowGroupの .windowResizabilityに .contentSizeを指定すると そのウインドウは 含まれるコンテンツの 最小/最大サイズに基づく制限を受けます MovieContentViewに minWidthとmaxWidth およびminHeightと maxHeightを追加します

    これで動画ウインドウのサイズを 最小限にすると正方形になり 拡大も合理的な範囲に 制限されるようになりました

    一日中 BOT-anistを見ていられそうです しかし コントロールウインドウに 手を入れる必要があります

    非常に大きなサイズに変更可能なので ボリュームの邪魔になります

    ウインドウのサイズを そこに含まれる コンテンツのサイズに 合うようにするべきです

    movieの WindowGroupの場合と同様に windowResizability モディファイアを controllerの WindowGroupにも追加します

    これで コントローラのモードを変更すると コンテンツに合わせて ウインドウのサイズが変わります

    このウインドウのサイズを プレイヤーは変更できません 各モードのビューのサイズは固定で 最小/最大サイズが 指定されているのではないためです

    BOT-anistは 本当に良くなりましたね visionOSおよびmacOS向けに 素晴らしい改善をアプリに加えられました みなさんのアプリでも ウインドウと それをサポートするAPIをご活用ください

    ウインドウと最上位ビューのどちらが アプリに適しているか検討しましょう WindowPlacement APIは 最初のレイアウトを指定する際に有用です コンテンツに合わせてウインドウサイズを 設定し ユーザーによる変更を制限できます

    ウインドウに関する プラットフォーム固有の機能により ユーザーによるアプリ利用が より快適になります

    ご視聴ありがとうございましたアプリで ウインドウの機能をぜひご活用ください

    • 2:36 - BOT-anist scenes

      @main
      struct BOTanistApp: App {
          var body: some Scene {
              WindowGroup(id: "editor") {
                  EditorContentView()
              }
      
              WindowGroup(id: "game") {
                  GameContentView()
              }
              .windowStyle(.volumetric)
          }
      }
    • 3:09 - Creating the movie WindowGroup

      @main
      struct BOTanistApp: App {
          var body: some Scene {
              WindowGroup(id: "editor") {
                  EditorContentView()
              }
      
              WindowGroup(id: "game") {
                  GameContentView()
              }
              .windowStyle(.volumetric)
      
              WindowGroup(id: "movie") {
                  MovieContentView()
              }
          }
      }
    • 3:55 - Opening a movie window

      struct EditorContentView: View {
          @Environment(\.openWindow) private var openWindow
      
          var body: some View {
              Button("Open Movie", systemImage: "tv") {
                  openWindow(id: "movie")
              }
          }
      }
    • 4:45 - Pushing a movie window

      struct EditorContentView: View {
          @Environment(\.pushWindow) private var pushWindow
      
          var body: some View {
              Button("Open Movie", systemImage: "tv") {
                  pushWindow(id: "movie")
              }
          }
      }
    • 5:34 - Toolbar

      CanvasView()
          .toolbar {
              ToolbarItem {
                  Button(...)
              }
              ...
          }
    • 5:40 - Title menu

      CanvasView()
          .toolbar {
              ToolbarTitleMenu {
                  Button(...)
              }
              ...
          }
    • 5:48 - Hiding window controls

      WindowGroup(id: "movie") {
          ...
      }
      .persistentSystemOverlays(.hidden)
    • 6:28 - Creating the controller window

      @main
      struct BOTanistApp: App {
          var body: some Scene {
              ...
      
              WindowGroup(id: "movie") {
                  MovieContentView()
              }
      
              WindowGroup(id: "controller") {
                  ControllerContentView()
              }
          }
      }
    • 6:34 - Opening the controller window

      struct GameContentView: View {
          @Environment(\.openWindow) private var openWindow
      
          var body: some View {
              ...
              Button("Open Controller", systemImage: "gamecontroller.fill") {
                  openWindow(id: "controller")
              }
          }
      }
    • 7:46 - Positioning the controller window

      WindowGroup(id: "controller") {
          ControllerContentView()
      }
      .defaultWindowPlacement { content, context in
          #if os(visionOS)
          return WindowPlacement(.utilityPanel)
          #elseif os(macOS)
          ...
          #endif
      }
    • 8:45 - Positioning the controller window continued

      WindowGroup(id: "controller") {
          ControllerContentView()
      }
      .defaultWindowPlacement { content, context in
          #if os(visionOS)
          return WindowPlacement(.utilityPanel)
          #elseif os(macOS)
          let displayBounds = context.defaultDisplay.visibleRect
          let size = content.sizeThatFits(.unspecified)
          let position = CGPoint(
              x: displayBounds.midX - (size.width / 2),
              y: displayBounds.maxY - size.height - 20
          )
          return WindowPlacement(position, size: size)
          #endif
      }
    • 10:12 - Default size

      @main
      struct BOTanistApp: App {
          var body: some Scene {
              ...
              WindowGroup(id: "movie") {
                  MovieContentView()
              }
              .defaultSize(width: 1166, height: 680)
          }
      }
    • 10:49 - Setting resize limits on the movie window

      @main
      struct BOTanistApp: App {
          var body: some Scene {
              ...
              WindowGroup(id: "movie") {
                  MovieContentView()
                      .frame(
                          minWidth: 680, maxWidth: 2720,
                          minHeight: 680, maxHeight: 1020
                      )
              }
              .windowResizability(.contentSize)
          }
      }
    • 11:37 - Controller window resizability

      @main
      struct BOTanistApp: App {
          var body: some Scene {
              ...
              WindowGroup(id: "controller") {
                  ControllerContentView()
              }
              .windowResizability(.contentSize)
          }
      }

Developer Footer

  • ビデオ
  • WWDC24
  • SwiftUIでのウインドウの操作
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン