View in English

  • 메뉴 열기 메뉴 닫기
  • Apple Developer
검색
검색 닫기
  • Apple Developer
  • 뉴스
  • 둘러보기
  • 디자인
  • 개발
  • 배포
  • 지원
  • 계정
페이지에서만 검색

빠른 링크

5 빠른 링크

비디오

메뉴 열기 메뉴 닫기
  • 컬렉션
  • 주제
  • 전체 비디오
  • 소개

더 많은 비디오

스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.

  • 소개
  • 요약
  • 자막 전문
  • 코드
  • UIKit 앱을 더욱 유연하게 만들기

    iPhone, iPad, Mac 및 Apple Vision Pro에서 장면 및 컨테이너 뷰 컨트롤러를 사용하여 UIKit 앱을 더욱 유연하게 만드는 방법을 알아보세요. 앱 중심의 수명 주기에서 장면 기반의 수명 주기로 전환하여 향상된 윈도우 크기 조절 및 개선된 멀티태스킹 등 앱의 잠재력을 최대한 발휘할 수 있습니다. 상호작용 방식의 열 크기 조정 및 Inspetcor 열의 온전한 지원 등 UISplitViewController의 향상된 기능을 살펴보세요. 새로운 레이아웃 API를 채택하여 뷰와 제어의 적응력을 더욱 높일 수도 있습니다.

    챕터

    • 0:00 - Introduction
    • 0:58 - Scenes
    • 4:58 - Container view controllers
    • 10:45 - Adaptivity
    • 15:39 - Future compatibility
    • 16:07 - Next steps

    리소스

    • TN3187: Migrating to the UIKit scene-based life cycle
    • UIKit updates
      • HD 비디오
      • SD 비디오

    관련 비디오

    WWDC25

    • UIKit의 새로운 기능

    WWDC24

    • iPadOS에서 탭 및 사이드바 경험 향상하기
  • 비디오 검색…

    Hi there, welcome to “Make your UIKit app more flexible.” My name is Alexander MacLeod, and I'm an engineer on the UIKit team. A flexible app delivers an amazing experience across a variety of sizes and platforms. It maintains a familiar and intuitive navigation experience at any size. In this video, I will talk about some of the best practices to ensure that your app is flexible. First, I will go over the fundamentals of scenes, and share how they are foundational to a flexible application.

    Next, I will cover container view controllers, such as UISplitViewController and UITabBarController, and explore how they bring flexibility to your app. Finally, I will talk about APIs to support you in building an adaptive, and truly flexible UI.

    I’ll start with scenes.

    A scene is an instance of your app’s UI. It contains your app’s view controllers and views. Scenes provide hooks for handling external data, like a URL for deep linking to a section of your app’s UI.

    Each scene independently saves and restores UI state. A scene determines the best opportunities to ask for the current state, before persisting it to disk. You can query the previous UI state when a scene reconnects. This enables you to restore your scene exactly how it was before. Scenes also provide context on how your app is displayed, including details about the screen, and the window’s geometry.

    You can have multiple scenes, each with their own lifecycle and state.

    Dedicated scene types are designed to encapsulate distinct experiences. For example, a messaging app can have a dedicated compose scene for sending new messages. In iOS 26, you can now mix SwiftUI and UIKit scene types in a single app. Check out “What’s new in UIKit” for more. The portability that scenes provide are the perfect foundation for a flexible app.

    As scenes are vital for ensuring flexibility, adopting UIScene life cycle will soon be mandatory. In the next major release following iOS 26, UIScene life cycle will be required when building with the latest SDK.

    While supporting multiple scenes is encouraged, only the adoption of scene life cycle is required. For details on how to adopt UIScene life cycle, read the tech note: “Migrating to the UIKit scene-based life cycle.” Because scenes are so important, I will show you an example of them in practice. I have developed an app that tracks the time I spend on a particular task. It has a feature where I can AirPlay the current task to an Apple TV.

    It is the responsibility of the app delegate to determine the scene configuration for a connecting session. In the configurationForConnecingSceneSession delegate method, I check the scene session’s role.

    If the role is a non-interactive external display, I return a bespoke scene configuration. Otherwise, the main scene configuration is preferred. Each configuration is defined in the app's Info.plist file.

    UISceneDelegate manages the life cycle of an individual scene.

    In sceneWillConnectToSession, I first create a window, and associate it with the connecting scene. Note, if your scene configuration specifies a storyboard, window creation happens automatically.

    I specify the window’s root view controller and provide it with scene-specific data, like the timer model.

    For my app, it is important to pause the timer when the scene moves to the background. To achieve this, I implement the sceneDidEnterBackground delegate method and pause the timer.

    I handle state restoration to ensure that the UI state of a connecting scene is exactly how it was left before.

    My scene delegate provides a state restoration activity, which can include selections, navigation paths, and other UI state. The system persists this UI state, associating it with the scene instance. If the scene later reconnects, the state restoration activity is made available in the restoreInteractionStateWith userActivity delegate method. By populating the timer model with info from the user activity, I ensure that the UI state of the connecting scene is exactly how it was left before.

    By adopting UIScene life cycle, I have strong foundations for a flexible application. Now, I will cover container view controllers, and explain how they are vital for building a flexible application. A container view controller is responsible for managing the layout of one or more child view controllers. UIKit provides a number of container view controllers that are designed to be flexible. First, I will talk about UISplitViewController.

    UISplitViewController manages the display of multiple adjacent columns of content, supporting seamless navigation throughout a hierarchy of information. When horizontal space is limited, the split view controller adapts by collapsing its columns into a navigation stack. UISplitViewController gains a host of new features, starting with interactive column resizing.

    You can now resize columns by dragging the split view controller’s separators. When using the pointer, its shape will adapt to indicate the directions in which a column can be resized. UISplitViewController provides a default minimum, maximum, and preferred width for each column.

    There may be columns in your app that prefer displaying content at greater widths, or only require a fraction of the default width to remain functional. You can customize the minimum, maximum, and preferred widths of each column using their associated split view controller properties. Be careful not to require a width that limits the number of columns that can be displayed, as this reduces the flexibility of your app. Your UI may need to adapt depending on whether the split view controller is expanded or collapsed.

    In Mail, disclosure indicators are shown when the split view controller is collapsed, to convey additional content can be revealed upon cell selection.

    A new trait, split view controller layout environment, conveys whether an ancestor split view controller is expanded or collapsed. In this example, the trait is queried to conditionally add a disclosure indicator when the split view controller is collapsed. Also new, is first-class support for inspector columns.

    An inspector is a column within a split view controller that provides additional details of the selected content. Preview uses an inspector to display metadata alongside the photo in the secondary column. When the split view controller is expanded, the inspector column resides on the trailing edge, adjacent to the secondary column.

    When collapsed, the split view controller adapts automatically, and presents the inspector column as a sheet.

    To incorporate an inspector in your split view controller, specify a view controller for the inspector column. When the split view controller first appears, the inspector column is hidden. Call show to display the inspector column. UISplitViewController is designed to be flexible, and will ensure that your app delivers the best navigation experience at any size.

    Another container at your disposal is UITabBarController.

    UITabBarController displays multiple, mutually exclusive panes of content, in the same area. The tab bar enables quick switching between tabs, while preserving the current state within each pane.

    What’s more, the appearance of the tab bar adapts for each platform.

    On iPhone, the tab bar is located at the bottom of the scene.

    On Mac, the tab bar can reside in the toolbar or can be displayed as a sidebar.

    On Apple Vision Pro, the tab bar is displayed in an ornament on the leading edge of the scene. On iPad, the tab bar resides at the top of the scene alongside navigation controls.

    The tab bar can also adapt into a sidebar, allowing quick access to collections of content.

    Tab groups surface additional destinations in the sidebar. For example, in the Music app on iPad, the Library tab group includes Artists, Albums, and more.

    When the sidebar is not available, the Library group is a tab destination.

    UITabBarController offers API to seamlessly manage this adaptation. First, provide the tab group with a managing navigation controller. When a leaf tab of the tab group is selected, its view controller, along with the view controllers of its ancestor groups, are pushed onto this navigation stack.

    To customize the view controllers pushed onto this navigation stack, implement the UITabBarController delegate method, displayedViewControllersFor tab.

    In this example, when the library tab cannot be selected, the delegate method returns an empty array to omit the library tab’s view controller from the stack.

    For more on how UITabBarController offers flexibility to display a tab bar or sidebar, watch "Elevate your tab and sidebar experience in iPadOS” from WWDC24. Adopting container view controllers, such as UISplitViewController and UITabBarController, is the best way to ensure your app is flexible. While these containers are designed to support a wide range of sizes, your app may require a minimum size to maintain core functionality.

    You can use the UISceneSizeRestrictions API to express the preferred minimum size of your scene’s content. The best time to specify the minimum size is when the scene is about to connect. In this example, I specify a preferred minimum width of 500 points.

    For your app to be truly flexible, your own UI should be able to adapt. Next, I will talk about APIs that will support you in building an adaptive UI.

    A crucial step in making your UI adaptable is to ensure that content remains within the safe area. The safe area is a region within a view that is appropriate for interactive or important content. Content placed outside of this region is vulnerable to getting covered, such as by a navigation bar or a toolbar.

    Content could also be occluded by system UI like the status bar, or even device features, like the Dynamic Island.

    The sidebar adds a non-symmetrical safe area inset to the adjacent column in a split view controller. The background can freely extend outside of the safe area, underneath the sidebar.

    Content, such as the message transcript, is positioned within the safe area to ensure that is remains visible. The message bubbles are inset from the edges of the safe area using layout margins. This provides consistent spacing, and clear visual separation from the sidebar.

    Each view provides layout guides to apply standard margins around content. Layout margins are inset from the safe area by default.

    In this example, I request a layout guide for positioning content inside the container view.

    I then use this layout guide to configure constraints for the content view.

    In iPadOS 26, scenes gain a new control to close, minimize, and arrange the window, similar to macOS. The window control appears alongside the content in your scene.

    A scene can specify a preferred windowing control style to compliment its content.

    To specify a preference, implement the UIWindowSceneDelegate method preferredWindowingControlStyle for scene.

    System components, such as UINavigationBar, adapt automatically by arranging their subviews around the window control. Your UI should also adapt to the window control, regardless of its style.

    To ensure that your UI is not occluded, use a layout guide that accounts for the window control.

    In this example, I request a layout margins guide with a horizontal corner adaptation.

    This layout guide is great for bar-like content at the top of a scene, which should be inset from the trailing edge of the window control. I then use this layout guide to configure constraints for the content view. When your UI is adaptive, the interface orientation should be redundant. Scene resizing, device rotation, and changes to window layout, all ultimately result in a modification to your scene’s size. Certain categories of apps may benefit from temporarily locking the orientation. For example, a driving game may want to lock the orientation when the device is expected to rotate for steering a vehicle.

    When a view controller is visible, it can prefer a locked interface orientation. To specify a preference, override prefersInterfaceOrientationLocked in your view controller subclass.

    Whenever this preference changes, call setNeedsUpdateOfPrefersInterfaceOrientationLocked.

    To observe the interface orientation lock, implement the UIWindowSceneDelegate method, didUpdateEffectiveGeometry. Then, compare whether the value of isInterfaceOrientationLocked has changed.

    For your app to be truly adaptable, it should respond quickly to being resized. There may be elements of your app’s UI that are computationally expensive to draw.

    This is common for games, where a number of assets may need to be resized when the scene changes size.

    Re-rendering assets for every size within a resize interaction is unnecessary.

    In this example, isInteractivelyResizing is queried to only update assets for a new scene size after the interaction finishes.

    Flexible apps empower people to use their devices how they want. They provide great experiences across a wide range of sizes, allowing them to be used in any orientation or layout. The UIRequiresFullscreen Info.plist key is a compatibility mode from iOS 9 that prevents scene resizing. UIRequiresFullscreen is deprecated and will be ignored in a future release.

    Apps that are adaptable do not need this key, and should remove it.

    There is another compatibility mode, specifically for new hardware. Previously, when new hardware was released with a different screen size, the system would scale or letterbox your app’s UI. That scaling would stay in place until you built with a newer SDK and resubmitted your app.

    Once you build and submit with the iOS 26 SDK, the system will no longer scale or letterbox your app’s UI for a new screen size.

    These are the best practices to ensure that your app is flexible. So what’s next? Adopt scene life cycle in your app to ensure strong foundations for a flexible application. Use container view controllers to manage components of your UI. Finally, leverage APIs like layout guides to support you in building an adaptive UI. I can't wait to see your apps become more flexible. Thank you!

    • 3:02 - Specify the scene configuration

      // Specify the scene configuration
      
      @main
      class AppDelegate: UIResponder, UIApplicationDelegate {
      
          func application(_ application: UIApplication,
                           configurationForConnecting sceneSession: UISceneSession,
                           options: UIScene.ConnectionOptions) -> UISceneConfiguration {
      
              if sceneSession.role == .windowExternalDisplayNonInteractive {
                  return UISceneConfiguration(name: "Timer Scene",
                                              sessionRole: sceneSession.role)
              } else {
                  return UISceneConfiguration(name: "Main Scene",
                                              sessionRole: sceneSession.role)
              }
          }
      }
    • 3:30 - Configure the UI

      // Configure the UI
      
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
          var window: UIWindow?
          var timerModel = TimerModel()
      
          func scene(_ scene: UIScene,
                     willConnectTo session: UISceneSession,
                     options connectionOptions: UIScene.ConnectionOptions) {
      
              let windowScene = scene as! UIWindowScene
              let window = UIWindow(windowScene: windowScene)
              window.rootViewController = TimerViewController(model: timerModel)
              window.makeKeyAndVisible()
              self.window = window
          }
      }
    • 3:56 - Handle life cycle events

      // Handle life cycle events
      
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
          var window: UIWindow?
          var timerModel = TimerModel()
      
          // ...
      
          func sceneDidEnterBackground(_ scene: UIScene) {
              timerModel.pause()
          }
      }
    • 4:09 - Restore UI state

      // Restore UI state
      
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
          var window: UIWindow?
          var timerModel = TimerModel()
      
          // ...
      
          func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
              let userActivity = NSUserActivity(activityType: "com.example.timer.ui-state")
              userActivity.userInfo = ["selectedTimeFormat": timerModel.selectedTimeFormat]
              return userActivity
          }
      
          func scene(_ scene: UIScene restoreInteractionStateWith userActivity: NSUserActivity) {
              if let selectedTimeFormat = userActivity?["selectedTimeFormat"] as? String {
                  timerModel.selectedTimeFormat = selectedTimeFormat
              }
          
      }
    • 4:46 - Adapt for the split view controller layout environment

      // Adapt for the split view controller layout environment
      
      override func updateConfiguration(using state: UICellConfigurationState) {
         
          // ...
          
          if state.traitCollection.splitViewControllerLayoutEnvironment == .collapsed {
              accessories = [.disclosureIndicator()]
          } else {
              accessories = []
          }
      }
    • 6:11 - Customize the minimum, maximum, and preferred column widths

      // Customize the minimum, maximum, and preferred column widths
      
      let splitViewController = // ...
      
      splitViewController.minimumPrimaryColumnWidth = 200.0
      splitViewController.maximumPrimaryColumnWidth = 400.0
      splitViewController.preferredSupplementaryColumnWidth = 500.0
    • 7:37 - Show an inspector column

      // Show an inspector column
      
      let splitViewController = // ... 
      splitViewController.setViewController(inspectorViewController, for: .inspector)
      
      splitViewController.show(.inspector)
    • 9:19 - Managing tab groups

      // Managing tab groups
      
      let group = UITabGroup(title: "Library", ...)
      group.managingNavigationController = UINavigationController()
      
      // ...
      
      // MARK: - UITabBarControllerDelegate
      
      func tabBarController(
          _ tabBarController: UITabBarController,
          displayedViewControllersFor tab: UITab,
          proposedViewControllers: [UIViewController]) -> [UIViewController] {
      
          if tab.identifier == "Library" && !self.allowsSelectingLibraryTab {
              return []
          } else {
              return proposedViewControllers
          }
      }
    • 10:25 - Preferred minimum size

      // Specify a preferred minimum size
      
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
      
          func scene(_ scene: UIScene,
                     willConnectTo session: UISceneSession,
                     options connectionOptions: UIScene.ConnectionOptions) {
      
              let windowScene = scene as! UIWindowScene
              windowScene.sizeRestrictions?.minimumSize.width = 500.0
          }
      }
    • 11:57 - Position content using the layout margins guide

      // Position content using the layout margins guide
      
      let containerView = // ...
      let contentView = // ...
      
      let contentGuide = containerView.layoutMarginsGuide
      
      NSLayoutConstraint.activate([
          contentView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
          contentView.leadingAnchor.constraint(equalTo: contentGuide.leadingAnchor),
          contentView.bottomAnchor.constraint(equalTo: contentGuide.bottomAnchor)
          contentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor)
      ])
    • 12:34 - Specify the window control style

      // Specify the window control style
      
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
      
          func preferredWindowingControlStyle(
              for scene: UIWindowScene) -> UIWindowScene.WindowingControlStyle {
              return .unified
          }
      }
    • 13:04 - Respect the window control area

      // Respect the window control area
      
      let containerView = // ...
      let contentView = // ...
      
      let contentGuide = containerView.layoutGuide(for: .margins(cornerAdaptation: .horizontal)
      
      NSLayoutConstraint.activate([
          contentView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
          contentView.leadingAnchor.constraint(equalTo: contentGuide.leadingAnchor),
          contentView.bottomAnchor.constraint(equalTo: contentGuide.bottomAnchor),
          contentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor)
      ])
    • 13:57 - Request orientation lock

      // Request orientation lock
      
      class RaceViewController: UIViewController {
      
          override var prefersInterfaceOrientationLocked: Bool {
              return isDriving
          }
      
          // ...
      
          var isDriving: Bool = false {
              didSet {
                  if isDriving != oldValue {
                      setNeedsUpdateOfPrefersInterfaceOrientationLocked()
                  }
              }
          }
      }
    • 14:18 - Observe the interface orientation lock

      // Observe the interface orientation lock
      
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
          var game = Game()
      
          func windowScene(
              _ windowScene: UIWindowScene,
              didUpdateEffectiveGeometry previousGeometry: UIWindowScene.Geometry) {
              
              let wasLocked = previousGeometry.isInterfaceOrientationLocked
              let isLocked = windowScene.effectiveGeometry.isInterfaceOrientationLocked
      
              if wasLocked != isLocked {
          game.pauseIfNeeded(isInterfaceOrientationLocked: isLocked)
              }
          }
      }
    • 14:44 - Query whether the scene is resizing

      // Query whether the scene is resizing
      
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
          var gameAssetManager = GameAssetManager()
          var previousSceneSize = CGSize.zero
      
          func windowScene(
              _ windowScene: UIWindowScene,
              didUpdateEffectiveGeometry previousGeometry: UIWindowScene.Geometry) {
      
              let geometry = windowScene.effectiveGeometry
              let sceneSize = geometry.coordinateSpace.bounds.size
      
              if !geometry.isInteractivelyResizing && sceneSize != previousSceneSize {
                  previousSceneSize = sceneSize
                  gameAssetManager.updateAssets(sceneSize: sceneSize)
              }
          }
      }
    • 0:00 - Introduction
    • Make UIKit apps flexible and adaptable across different screen sizes and platforms using scenes, container view controllers, and other APIs.

    • 0:58 - Scenes
    • Scenes represent distinct instances of an app's UI. Each Scene independently manages its state, and seamlessly restores upon reconnection. Scenes provide context about the app's display, such as screen size and window geometry. Starting with the next major release following iOS 26, adopting the UIScene life cycle will be mandatory.

    • 4:58 - Container view controllers
    • Container view controllers like 'UISplitViewController' and 'UITabBarController' manage the layout of one or more child view controllers. They help make apps flexible, adaptable, and customizable. Use the UISceneRestrictions API to express the minimum size for scenes in the app.

    • 10:45 - Adaptivity
    • Layout guides and margins help position an app’s content consistently within the device’s safe area. iPadOS 26 introduces a new window control. Apps can specify 'preferredWindowingControlStyle' and layout guides to accommodate these controls. Adaptive UIs need to respond quickly to resizing and orientation changes, but certain apps may want to override 'prefersInterfaceOrientationLocked' to temporarily lock the orientation. For computationally expensive operations, check 'isInteractivelyResizing' to perform the operations after an interaction finishes. 'UIRequiresFullscreen' is deprecated.

    • 15:39 - Future compatibility
    • With iOS 26 SDK, apps automatically adapt to new screen sizes without needing manual updates or resubmission.

    • 16:07 - Next steps
    • To build a flexible app, adopt scene life cycle, use container view controllers, and leverage APIs like layout guides.

