Para manter uma folha do SwiftUI aberta durante as atualizações, preserve a identidade do host de apresentação e mantenha ativa a vinculação de apresentação. Anexe a folha a um contêiner estável para que o conteúdo dos filhos possa mudar sem substituir o apresentador.

Este guia é para um desenvolvedor que está criando um app para Mac com uma folha editável que deve permanecer visível enquanto a interface subjacente muda. O exemplo usa texto local sintético e separa três aspectos que você pode inspecionar de forma independente: o estado da apresentação, o tempo de vida da view apresentadora e o conteúdo do editor.

Por que uma atualização da view pode fazer uma folha desaparecer?

O SwiftUI pode criar novos valores de view ao avaliar body, preservando a identidade da view exibida. O estado persiste durante o tempo de vida dessa identidade. Remover a view ou mudar sua identidade encerra esse tempo de vida; alternar entre ramificações condicionais também pode criar identidades distintas, mesmo quando ambas contêm o mesmo tipo de view. A Apple explica essas relações em Demystify SwiftUI.

Para uma folha controlada por um valor booleano, o controle de apresentação documentado é a vinculação passada a sheet(isPresented:onDismiss:content:): um valor verdadeiro solicita a apresentação. Ao investigar o desaparecimento de uma folha, verifique tanto essa vinculação quanto a permanência da view que contém o modificador. Um valor verdadeiro armazenado em outro lugar não garante que o host de apresentação original tenha sobrevivido. Consulte a documentação da folha controlada por valor booleano da Apple.

Uma causa particularmente direta é a mudança de um identificador explícito. A Apple documenta que mudar o valor fornecido a id(_:) redefine a identidade da view, inclusive seu estado. Inspecione a view apresentadora e suas ancestrais em busca de identificadores derivados de contadores de revisão, texto editável ou valores recém-gerados. Remova esses identificadores quando o comportamento de redefinição não for intencional. Consulte id(_:).

A conclusão prática de projeto é colocar o modificador da folha fora da parte mutável da interface. Se uma view de carregamento, uma linha filtrada ou um layout de detalhes alternativo puder desaparecer, a ancestral persistente mais próxima costuma ser uma proprietária de apresentação melhor.

Crie um proprietário estável para a folha com um exemplo completo para Mac

Crie um app SwiftUI para macOS no Xcode, defina o destino de implantação como macOS 12 ou posterior e substitua as declarações geradas do app e da view de conteúdo pelo arquivo único a seguir. Mantenha apenas uma declaração @main do app. O requisito mínimo do macOS 12 atende à ação de ambiente dismiss usada no exemplo; TextEditor está disponível desde o macOS 11. Consulte a documentação de dismiss e a documentação de TextEditor da Apple.

A view raiz da janela é proprietária da flag de apresentação, do texto editável, do contador de revisão e da flag de layout. O VStack externo contém o modificador da folha. Apenas o conteúdo dentro desse contêiner alterna entre ramificações. A Apple recomenda declarar o estado como privado na view mais alta que dele precisa e passar vinculações aos filhos que precisam alterá-lo. Consulte State.

import SwiftUI

@main
struct StableSheetDemoApp: App {
    var body: some Scene {
        WindowGroup {
            SheetOwnerView()
        }
    }
}

struct SheetOwnerView: View {
    @State private var isEditorPresented = false
    @State private var note = "Sample note Alpha"
    @State private var revision = 0
    @State private var compactLayout = false

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Local Notes")
                .font(.title)

            if compactLayout {
                Text("Compact summary: revision \(revision)")
            } else {
                VStack(alignment: .leading, spacing: 8) {
                    Text("Expanded summary")
                    Text("Revision \(revision)")
                    Text(note)
                }
            }

            Button("Edit Note") {
                isEditorPresented = true
            }
        }
        .padding(24)
        .frame(minWidth: 440, minHeight: 260)
        .sheet(isPresented: $isEditorPresented) {
            NoteEditorSheet(
                note: $note,
                revision: $revision,
                compactLayout: $compactLayout
            )
        }
    }
}

struct NoteEditorSheet: View {
    @Binding var note: String
    @Binding var revision: Int
    @Binding var compactLayout: Bool
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Edit Sample Note")
                .font(.headline)

            TextEditor(text: $note)
                .frame(minHeight: 160)

            Text("Owner revision: \(revision)")

            Button("Update Owner") {
                revision += 1
                compactLayout.toggle()
            }

