View in English

  • Global Nav Open Menu Global Nav Close Menu
  • Apple Developer
Search
Cancel
  • Apple Developer
  • News
  • Discover
  • Design
  • Develop
  • Distribute
  • Support
  • Account
Only search within “”

Quick Links

5 Quick Links

Vídeos

Abrir menu Fechar menu
  • Coleções
  • Tópicos
  • Todos os vídeos
  • Sobre

Voltar para WWDC25

  • Sobre
  • Resumo
  • Transcrição
  • Código
  • Deixe o seu app do UIKit mais flexível

    Descubra como seu app do UIKit pode ser mais flexível no iPhone, iPad, Mac e Apple Vision Pro usando cenas e controladores de visualização de contêiner. Saiba como aproveitar todo o potencial do seu app fazendo a transição de um ciclo de vida centrado no app para um ciclo de vida baseado em cenas, incluindo redimensionamento melhorado de janelas e multitarefa aprimorada. Explore as melhorias do UISplitViewController, como redimensionamento interativo de colunas e compatibilidade avançada com colunas de inspetor. Além disso, deixe as visualizações e controles mais adaptáveis adotando novas APIs de layout.

    Capítulos

    • 0:00 - Introdução
    • 0:58 - Cenas
    • 4:58 - Controladores de visualização de contêiner
    • 10:45 - Adaptabilidade
    • 15:39 - Compatibilidade futura
    • 16:07 - Próximas etapas

    Recursos

    • TN3187: Migrating to the UIKit scene-based life cycle
    • UIKit updates
      • Vídeo HD
      • Vídeo SD

    Vídeos relacionados

    WWDC25

    • Novidades do UIKit

    WWDC24

    • Elevate your tab and sidebar experience in iPadOS
  • Buscar neste vídeo...

    Olá! Esta é a sessão "Deixar o seu app do UIKit mais flexível". Sou Alexander MacLeod, engineer da equipe UIKit. Um app flexível oferece uma experiência incrível em vários tamanhos e plataformas. Ele tem uma experiência de navegação familiar e intuitiva em qualquer tamanho. Neste vídeo, vou falar sobre algumas boas práticas para garantir a flexibilidade no seu app. Primeiro, vou detalhar as noções básicas sobre as cenas e como elas são fundamentais para um app flexível. Depois vou falar sobre controladores de visualização de contêiner, como UISplitViewController e UITabBarController, e como eles trazem flexibilidade ao app. Por fim, vou discorrer sobre APIs para ajudar na criação de uma interface adaptativa e totalmente flexível. Vou começar com as cenas. Cena é uma instância da interface do seu app. Ela inclui os controladores de visualização e as visualizações do app. As cenas fornecem ganchos para lidar com dados externos, como URL de link direto para uma seção da interface.

    Cada cena salva e restaura o estado da interface de forma independente. Ela determina as melhores oportunidades para consultar o estado atual, antes de gravá-lo no disco. Você pode consultar o estado anterior da interface quando uma cena se reconecta. Isso permite restaurar a cena exatamente como era antes. As cenas também dão contexto sobre como seu app é exibido, incluindo detalhes sobre a tela e geometria da janela. Você pode ter várias cenas, cada uma com o próprio ciclo de vida e estado. Tipos de cena dedicados são projetados para experiências distintas. Por exemplo, um app de mensagens pode ter uma cena de redação para envio de novas mensagens. No iOS 26, agora é possível misturar tipos de cena SwiftUI e UIKit em um único app. Confira "Novidades do UIKit" para saber mais. A portabilidade que as cenas proporcionam é a base perfeita para um app flexível.

    Como elas são vitais para flexibilidade, em breve será obrigatório adotar o ciclo de vida do UIScene. Na próxima versão após o iOS 26, o ciclo de vida do UIScene será necessário ao compilar com o SDK mais recente.

    Embora o suporte a várias cenas seja recomendável, só a adoção do ciclo de vida da cena é obrigatória. Para obter detalhes sobre como adotar o ciclo de vida do UIScene, leia a nota técnica: "Migrar para o ciclo de vida baseado na cena UIKit". As cenas são muito importantes, então vou mostrar um exemplo delas na prática. Desenvolvi um app que rastreia o tempo que gasto em uma tarefa específica. Ele pode enviar uma tarefa atual para a Apple TV via AirPlay. É responsabilidade do delegado do app definir a configuração da cena para uma sessão de conexão. No método delegado configurationForConnecingSceneSession, verifico a função da sessão de cena. Se ela for um monitor externo não interativo, uma configuração de cena ajustada será retornada. Se não, a configuração da cena principal é preferida. Cada configuração é definida no arquivo Info.plist do app.

    O UISceneDelegate gerencia o ciclo de vida de uma cena individual. No sceneWillConnectToSession, primeiro crio uma janela e a associo à cena de conexão. Se a configuração da cena especificar um storyboard, a criação da janela ocorrerá automaticamente.

    Especifico o controlador de visualização raiz da janela e envio dados específicos da cena, como modelo de timer. No meu app, é importante pausar o timer quando a cena passar para o segundo plano. Para isso, implemento o método delegado sceneDidEnterBackground e pauso o timer. Faço a restauração de estado para garantir que o estado da interface de uma cena de conexão não tenha mudado. O delegado fornece uma atividade de restauração de estado, que pode incluir seleções, caminhos de navegação e outro estado. O sistema registra esse estado, associando-o à instância da cena. Se a cena se reconectar depois, a atividade de restauração de estado será disponibilizada no método delegado restoreInteractionStateWith userActivity. Ao preencher o modelo de timer com informações da atividade, confiro se o estado da interface da cena de conexão não mudou.

    Ao adotar o ciclo de vida do UIScene, consigo criar um app flexível. Agora, vou falar sobre os controladores de visualização de contêiner e sua importância para criar um app flexível. Um controlador de visualização de contêiner é responsável por gerenciar o layout de um ou mais controladores de visualização secundários. O UIKit fornece vários controladores de visualização de contêiner projetados para serem flexíveis. Primeiro, vou falar sobre o UISplitViewController. O UISplitViewController gerencia a visualização de várias colunas de conteúdo, oferecendo suporte à navegação contínua em uma hierarquia de informações. Quando o espaço horizontal é limitado, o splitViewController se adapta recolhendo as colunas em uma pilha de navegação. O UISplitViewController tem vários novos recursos, começando com redimensionamento interativo de colunas. Você pode redimensionar colunas arrastando os separadores do splitViewController. Quando se usa o ponteiro, seu formato mudará para indicar as direções em que a coluna pode ser redimensionada. O UISplitViewController fornece uma largura padrão, mínima, máxima e preferencial para cada coluna. É possível que haja colunas no seu app que precisem exibir conteúdo em larguras maiores ou menores que a largura padrão para permanecer funcionais. Você pode personalizar as larguras mínima, máxima e preferencial de cada coluna usando as propriedades associadas do splitViewController. Cuidado para não exigir uma largura que limite o número de colunas que podem ser exibidas, pois isso reduz a flexibilidade do app. A interface pode precisar se adaptar dependendo se o splitViewController está expandido ou recolhido. No Mail, quando o Split View está recolhido, indicadores de divulgação mostram que há conteúdo extra que será revelado ao selecionar a célula. Um novo trait, splitViewControllerLayoutEnvironment, indica se um splitViewController ancestral está expandido ou recolhido. No exemplo, o trait é consultado para adicionar condicionalmente um disclosureIndicator quando o splitViewController está recolhido. Outra novidade é o suporte de primeira classe para colunas de inspetor. Um inspetor é uma coluna em um splitViewController que fornece detalhes do conteúdo selecionado. A pré-visualização usa um inspetor para exibir metadados ao lado da foto na coluna secundária. Quando o splitViewController é expandido, a coluna do inspetor fica na borda à direita, adjacente à coluna secundária.

    Quando recolhido, o splitViewController se adapta automaticamente e mostra a coluna do inspetor como planilha.

    Para incorporar um inspetor no splitViewController, especifique um viewController para a coluna do inspetor. Quando o splitViewController aparece pela primeira vez, a coluna do inspetor fica oculta. Chame Show para exibir a coluna do inspetor. O UISplitViewController foi projetado para ser flexível e garantir que seu app ofereça a melhor experiência de navegação em qualquer tamanho. Outro contêiner que você pode usar é o UITabBarController. O UITabBarController exibe vários painéis de conteúdo mutuamente exclusivos, na mesma área. A barra de abas permite a troca rápida entre abas, preservando o estado atual dentro de cada painel. Além disso, a aparência da barra de abas adapta-se a cada plataforma.

    No iPhone, ela fica na parte inferior da cena. No Mac, ela pode ficar na barra de ferramentas ou ser exibida como barra lateral.

    No Apple Vision Pro, a barra de abas é exibida com um ornamento à esquerda da cena. No iPad, ela fica na parte superior da cena, ao lado dos controles de navegação. A barra de abas também pode se adaptar a uma barra lateral, permitindo acesso rápido a coleções de conteúdo. Os grupos de abas mostram destinos adicionais na barra lateral. Por exemplo, no app Música no iPad, o grupo de abas Biblioteca inclui Artistas, Álbuns e muito mais.

    Quando a barra lateral não está disponível, Biblioteca é um destino de abas.

    O UITabBarController tem uma API para gerenciar essa adaptação. Primeiro, forneça ao grupo um managingNavigationController. Quando uma aba final do grupo de abas é selecionada, os viewControllers do grupo e dos grupos ancestrais são adicionados à pilha de navegação. Para personalizar os viewControllers dessa pilha de navegação, implemente o método delegado UITabBarController, displayedViewControllersForTab. Neste exemplo, a aba Biblioteca não pode ser selecionada, e o método delegado retorna uma matriz vazia para omitir o controlador de visualização da aba Biblioteca na pilha.

    Para saber mais sobre a flexibilidade do UITabBarController nas barras de abas e lateral, confira "Aprimorar a experiência nas barras de abas e lateral no iPadOS", da WWDC24. Adotar controladores de visualização de contêiner, como UISplitViewController e UITabBarController, é a melhor forma de garantir que seu app seja flexível. Embora esses contêineres sejam projetados para permitir vários tamanhos, seu app pode exigir um tamanho mínimo para manter a funcionalidade. Você pode usar a API UISceneSizeRestrictions para definir o tamanho mínimo preferencial do conteúdo da cena. O melhor momento para especificar o tamanho mínimo é quando a cena está prestes a se conectar. Neste exemplo, defini uma largura mínima preferencial de 500 pontos. Para que o app seja realmente flexível, a interface deve ser capaz de se adaptar. Agora vou falar sobre as APIs que vão ajudar na criação de uma interface adaptável. Uma etapa crucial para tornar sua interface adaptável é garantir que o conteúdo permaneça dentro da área segura. Essa é uma região dentro de uma visualização apropriada para conteúdo interativo ou importante. O conteúdo fora dela fica vulnerável a ser ocultado, por exemplo, por uma barra de navegação ou de ferramentas. Também pode ser ocultado pela interface, como pela barra de status, ou por recursos do dispositivo, como Dynamic Island.

    A barra lateral adiciona uma área segura não simétrica à coluna adjacente em um splitViewController. O fundo pode se estender livremente para fora da área segura, abaixo da barra lateral.

    O conteúdo, como a transcrição da mensagem, é posicionado dentro da área segura para garantir que permaneça visível. Os balões de mensagem são inseridos a partir das bordas da área segura usando margens de layout. Isso permite um espaçamento consistente e uma separação visual clara da barra lateral.

    Cada visualização fornece guias de layout para aplicar margens padrão ao redor do conteúdo. Por padrão, as margens de layout são inseridas a partir da área segura. Neste exemplo, chamo uma guia de layout para posicionar o conteúdo dentro da visualização. Depois uso este guia de layout para configurar restrições.

    No iPadOS 26, as cenas ganham um novo controle para fechar, minimizar e organizar a janela, semelhante ao macOS. O controle de janela aparece ao lado do conteúdo da cena.

    Uma cena pode especificar um estilo de controle de janela preferencial para complementar o conteúdo. Para especificar uma preferência, implemente o método UIWindowSceneDelegate preferredWindowingControlStyle.

    Os componentes, como UINavigationBar, adaptam-se automaticamente organizando as subvisualizações ao redor do controle da janela. Sua interface também deve se adaptar ao controle de janela, seja qual for o estilo.

    Para garantir que a interface não seja encoberta, use um guia de layout que responda pelo controle de janela. Neste exemplo, chamo uma guia de margens de layout com uma adaptação de canto horizontal. Este guia é ótimo para conteúdo semelhante a uma barra na parte superior da cena, que deve ser inserida na borda inferior do controle da janela. Depois uso este guia de layout para configurar restrições. Quando a interface é adaptativa, a orientação da interface deve ser redundante. O redimensionamento da cena, a rotação do dispositivo e as alterações no layout da janela modificam o tamanho da cena. Certas categorias de apps podem se beneficiar do bloqueio temporário da orientação. Por exemplo, um jogo de corrida pode querer fixar a orientação quando o dispositivo for inclinado para controlar o veículo.

    Quando visível, um controlador pode optar por uma orientação fixa de interface. Para especificar uma preferência, substitua prefersInterfaceOrientationLocked na subclasse do controlador. Sempre que essa preferência for alterada, chame setNeedsUpdateOfPrefersInterfaceOrientationLocked.

    Para observar o bloqueio de orientação da interface, implemente o método UIWindowSceneDelegate: didUpdateEffectiveGeometry. Em seguida, compare se o valor de isInterfaceOrientationLocked foi alterado. Para que o app seja realmente adaptável, ele deve responder rapidamente ao redimensionamento. Pode haver elementos da interface que exigem muito processamento para renderizar. Isso é comum em jogos, em que vários ativos podem precisar ser redimensionados quando a cena muda de tamanho. Não é preciso renderizar novamente os ativos para cada tamanho durante o redimensionamento. Neste exemplo, isInteractivelyResizing é consultado para atualizar os ativos para um novo tamanho após a conclusão da interação.

    Apps flexíveis permitem que as pessoas usem seus dispositivos como quiserem. Eles proporcionam ótimas experiências em uma ampla gama de tamanhos, podendo ser usados em qualquer orientação ou layout. UIRequiresFullscreen no Info.plist é um modo de compatibilidade do iOS 9 que impede o redimensionamento de cenas. UIRequiresFullscreen foi descontinuado e será ignorado em versões futuras. Os apps adaptáveis não precisam dessa chave, e você deve removê-la.

    Há outro modo de compatibilidade, especificamente para hardware novo. Antes, quando surgia um novo hardware com tamanho de tela diferente, o sistema dimensionava ou aplicava letterbox na interface do app. Esse dimensionamento permaneceria assim até que você criasse com um SDK mais recente e reenviasse o app. Após compilar e enviar com o SDK iOS 26, o sistema não escalonará nem aplicará letterbox na interface para novos tamanhos de tela.

    Essas são as práticas recomendadas para garantir que seu app seja flexível. E agora, quais são os próximos passos? Adote o ciclo de vida da cena no seu app para garantir um app flexível. Use controladores de visualização de contêiner para gerenciar componentes da sua interface. Por fim, aproveite as APIs como guias de layout para criar uma interface adaptável. Mal posso esperar para ver seus apps ficarem mais flexíveis. Agradeço sua participação.

    • 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 - Introdução
    • Torne os apps do UIKit flexíveis e adaptáveis em diferentes tamanhos de tela e plataformas usando cenas, controles de visualização de contêiner e outras APIs.

    • 0:58 - Cenas
    • As cenas representam instâncias distintas da interface do usuário de um app. Cada cena gerencia seu estado de forma independente e é restaurada perfeitamente após a reconexão. As cenas fornecem contexto sobre a tela do app, como tamanho da tela e geometria da janela. A partir do próximo grande lançamento após o iOS 26, a adoção do ciclo de vida do UIScene será obrigatória.

    • 4:58 - Controladores de visualização de contêiner
    • Controladores de exibição de contêiner, como "UISplitViewController" e "UITabBarController", gerenciam o layout de um ou mais controladores de exibição secundários. Eles ajudam a tornar os apps flexíveis, adaptáveis e personalizáveis. Use a API UISceneRestrictions para expressar o tamanho mínimo para cenas no app.

    • 10:45 - Adaptabilidade
    • As guias de layout e as margens ajudam a posicionar o conteúdo do・€・app de forma consistente dentro da área segura do・€・dispositivo. iPadOS 26 introduz um novo controle de janela. Os apps podem especificar "preferredWindowingControlStyle" e guias de layout para acomodar esses controles. As interfaces do usuário adaptáveis precisam responder rapidamente a mudanças de redimensionamento e orientação, mas alguns apps podem querer substituir "prefersInterfaceOrientationLocked" para bloquear temporariamente a orientação. Para operações computacionalmente caras, marque "isInteractivelyResizing" para executar as operações após a conclusão de uma interação. O "UIRequiresFullscreen" foi descontinuado.

    • 15:39 - Compatibilidade futura
    • Com o SDK do iOS 26, os apps se adaptam automaticamente a novos tamanhos de tela sem a necessidade de atualizações manuais ou reenvio.

    • 16:07 - Próximas etapas
    • Para criar um app flexível, adote o ciclo de vida da cena, use controladores de exibição de contêiner e utilize as APIs como guias de layout.

Developer Footer

  • Vídeos
  • WWDC25
  • Deixe o seu app do UIKit mais flexível
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • App Store
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Fonts
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Open Source
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Sample Code
    • Tutorials
    • Downloads
    • Forums
    • Videos
    Open Menu Close Menu
    • Support Articles
    • Contact Us
    • Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • 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
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Get the Apple Developer app.
    Copyright © 2025 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines