Para arrastar e soltar dentro do seu app para Mac, a origem declara as operações compatíveis, o destino escolhe uma e seu modelo implementa o resultado. No SwiftUI no macOS 26 ou posterior, configure as duas extremidades e remova o original somente depois que uma movimentação for concluída.
Este artigo apresenta uma pequena interação para desktop: arraste um círculo sintético para Copy here ou Move here. Tudo permanece na memória. O exemplo deixa explícitos a operação, a identidade do item e a limpeza da origem e, em seguida, associa as mesmas responsabilidades ao AppKit.
Com o que a origem e o destino precisam concordar?
Uma carga útil transferível descreve o que viaja entre as visualizações. Copiar ou mover descreve o que acontece com o original. São decisões distintas. A documentação do SwiftUI da Apple diz que o arraste oferece suporte à cópia por padrão; habilitar a movimentação adiciona outra operação compatível. Em seguida, o destino escolhe como receber o item. DragConfiguration
Use três responsabilidades para projetar a interação:
| Responsabilidade | Cópia | Movimentação |
|---|---|---|
| Capacidade da origem | Oferecer cópia | Oferecer movimentação |
| Confirmação do destino | Inserir um item independente | Receber o item existente |
| Conclusão na origem | Manter o original | Remover o original após a movimentação concluída |
Neste exemplo do círculo, uma cópia independente recebe um novo identificador. Uma movimentação preserva o identificador e altera a localização. Essa é uma escolha de projeto do app, compatível com o requisito do Swift de que uma entidade identificável tenha identidade estável; o Swift deixa o escopo e a duração dessa identidade a cargo do app. Identifiable
A consequência prática é que retornar uma operação de movimentação não realoca um elemento nos seus arrays. Você ainda precisa inserir o item no destino e removê-lo da origem. A Apple demonstra especificamente como observar a operação concluída de arraste para informar ao modelo quando deve remover um item da origem. onDragSessionUpdated(_:)
Quais APIs do SwiftUI um app para Mac deve usar?
Para esta implementação, use um destino de implantação no macOS 26 ou posterior e um SDK que contenha os modificadores de configuração documentados. Adicione draggable(_:) à origem, dragConfiguration(_:) para declarar suas operações e dropConfiguration(_:) para selecionar a operação do destino. Ambos os modificadores de configuração estão documentados como disponíveis no macOS 26. dragConfiguration(_:), dropConfiguration(_:)
A sobrecarga atual de dropDestination(for:isEnabled:action:) fornece um DropSession à sua ação. A ação retorna Void. Isso difere da sobrecarga mais antiga, cuja ação recebe uma localização e retorna um valor Boolean. Especifique isEnabled: explicitamente no exemplo para deixar clara a sobrecarga pretendida. dropDestination(for:isEnabled:action:)
No destino, inspecione suggestedOperations antes de selecionar uma operação. Para um destino genérico que prioriza a movimentação, escolha movimentação quando oferecida; caso contrário, escolha cópia quando oferecida; se nenhuma estiver disponível, escolha proibido. O exemplo documentado pela Apple usa essa alternativa de movimentação para cópia e observa que a closure de configuração é executada com frequência, portanto mantenha-a simples. dropConfiguration(_:)
Nossos dois destinos têm significados fixos de propósito. Copy here aceita cópia; Move here aceita movimentação. Um destino retorna proibido quando a operação indicada no nome não está disponível. Isso torna o resultado previsível sem exigir um modificador de teclado.
Crie um exemplo completo de cópia e movimentação na memória
Crie um app SwiftUI para macOS, defina o destino de implantação como macOS 26 ou posterior e substitua as declarações geradas do app e da visualização de conteúdo pelo arquivo único a seguir. O exemplo transfere uma string incorporada que contém o identificador de um item, portanto não precisa de um tipo transferível personalizado nem de uma declaração de tipo personalizada. A Apple documenta tanto cargas úteis transferíveis para origens de arraste quanto cargas úteis de string para destinos de soltura. draggable(_:), dropDestination(for:isEnabled:action:)
Este é um app mínimo completo, destinado a uma janela aberta e a um único item de origem. Conferi as referências da API com a documentação consultada; não foi possível compilá-lo nem executá-lo aqui porque o Swift e o SDK do macOS não estão disponíveis.
import SwiftUI
import Foundation
@main
struct CopyMoveDemoApp: App {
var body: some Scene {
WindowGroup {
CopyMoveDemo()
}
}
}
struct CopyMoveDemo: View {
@State private var sourceID = UUID()
@State private var sourcePresent = true
@State private var copiedIDs: [UUID] = []
@State private var movedIDs: [UUID] = []
@State private var moveReceived = false
private var sourceConfiguration: DragConfiguration {
DragConfiguration(
operationsWithinApp: .init(allowMove: true),
operationsOutsideApp: .init(
allowCopy: false,
allowMove: false,
allowDelete: false
)
)
}
var body: some View {
VStack(spacing: 24) {
Text("Drag the circle to a target")
.font(.headline)
if sourcePresent {
Circle()
.fill(.blue)
.frame(width: 90, height: 90)
.overlay(Text("Circle").foregroundStyle(.white))
.draggable(sourceID.uuidString)
.dragConfiguration(sourceConfiguration)
.onDragSessionUpdated { session in
if session.phase == .initial {
moveReceived = false
}
if session.phase == .ended(.move), moveReceived {
sourcePresent = false
}
if case .ended = session.phase {
moveReceived = false
}
}
} else {
Text("Original moved")
.frame(width: 90, height: 90)
}
HStack(alignment: .top, spacing: 20) {
target(moving: false)
target(moving: true)
}
}
.padding(24)
.frame(minWidth: 520, minHeight: 320)
}
private func target(moving: Bool) -> some View {
let operation: DropOperation = moving ? .move : .copy
let ids = moving ? movedIDs : copiedIDs
return VStack(spacing: 10) {
Text(moving ? "Move here" : "Copy here")
.font(.headline)
ForEach(ids, id: \.self) { id in
Text("Circle \(id.uuidString.prefix(4))")
}
Spacer(minLength: 20)
}
.frame(width: 210, height: 150)
.background(.blue.opacity(0.08))
.contentShape(Rectangle())
.dropDestination(for: String.self, isEnabled: true) {
tokens, session in
guard session.localSession != nil,
sourcePresent,
tokens == [sourceID.uuidString] else {
return
}
if moving {
guard !movedIDs.contains(sourceID) else { return }
movedIDs.append(sourceID)
moveReceived = true
} else {
copiedIDs.append(UUID())
}
}
.dropConfiguration { session in
guard sourcePresent,
session.localSession != nil,
session.itemsCount == 1,
session.suggestedOperations.contains(operation) else {
return DropConfiguration(operation: .forbidden)
}
return DropConfiguration(operation: operation)
}
}
}
A configuração da origem oferece cópia e movimentação dentro do app. Ela desativa as operações fora do app usando o inicializador do macOS para operações externas ao app. O destino também verifica localSession, que, segundo a documentação da Apple, não está presente quando o arraste tem origem em outro lugar. Essas configurações mantêm o exemplo focado no item em memória da própria janela. DragConfiguration.OperationsWithinApp, init(allowCopy:allowMove:allowDelete:), DropSession
A ação de soltura valida o token exato antes de inserir qualquer coisa. A cópia adiciona um novo identificador. A movimentação adiciona o identificador original e registra que esse destino o recebeu. Em seguida, a origem remove o original exibido somente quando a sessão termina com uma movimentação e há registro desse recebimento. Esse registro é uma proteção no nível do app para esta demonstração limitada, não um recurso de transação do SwiftUI.
A declaração atual das fases de arraste da Apple inclui initial, active, ending, ended e dataTransferCompleted. O exemplo usa o caso documentado ended(DropOperation) para o resultado da operação. Ele não remove a origem enquanto o item está sobre o destino nem durante a fase anterior de encerramento. DragSession.Phase
Deixe explícitos os limites de propriedade e dos gestos
A string é um token de consulta, não o modelo completo do círculo. O destino consulta o estado da origem pertencente a esta visualização. Para vários documentos ou janelas, substitua essa consulta local por um proprietário explícito do modelo e por um registro associado à sessão de arraste e aos itens reais. Um arraste iniciado dentro do app, por si só, não estabelece qual documento é proprietário de um item.
Para vários itens arrastáveis, use identificadores estáveis do modelo e obtenha da sessão os identificadores dos itens arrastados, quando disponíveis. A Apple fornece draggedItemIDs(for:) para cargas úteis identificáveis ou identificadores fornecidos separadamente. Evite remover o que estiver selecionado no momento em que a conclusão chegar; a seleção e a identidade do item arrastado são dados distintos. DragSession
Deixe que o modificador draggable(_:) do SwiftUI instale a interação de arraste. Não faça a construção da carga útil remover itens do modelo. A Apple documenta que o modificador adiciona os gestos apropriados e alerta contra o envio de uma closure de exportação para o ator principal. draggable(_:)
Como o mesmo acordo funciona no AppKit?
Use o AppKit quando o destino de implantação ou a arquitetura de visualizações existente exigir seu ciclo de vida explícito de arraste. O protocolo da origem declara as operações permitidas por meio de draggingSession(_:sourceOperationMaskFor:). O contexto permite distinguir destinos dentro e fora do app. Para a interação acima, ofereça cópia e movimentação dentro do app e nenhuma operação fora dele. draggingSession(_:sourceOperationMaskFor:)
O destino se registra para o tipo de área de transferência pertinente, escolhe uma operação em draggingEntered(_:) e atualiza essa escolha conforme necessário em draggingUpdated(_:). A Apple exige que o destino considere a sobreposição entre as operações da origem e as operações e os tipos de dados compatíveis com ele. Retorne uma operação ou nenhuma quando não houver sobreposição aceitável. NSDraggingDestination, draggingEntered(_:)
Faça a inserção em performDragOperation(_:), depois que a preparação for concluída com sucesso. Retorne true somente quando o destino aceitar os dados. A origem recebe a operação concluída por meio de draggingSession(_:endedAt:operation:); use esse resultado, junto com as regras de propriedade do modelo, para concluir a remoção do lado da origem em uma movimentação. performDragOperation(_:), draggingSession(_:endedAt:operation:)
Se incorporar uma área de arraste do AppKit em uma interface SwiftUI, deixe essa área controlar a interação do AppKit. beginDraggingSession(with:event:source:) recebe o evento de pressionamento do mouse que inicia o arraste, e os quadros dos itens arrastados usam o sistema de coordenadas da visualização. Preserve essa relação entre evento e coordenadas em vez de fabricar um evento a partir de um valor posterior de gesto do SwiftUI. beginDraggingSession(with:event:source:)
O código mais antigo de destino do SwiftUI pode retornar DropProposal(operation: .move) de DropDelegate.dropUpdated(info:). Isso expressa a proposta do destino. Não substitui a configuração dos recursos da origem, a observação da operação concluída nem as mutações do seu modelo. Quando as APIs mais novas de origem do SwiftUI não estiverem disponíveis e você precisar de controle explícito das duas extremidades, o ciclo de vida de origem e destino do AppKit oferece esse caminho. dropUpdated(info:), NSDraggingSource
Verifique a operação e o modelo em conjunto
Para o exemplo, use esta lista de estados esperados. Estas são condições de aceitação derivadas da implementação, não resultados de testes relatados:
| Ação | Original | Destino de cópia | Destino de movimentação |
|---|---|---|---|
Soltar uma vez em Copy here |
Permanece | Recebe um novo identificador | Sem alterações |
| Copiar novamente | Permanece | Recebe outro novo identificador | Sem alterações |
Soltar em Move here |
Desaparece após a movimentação concluída | Sem alterações | Recebe o identificador original |
| Cancelar ou soltar longe dos dois destinos | Permanece | Sem alterações | Sem alterações |
Quando o comportamento divergir, inspecione as etapas nesta ordem: recursos da origem, operação do destino, inserção no destino e conclusão na origem. Um original duplicado após a movimentação indica que a limpeza da origem pode estar faltando. Duas remoções indicam que há proprietários concorrentes do modelo. Uma cópia inesperada indica que a origem não ofereceu movimentação ou que o destino selecionou cópia.
Mantenha clara a responsabilidade final: a operação negociada descreve a transferência, enquanto seu modelo implementa a localização e a identidade do item. Ofereça as operações compatíveis, escolha uma operação permitida no destino e faça cada alteração do modelo uma única vez, no limite do ciclo de vida documentado.
Fontes
DragConfiguration — Apple
Identifiable — Apple
onDragSessionUpdated(_:) — Apple
dragConfiguration(_:) — Apple
dropConfiguration(_:) — Apple
draggable(_:) — Apple
DropSession — Apple
DragSession.Phase — Apple
DragSession — Apple
NSDraggingDestination — Apple
draggingEntered(_:) — Apple
performDragOperation(_:) — Apple
dropUpdated(info:) — Apple
NSDraggingSource — Apple