            HStack {
                Spacer()
                Button("Done") {
                    dismiss()
                }
            }
        }
        .padding(24)
        .frame(width: 460, height: 340)
    }
}

Este é um app de demonstração completo, conferido com as APIs documentadas. Ele não foi compilado nem executado aqui porque este ambiente não tem uma cadeia de ferramentas Swift nem um ambiente de execução do macOS.

Como testar a atualização sem fechar a folha

  1. Execute o app e clique em Edit Note.
  2. Substitua o texto de exemplo por Sample note Beta.
  3. Clique em Update Owner várias vezes. Cada clique incrementa o estado do proprietário e alterna o conteúdo condicional do proprietário.
  4. Confirme que o editor continua visível, que o texto permanece Sample note Beta e que o indicador de revisão avança.
  5. Clique em Done. Confirme que a janela exibe o texto editado no layout expandido quando esse layout está ativo.
  6. Reabra o editor e confirme que o texto editado continua disponível.

O botão de atualização fica dentro da folha, de modo que o teste não depende de clicar na janela subjacente. O AppKit documenta as folhas como modais de documento: enquanto uma folha está visível, a maioria dos eventos direcionados à janela pai é proibida. Atualizar o proprietário por meio de uma vinculação permite que este exemplo exercite diretamente a mudança de estado. Consulte beginSheet(_:completionHandler:).

Decida o que deve acontecer ao fechar o editor

Este exemplo edita o texto do proprietário imediatamente. Done fecha o editor; não executa uma operação de salvamento separada. A string permanece no estado local da janela, e o exemplo não oferece persistência entre execuções do app. Esse comportamento segue o projeto com vinculação compartilhada descrito em State.

Se a tarefa exigir um rascunho que possa ser cancelado, use um valor de rascunho separado e copie-o para o documento apenas após uma ação explícita de confirmação. Defina esse comportamento antes de implementar o fechamento: decidir se um rascunho será confirmado ou descartado é uma questão separada de decidir qual view é proprietária da folha.

Diagnostique o fechamento com três verificações independentes

Siga a sequência abaixo no seu app. Ela transforma as regras gerais de identidade em uma investigação direcionada, sem presumir que toda folha que desaparece tenha a mesma causa.

Verificação O que inspecionar Correção prática
Valor de apresentação Cada gravação que define o valor booleano como false ou limpa o item de apresentação opcional Mantenha as gravações que fecham a apresentação em ações de fechamento intencionais
Identidade do apresentador Ramificações condicionais, linhas removidas, identificadores de ancestrais que mudam Mova tanto o estado de apresentação quanto o modificador para uma ancestral estável e adequada
Tempo de vida do conteúdo Estado local da folha, identificadores de conteúdo que mudam, sincronização do editor Preserve a sessão de edição pretendida e atualize o conteúdo existente deliberadamente

Primeiro, procure no projeto atribuições à propriedade de apresentação e chamadas a dismiss(). Um callback de atualização pode estar fechando a folha explicitamente. Leia dismiss dentro da própria view da folha, como faz o exemplo: a Apple documenta que a ação se aplica ao ambiente em que é obtida. Lê-la na view raiz da janela pode fazer com que ela atinja essa janela. Consulte dismiss.

Segundo, inspecione a hierarquia acima do modificador. Se a folha estiver anexada a uma view de detalhes que uma atualização remove, mova-a para cima desse ponto de substituição. Mova também o estado de apresentação que pertence a essa view. Manter a flag em um filho cujo tempo de vida termina deixa o armazenamento sujeito a substituição, conforme a explicação da Apple sobre o tempo de vida das views.

Terceiro, diferencie uma apresentação fechada de um editor que continua visível, mas perde o rascunho. Comece pelo exemplo simples com TextEditor e depois reintroduza seu conteúdo personalizado em etapas. Esta comparação é um método de diagnóstico: ajuda a determinar se a falha está relacionada à propriedade da apresentação ou à lógica de atualização do conteúdo.

Você deve mudar para uma folha controlada por item?

Use sheet(item:onDismiss:content:) quando uma entidade selecionada específica for a origem da apresentação. A Apple documenta um item opcional que adere a Identifiable, passa um item não nil para a closure de conteúdo e descreve a substituição quando o item muda. Trocar de sobrecarga não preserva, por si só, um apresentador cujo tempo de vida termina. Consulte as folhas controladas por item.