Developer Footer

  • 비디오
  • WWDC25
  • UIKit 앱을 더욱 유연하게 만들기
  • 메뉴 열기 메뉴 닫기
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    메뉴 열기 메뉴 닫기
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    메뉴 열기 메뉴 닫기
    • 손쉬운 사용
    • 액세서리
    • 앱 확장 프로그램
    • App Store
    • 오디오 및 비디오(영문)
    • 증강 현실
    • 디자인
    • 배포
    • 교육
    • 서체(영문)
    • 게임
    • 건강 및 피트니스
    • 앱 내 구입
    • 현지화
    • 지도 및 위치
    • 머신 러닝
    • 오픈 소스(영문)
    • 보안
    • Safari 및 웹(영문)
    메뉴 열기 메뉴 닫기
    • 문서(영문)
    • 튜토리얼
    • 다운로드(영문)
    • 포럼(영문)
    • 비디오
    메뉴 열기 메뉴 닫기
    • 지원 문서
    • 문의하기
    • 버그 보고
    • 시스템 상태(영문)
    메뉴 열기 메뉴 닫기
    • Apple Developer
    • App Store Connect
    • 인증서, 식별자 및 프로파일(영문)
    • 피드백 지원
    메뉴 열기 메뉴 닫기
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(영문)
    • News Partner Program(영문)
    • Video Partner Program(영문)
    • Security Bounty Program(영문)
    • Security Research Device Program(영문)
    메뉴 열기 메뉴 닫기
    • Apple과의 만남
    • Apple Developer Center
    • App Store 어워드(영문)
    • Apple 디자인 어워드
    • Apple Developer Academy(영문)
    • WWDC
    Apple Developer 앱 받기
    Copyright © 2025 Apple Inc. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침