Cinco plataformas Apple, três arquivos compartilhados: como o Return realmente entrega SwiftUI multiplataforma
O Return, meu cronômetro de meditação, roda em cinco plataformas Apple: iPhone, iPad, Mac, Apple Watch e Apple TV.1 A base de código tem 40 arquivos Swift (excluindo testes). Três deles são compartilhados entre todas as cinco plataformas. O restante é dividido em targets separados do Xcode que duplicam conceitos como TimerManager, AudioManager e ContentView, em vez de compartilhá-los por meio de compilação condicional #if os(...).
A taxa de compartilhamento é de cerca de 7,5%, e isso é intencional.
Este ensaio é sobre como realmente é entregar um app SwiftUI multiplataforma em 2026, por que o compartilhamento agressivo de código é superestimado e o que os três arquivos que foram compartilhados têm em comum.
As cinco plataformas que o Return atinge, como a Apple as apresenta em developer.apple.com. Cada uma é um target de plataforma distinto no Xcode, não uma ramificação em tempo de execução.
TL;DR
- Return: 18 arquivos Swift no target principal (iOS + iPadOS + macOS), 10 arquivos no target tvOS, 7 arquivos no target watchOS, 2 arquivos de widget (Live Activities) e 3 arquivos verdadeiramente multiplataforma em
Return/Shared/. Total de 40. - Os três arquivos compartilhados são os ligados à persistência:
MeditationSession,SessionStore,SessionHistoryView. Estado que viaja via iCloud, não UI que se adapta à plataforma. - tvOS e watchOS são targets separados do Xcode, não ramificações
#if os(tvOS)no target principal. Os modelos de controle são diferentes demais para caber em um único ContentView. - Mesmo dentro do target principal iOS/iPadOS/macOS, blocos
#if osproliferam: 10 emContentView.swift, 8 emLiveActivityManager.swift, 8 emVideoBackgroundView.swift, 6 emAudioManager.swift. - A leitura honesta: compartilhamento agressivo entre cinco plataformas Apple é um passivo de manutenção. Um pequeno núcleo compartilhado (a camada de persistência) somado a UIs específicas de cada plataforma entrega mais rápido e quebra menos do que um arquivo gigante recheado de
#if.
Para os companheiros específicos de plataforma, leia a matriz de plataformas Apple, o contrato de tempo de execução do watchOS e os padrões de Liquid Glass no SwiftUI.
Os números
A forma da base de código, por contagem de arquivos Swift, depois de remover testes e UI tests:
Return/ 18 files (iPhone + iPad + Mac, single target)
├── Shared/ 3 files ← cross-platform truth
│ ├── MeditationSession.swift
│ ├── SessionStore.swift
│ └── SessionHistoryView.swift
├── ContentView.swift (10 #if os branches)
├── TimerManager.swift (2 #if os branches)
├── AudioManager.swift (6 #if os branches)
├── HealthKitManager.swift
├── LiveActivityManager.swift (8 #if os branches, iOS-only)
├── ThemeManager.swift
├── VideoBackgroundView.swift (8 #if os branches)
├── GlassTextShape.swift (Liquid Glass, see prior post)
├── GlassTimerText.swift
└── … (settings, theme, audio assets, etc.)
ReturnTV/ 10 files (tvOS, separate target)
├── TVContentView.swift
├── TVTimerManager.swift ← duplicates main TimerManager
├── TVAudioManager.swift ← duplicates main AudioManager
├── TVDurationPicker.swift
├── TVFocusModifier.swift ← tvOS button styles for focus
├── TVSettingsView.swift
└── …
ReturnWatch Watch App/ 7 files (watchOS, separate target)
├── WatchContentView.swift
├── WatchTimerManager.swift ← duplicates main TimerManager
├── WatchAudioManager.swift ← duplicates main AudioManager
├── WatchHealthKitManager.swift ← duplicates main HealthKitManager (mostly)
├── WatchSettingsView.swift
└── …
ReturnWidgets/ 2 files (Live Activity + bundle)
├── ReturnLiveActivity.swift
└── ReturnWidgetsBundle.swift
Cinco plataformas, três arquivos compartilhados, dois targets separados por plataforma mais um target de widget, somados a uma compilação condicional pesada dentro do target principal. A razão de compartilhamento é de cerca de 7,5%. A maioria dos tutoriais de “SwiftUI multiplataforma” sugere o oposto: escrever um único ContentView que se adapta a cada plataforma via @Environment(\.horizontalSizeClass) e #if os(...).2 Isso funciona para duas plataformas (iPhone + iPad). E quebra com cinco.
O que os três arquivos compartilhados têm em comum
Return/Shared/MeditationSession.swift define o tipo de valor adjacente ao SwiftData:3
struct MeditationSession: Codable, Identifiable, Equatable {
let id: UUID
let startDate: Date
let endDate: Date
let durationSeconds: Int
let sourceDevice: DeviceType
var syncedToHealthKit: Bool
enum DeviceType: String, Codable, CaseIterable {
case iPhone, iPad, mac, appleTV, appleWatch
}
}
O comentário no cabeçalho do arquivo é decisivo: // Add this file to: Return, ReturnTV, ReturnWatch Watch App targets. O mesmo arquivo de origem é referenciado pelos três targets do Xcode, sem symlinks, sem ser embutido em um Swift package. O sistema de build da Apple compila com prazer um único arquivo em três binários.
SessionStore.swift é a camada de persistência: um wrapper fino sobre o NSUbiquitousKeyValueStore (o iCloud Key-Value Store da Apple) que lê e escreve arrays de MeditationSession. A escolha importa: a sincronização via KV-store dá ao Return um histórico de sessões entre dispositivos sem precisar provisionar um container CloudKit, com a contrapartida de que o store inteiro está limitado a 1 MB no total.12 Para uma lista de sessões de meditação, com algumas centenas de bytes em média cada, o teto sobra. SessionHistoryView.swift é uma lista SwiftUI que renderiza as sessões. Os dois são usados de forma idêntica pelos targets de iPhone, iPad, Mac, Watch e TV.
O que esses três arquivos têm em comum: eles descrevem estado, não interação. Uma MeditationSession é o mesmo conceito em todos os dispositivos. A lista de sessões anteriores se lê do mesmo jeito em todos os dispositivos. Nenhum deles envolve uma superfície de controle, um gerenciador de janelas, uma decisão de roteamento de áudio, um focus engine ou uma digital crown. No momento em que um arquivo precisa saber em qual plataforma está rodando, ele deixa de ser compartilhável.
Por que o resto não foi compartilhado
Pegue o TimerManager. A versão de iOS/iPadOS/macOS usa Timer.publish(every: 1, ...) e roteia notificações por UserNotifications. A versão tvOS (TVTimerManager) lida com o caso em que o usuário pausou pelo Siri Remote e a tela de descanso entra em ação. A versão watchOS (WatchTimerManager) delega a um WKExtendedRuntimeSession (via WatchSessionManager) para que o sistema operacional mantenha o app responsivo enquanto a tela escurece, e roteia a entrada pela digital crown em vez do toque. Três plataformas, três comportamentos de cronômetro profundamente diferentes.
Você poderia unificá-los como class TimerManager { #if os(watchOS) ... #elif os(tvOS) ... }. O resultado seria uma classe com três modos, cada um com quarenta linhas de código sob #if, em que mexer no caminho do iOS arrisca quebrar o caminho do watchOS. Isso é um pesadelo de manutenção.
Três classes separadas com três nomes de arquivo são mais código em disco e menos código na sua cabeça. Duplicação que você consegue ler vence abstração que você não consegue.
A mesma lógica vale para:
ContentViewversusTVContentViewversusWatchContentView: os modelos de navegação são diferentes (baseado em push no iPhone, baseado em foco na TV, baseado em lista no Watch).AudioManagerversusTVAudioManagerversusWatchAudioManager: as categorias de sessão de áudio variam, o watchOS tem regras mais rígidas para áudio em background, o tvOS roteia diferente para o AirPlay.VideoBackgroundViewtem 8 ramificações#if os(iOS)no target principal (com uma companheira#elseif os(macOS)), cobrindo diferentes assets de vídeo (fire_phone.mp4versusfire_mac.mp4), diferentes tipos de layer e diferentes proporções.4
Vale registrar: o target principal Return/ de fato agrupa iOS, iPadOS e macOS. Essas três plataformas compartilham mais código do que não compartilham. O NavigationStack do SwiftUI funciona nas três. O .glassEffect() funciona nas três. As diferenças de gerenciamento de janelas são reais, mas tratáveis dentro de um único target. Foi com tvOS e watchOS que tracei a linha do target separado.
O caso do tvOS: por que o focus engine forçou um target separado
A navegação no Apple TV é construída em torno do focus engine.5 Cada elemento de UI com o qual o usuário pode interagir se declara focável; o sistema move o foco entre os elementos pelas setas do Siri Remote; pressionar select ativa o elemento focado. O SwiftUI no tvOS expõe isso por meio de .focusable(), .focusEffect e tipos ButtonStyle personalizados que respondem a @Environment(\.isFocused) para o efeito de inclinação por parallax que os apps nativos da Apple usam. Código real de produção em TVFocusModifier.swift:6
struct TVCapsuleButtonStyle: ButtonStyle {
var accentColor: Color = .white
@Environment(\.isFocused) private var isFocused
func makeBody(configuration: Configuration) -> some View {
configuration.label
.colorMultiply(isFocused ? focusedTextColor : accentColor)
.background(
Capsule().fill(isFocused
? AnyShapeStyle(accentColor)
: AnyShapeStyle(.ultraThinMaterial))
)
.clipShape(Capsule())
.scaleEffect(isFocused ? 1.1 : 1.0)
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.shadow(color: .black.opacity(isFocused ? 0.3 : 0.1),
radius: isFocused ? 20 : 5, y: isFocused ? 10 : 2)
.animation(.easeInOut(duration: 0.2), value: isFocused)
}
}
O mesmo arquivo também define TVCircleButtonStyle para controles quadrados/circulares. Os dois estilos invertem cor e translucidez no foco: botões não focados ficam sobre .ultraThinMaterial, botões focados se preenchem com a cor de destaque e ganham um aumento de escala + sombra. O padrão é estruturalmente específico do tvOS para este app. @Environment(\.isFocused) está disponível em iOS, iPadOS, macOS, watchOS e tvOS,13 mas a navegação dirigida por foco é o modelo de interação primário apenas no tvOS, onde o Siri Remote não produz nenhum evento de ponteiro ou de toque. No iPhone ou no iPad, o controle equivalente é detectado por toque; no Mac, ele é hover ou clique. Os estilos de botão em TVFocusModifier.swift partem do princípio de que o foco é a affordance principal do usuário e desenham toda a resposta visual em torno disso. Não há um bom jeito de escrever um ContentView que lide com toque no iOS, hover no Mac e navegação dirigida por foco no tvOS em um lugar só. A estrutura da view é genuinamente diferente: um ContentView de tvOS é um grafo de linhas focáveis, um ContentView de iOS é uma pilha de toque para agir.
O mesmo vale para o seletor de duração. No iPhone, ele desliza de baixo para cima e aceita toques. No Apple TV, é uma fileira horizontal de células focáveis pelas quais o usuário navega com o controle remoto. TVDurationPicker.swift é um arquivo próprio porque o desenho baseado em foco em células não tem análogo no iPhone. Forçá-los a um único arquivo significaria colar duas UIs sem relação por meio de #if os(tvOS).
O caso do watchOS: sessões de execução estendida, HealthKit e uma superfície menor
O watchOS adiciona dois condicionantes estruturais que as outras plataformas não têm:
WKExtendedRuntimeSessionpara manter o app responsivo enquanto a tela do relógio está esmaecida.8 Sem isso, o watchOS suspende o app de forma agressiva entre cada tique de segundo e o cronômetro deriva. O Return declaraWKBackgroundModes: mindfulnessnoInfo.plistdo target watchOS para que o sistema operacional reconheça o caso de uso e libere o orçamento de tempo de execução; a própria sessão de tempo de execução é criada com o inicializador padrãoWKExtendedRuntimeSession().- Sincronização via iCloud com
NSUbiquitousKeyValueStore, não com WatchConnectivity. A sincronização do histórico de sessões do Return roda no mesmo key-value store usado pelos targets de iPhone, iPad e Mac, então uma meditação registrada no relógio aparece na visão de histórico do iPhone sem qualquer mensagem direta entre relógio e telefone. WatchConnectivity poderia ser uma opção futura para sincronização de estado em tempo real, mas o Return optou pelo modelo mais simples: cada dispositivo escreve no mesmo KV-store do iCloud, a próxima leitura em qualquer dispositivo vê a união.
WatchTimerManager.swift é o cronômetro do lado do relógio; ele delega o trabalho de tempo de execução estendido para WatchSessionManager, definido em ReturnWatchApp.swift como final class WatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate. O TimerManager do iOS não tem análogo porque apps iOS permanecem responsivos em primeiro plano sem uma sessão de tempo de execução explícita. Colocar a lógica do relógio no TimerManager do iOS via #if os(watchOS) significaria que o caminho de código do iOS importaria símbolos do WatchKit que ele jamais usa, somado ao fato de que o caminho do watchOS precisa de caminhos de inicialização que o iOS não tem.
WatchHealthKitManager.swift é uma variante reduzida do HealthKitManager principal. Ele registra os minutos atentos do mesmo jeito, mas a UX do prompt de autorização é diferente (o relógio não consegue exibir um HealthKitPermissionSheet). A classe do Watch tem aproximadamente metade do tamanho da principal.
O que acontece dentro do target principal iOS/iPadOS/macOS
Mesmo dentro do target principal, o compartilhamento não é automático. ContentView.swift tem dez blocos #if os(macOS) ou #if !os(macOS); LiveActivityManager.swift tem oito; VideoBackgroundView.swift tem oito; AudioManager.swift tem seis. Live Activities é um recurso exclusivo do iPhone, então o LiveActivityManager inteiro fica encapsulado em #if os(iOS). O seletor de duração no iPhone usa um layout diferente do seletor de duração no iPad e Mac, então o ContentView tem ramificações paralelas de layout.
O padrão que tem funcionado: #if os(...) para pequenos deltas de plataforma (comportamento de teclado diferente, padding diferente, API ausente), target separado para grandes deltas estruturais (foco versus toque, sessão de treino versus cronômetro). O limiar com o qual acabei trabalhando é “mais de ~10 linhas de ramificação”. Abaixo disso, compilação condicional dá conta. Acima disso, o arquivo está fazendo dois trabalhos ao mesmo tempo e o segundo trabalho pertence a outro target.
Quando não entregar nas cinco plataformas
A avaliação honesta.
Pule o Apple Watch se o seu app é denso em informação. A tela de 46mm não tem espaço para uma lista de 30 itens, um seletor de duração e uma página de configurações. O Return sobrevive no watchOS porque a interação central é um botão (iniciar/parar um cronômetro). Um app de produtividade, um app financeiro ou um app rico em mídia não vai sobreviver.
Pule o Apple TV se o seu app é interativo. A TV é para experiências ambientes (cronômetro rodando numa tela do outro lado do cômodo, reprodução de música). Qualquer coisa que exija entrada frequente do usuário está brigando com a plataforma. O Return está no tvOS porque “definir um cronômetro de 20 minutos e olhar fogo na tela” é exatamente o caso ambiente certo. Um app de anotações seria miserável.
Pule o Mac se o seu app é uma interface phone-first. O SwiftUI no Mac funciona, mas o modelo push do NavigationStack se lê como brinquedo comparado a uma sidebar Mac de verdade. Se o app for parecer subdesenvolvido no Mac, lance via Catalyst (que converte o app de iPad) ou pule o Mac inteiramente até conseguir construir uma UI nativa para Mac.
Pule o iPad se você não fez adaptação de size class. Um app de iPhone esticado para preencher um iPad se lê como barato. O iPad precisa, no mínimo, de um NavigationSplitView com sidebar; idealmente um layout real de dois painéis. O Return usa split views no iPad e stacks no iPhone. O código está no mesmo target, mas a UI é genuinamente diferente.
A regra que tracei: entrega numa plataforma quando a interação central do app mapeia para o modelo de entrada daquela plataforma. Entregue um cronômetro de meditação no Apple Watch (um toque para iniciar). Entregue um cronômetro de meditação no Apple TV (definir e esquecer). Não entregue um quadro kanban em nenhum dos dois.
O que viaja sem esforço
As três coisas que de fato foram compartilhadas entre as cinco plataformas no Return:
- O modelo de dados (
MeditationSession). A struct é idêntica em todas as plataformas, é sincronizada viaNSUbiquitousKeyValueStore, e qualquer plataforma consegue ler o que qualquer outra escreveu. - A view de histórico de sessões (
SessionHistoryView). UmaListde sessões anteriores renderiza de forma idêntica no iPhone, iPad, Mac, Apple Watch e Apple TV. AListdo SwiftUI é um dos poucos primitivos que se adaptam de forma limpa entre os cinco fatores de forma. - O wrapper de persistência (
SessionStore). Leituras e escritas são agnósticas de plataforma; o armazenamento subjacente (NSUbiquitousKeyValueStore) é a mesma API em todo lugar.
Três conceitos. Estado, renderização de lista e persistência. Qualquer coisa com estado e apresentação que não envolva um modelo de entrada específico de hardware é compartilhável. Qualquer coisa que toca em entrada, foco, roteamento de áudio, tamanho de tela ou execução em background não é.
Esse padrão aparece no guia de Desenvolvimento de iOS com Agentes, em que defendi a mesma coisa com palavras diferentes: as partes de um app iOS que um agente consegue escrever compartilham a maior parte do código com as partes que um humano escreve; as partes que exigem julgamento humano (assinatura, polimento visual, performance) são exatamente as partes que também não compartilham bem entre plataformas.9 Os dois limites se alinham. Os dois falam de onde o conhecimento de domínio começa a importar.
O custo do multiplataforma
O ROI é assimétrico. Adicionar iPad a um app de iPhone custa talvez 20% mais código (ramificações de size class, split view em alguns lugares). Adicionar Mac ao mesmo target soma outros 15-20% (ramificações #if os(macOS), barra de menu, gerenciamento de janelas). Cada target principal adiciona algo em torno de 10 arquivos para um app pequeno.
Apple Watch e Apple TV são os caros. Adicionar watchOS ao Return exigiu 11 novos arquivos em um target separado, incluindo gerenciadores dedicados de áudio, cronômetro e HealthKit. Adicionar tvOS exigiu 10 novos arquivos em outro target separado, incluindo gestão de foco e um seletor de duração customizado. Juntos eles quase dobraram a superfície Swift para o que é, no nível de funcionalidade do usuário, o mesmo app.
A escolha de entregar nas cinco não foi “queremos ser multiplataforma por princípio”. Foi uma série de decisões separadas: Apple Watch porque cronômetros de meditação genuinamente pertencem ao pulso, Apple TV porque o formato de tela ambiente combina com sessões longas em um cômodo, Mac porque alguns usuários meditam na escrivaninha entre reuniões. Cada plataforma conquistou seu target por ter um caso de uso real.
Se um recurso não conquista seu target, o movimento mais barato é pular a plataforma e dobrar a aposta nas plataformas em que o app é excelente.
O que isso significa para o seu app
Três conclusões.
- Adote como padrão um target por grande grupo de plataformas. iOS + iPadOS + macOS em um target funciona porque a interação central (toque + cursor) é parecida. tvOS num target separado. watchOS num target separado. Cada target separado custa ~10 arquivos, mas livra você de uma classe-deus com ramificações
#ifque crescem sem limite. - Compartilhe estado de forma agressiva, não interação. Structs de modelo Codable, wrappers de persistência e renderizações de
Listviajam quase de graça. Gerenciadores de cronômetro, gerenciadores de áudio e content views não viajam. - Conquiste cada plataforma. Não entregue no watchOS porque dá. Entregue quando a interação central do seu app mapear para o modelo de entrada da plataforma. Pule o resto.
Esse padrão funciona junto com as outras três superfícies sobre as quais escrevi para a mesma família de apps: App Intents tipados para a Apple Intelligence, servidores MCP para agentes em diferentes LLM, Liquid Glass para o humano diante do dispositivo. A camada mais externa do mesmo stack é a plataforma: em quais telas o app de fato roda. Escolha isso com tanto cuidado quanto você escolhe a superfície de IA.
FAQ
Por que não usar um Swift package para o código compartilhado?
Eu considerei. Para três arquivos, um Swift package adiciona mais cerimônia do que poupa. O sistema de build do Xcode 26 da Apple compila com prazer um único arquivo de origem em múltiplos targets quando você marca as caixas de Target Membership. Um package adiciona um Package.swift separado, um target de testes separado e um passo de indireção que cada refatoração precisa atravessar. Para um pequeno núcleo compartilhado, a resposta mais simples vence.10
O SwiftData funciona no watchOS e no tvOS?
O SwiftData está disponível em iOS 17+, macOS 14+, watchOS 10+ e tvOS 17+, cobrindo todas as plataformas que o Return atinge.11 A struct MeditationSession é simplesmente Codable, não @Model, porque o Return usa NSUbiquitousKeyValueStore para sincronizar o histórico de sessões em vez de um container SwiftData. O padrão funciona da mesma forma para tipos @Model: o arquivo do modelo é compartilhado, o container de persistência difere por plataforma se precisar.
Devo usar Mac Catalyst ou um target Mac nativo?
O Catalyst é a ferramenta certa quando o app de iPad é bom o suficiente para que uma versão Mac reconstruída via Catalyst se leia como nativa. O target principal do Return é um target multiplataforma de verdade (não Catalyst), construído com SwiftUI para iOS, iPadOS e macOS em um único binário. A UI do Mac usa #if os(macOS) para renderizar de forma diferente do iPad: sidebar em vez de sheet, key-equivalents nos botões, e por aí vai. O Catalyst teria sido mais simples, mas a UI do Mac teria parecido um app de iPad rodando num Mac, que é justamente o modo de falha pelo qual o Catalyst é mais conhecido.
Vale a pena entregar no Apple TV para um app pequeno?
Provavelmente não. Apps de Apple TV têm casos de uso muito específicos (ambiente, mídia, jogo casual). Se o seu app não se encaixa em um deles, o público da plataforma é pequeno demais para justificar os 10 arquivos Swift por app. O Return atinge o tvOS justamente porque sessões longas de meditação numa tela do outro lado do cômodo é um dos poucos casos de uso adjacentes à produtividade que se encaixa na plataforma.
Quanto trabalho dá para entregar nas cinco plataformas?
Difícil dar um número exato; depende do app. O Return foi entregue multiplataforma desde o primeiro dia, em vez de adicionar plataformas de forma incremental, o que é mais rápido do que retroadaptar. Como regra de bolso aproximada: um MVP só de iPhone somado a suporte de iPad somado a suporte de Mac é mais ou menos 1,5x o tempo de só-iPhone. Adicionar Apple Watch é mais 0,5x. Adicionar Apple TV é mais 0,5x. Então um primeiro release de cinco plataformas é por volta de 2,5x o esforço de só-iPhone, com a ressalva de que esta foi uma construção assistida por agente, em que a maior parte do código duplicado foi editada em massa por Claude Code em vez de digitada manualmente.
Referências
-
O Return do autor, um app de cronômetro de meditação publicado na App Store em 21 de abril de 2026. Targets nativos: iOS 26+, iPadOS 26+, macOS 26+, watchOS 26+, tvOS 26+. SwiftUI em todo o app.
NSUbiquitousKeyValueStorepara histórico de sessões entre dispositivos. ↩ -
Apple Developer, “Configuring a Multi-Platform App” e a sessão “SwiftUI essentials” na WWDC 2024. A orientação padrão da Apple tende a um único target com adaptação dirigida pelo ambiente; o caminho de múltiplos targets adotado neste artigo é uma ruptura deliberada. ↩
-
Código de produção em
Return/Return/Shared/MeditationSession.swift,SessionStore.swift,SessionHistoryView.swift. O comentário no cabeçalho deMeditationSession.swiftdiz: “Add this file to: Return, ReturnTV, ReturnWatch Watch App targets.” ↩ -
Código de produção em
Return/Return/VideoBackgroundView.swift(8 ramificações#if os(iOS)mais uma ramificação#elseif os(macOS)),Return/Return/ContentView.swift(10 ramificações#if os),Return/Return/AudioManager.swift(6 ramificações#if os),Return/Return/LiveActivityManager.swift(8 ramificações#if os, arquivo exclusivo de iOS). Contagens de ramificações obtidas rodandogrep -Ec '^\s*#if os\\(' <file>. ↩ -
Apple Developer, “Focus interactions” Human Interface Guidelines. O focus engine do tvOS é um modelo de navegação fundamentalmente diferente do toque no iOS ou do ponteiro no Mac. ↩
-
Código de produção em
Return/ReturnTV/TVFocusModifier.swift. Define dois tiposButtonStyle(TVCapsuleButtonStyleeTVCircleButtonStyle) que envolvem@Environment(\.isFocused)para inverter cor e translucidez no foco e aplicar escala + sombra. ↩ -
Apple Developer, “WatchConnectivity”. O framework para comunicação pareada entre iPhone e Watch; o Return não o utiliza para sincronização de sessões, apoiando-se no iCloud key-value store. ↩
-
Apple Developer, “WKExtendedRuntimeSession” e a chave do Info.plist “WKBackgroundModes”. O valor
mindfulnessestá documentado como: “Enables extended runtime sessions for silent meditation” — o ajuste certo para um cronômetro de meditação. O Return cria umaWKExtendedRuntimeSession()padrão e declaraWKBackgroundModes: mindfulnessnoInfo.plistdo target watchOS. Código de produção:Return/ReturnWatch Watch App/ReturnWatchApp.swiftdefineWatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate;WatchTimerManager.swiftdelega o trabalho de tempo de execução estendido para ele. ↩ -
Análise do autor em Construindo apps iOS com agentes de IA, o guia do praticante para desenvolvimento iOS assistido por agentes em 8 apps em produção. ↩
-
Apple Developer, “Configuring a Multi-Platform App”. A Target Membership permite que um arquivo de origem compile em múltiplos targets sem um Swift package. Ferramenta certa para pequenos núcleos compartilhados. ↩
-
Apple Developer, “SwiftData” platform availability. Disponível em iOS 17+, iPadOS 17+, macOS 14+, watchOS 10+, tvOS 17+, visionOS 1+, cobrindo todas as cinco famílias de plataforma da Apple. ↩
-
Apple Developer, “NSUbiquitousKeyValueStore”. O iCloud Key-Value Store da Apple para sincronizar pequenas quantidades de estado entre os dispositivos do usuário. Tamanho total do store limitado a 1 MB no conjunto de todas as chaves, segundo os limites publicados pela Apple. Código de produção:
Return/Return/Shared/SessionStore.swift. ↩ -
Apple Developer,
EnvironmentValues.isFocused. Disponível em iOS 14+, iPadOS 14+, macOS 11+, tvOS 14+, watchOS 7+. A API é multiplataforma; o que difere é se o foco é a affordance principal de navegação do usuário. ↩