Escolha um identificador que represente a entidade durante toda a sessão de edição pretendida. O protocolo Identifiable do Swift expressa uma identidade estável, e sua documentação deixa explicitamente a duração e o escopo exigidos a cargo dos tipos participantes. Um identificador novo toda vez que uma propriedade é lida não pode representar uma entidade contínua. Consulte Identifiable.

Para um editor simples como o da demonstração, uma flag de apresentação booleana e vinculações de texto separadas facilitam a inspeção da relação pretendida. Para um editor de item selecionado, mantenha a identidade da seleção separada dos campos que mudam com frequência. Trate a seleção de outra entidade como uma decisão explícita de apresentação e defina primeiro o que deve acontecer com o rascunho atual.

Desativar o fechamento interativo manterá a folha ativa?

interactiveDismissDisabled(_:) controla o fechamento não programático. A Apple afirma explicitamente que ele não impede o fechamento causado por uma mudança na vinculação de apresentação nem por uma chamada a dismiss. Portanto, sua função documentada não oferece uma solução para a substituição de identidade. Consulte interactiveDismissDisabled(_:).

Aplique o modificador dentro da hierarquia da folha somente quando sua interface precisar desse comportamento de fechamento e mantenha um caminho explícito para concluir a ação. Para esta tarefa, comece preservando o host e seu estado. Adicionar uma restrição de fechamento antes de identificar a causa pode deixar o problema original de propriedade sem solução.

O que muda quando a folha contém conteúdo do AppKit ou TextKit?

Para um NSTextView incorporado com NSViewRepresentable, separe a criação das atualizações. A Apple define makeNSView(context:) como a etapa de criação e updateNSView(_:context:) como o local para aplicar informações posteriores do SwiftUI. Um coordenador leva as mudanças do delegado ou das ações de destino de volta ao SwiftUI. Consulte NSViewRepresentable e makeNSView(context:).

Uma regra de implementação útil derivada desse ciclo de vida é evitar reconstruir o editor apenas porque o estado não relacionado do proprietário mudou. Antes de substituir o conteúdo, determine se o texto recebido do modelo realmente difere e como lidar com edições locais simultâneas. Uma folha estável, por si só, não resolve gravações concorrentes no texto.

O TextKit fornece armazenamento, layout e renderização de texto. O NSTextView do AppKit reúne esses recursos e expõe mecanismos de layout modernos e legados. Essa estrutura diz respeito ao conteúdo do editor; a propriedade da apresentação ainda precisa de um tempo de vida estável próprio. Use o TextKit quando a tarefa de edição exigir e investigue o comportamento de atualização do texto separadamente do fechamento da folha. Consulte a visão geral do TextKit da Apple.

Quando é apropriado apresentar uma folha pertencente à janela do AppKit?

Se o ciclo de vida das janelas do seu app existente já pertence ao AppKit, o macOS 26 introduz NSWindow.beginSheet(content:completionHandler:) para apresentar conteúdo do SwiftUI diretamente nessa janela. A Apple documenta o suporte à ação de ambiente de fechamento da folha, ao dimensionamento, às barras de ferramentas e ao controle de fechamento interativo. Use um SDK do macOS 26 e respeite o requisito de disponibilidade do macOS 26 antes de adotá-lo. Consulte beginSheet(content:completionHandler:).

O NSWindow.HostingSheetRepresentation retornado pode alterar a view raiz apresentada ou permitir o fechamento programático pelo AppKit. Ele oferece uma opção documentada quando a propriedade no nível da janela se encaixa na arquitetura do app. Ele não é necessário para o exemplo com contêiner estável do SwiftUI acima. Consulte NSWindow.HostingSheetRepresentation.

Em versões anteriores do macOS, o beginSheet(_:completionHandler:) existente do AppKit aceita uma folha NSWindow e documenta o enfileiramento da apresentação. Não presuma que a sobrecarga mais recente, que recebe conteúdo do SwiftUI, esteja disponível nesses sistemas. Consulte beginSheet(_:completionHandler:).

Mantenha o proprietário estável, mantenha ativo o valor de apresentação e preserve a fonte de verdade pretendida para o editor. Verifique essas condições com uma pequena ação de atualização local antes de restaurar conteúdo mais complexo. Assim, você terá uma maneira concreta de continuar editando enquanto a interface é atualizada, dentro dos limites de ciclo de vida e das APIs documentados pela Apple.

Fontes