Para copiar formas sintéticas entre dois painéis em um app para Mac, comece com o recurso tipado de arrastar e soltar do SwiftUI. Escolha provedores de itens quando uma interface de provedor existente ou um destino de macOS anterior exigir isso. Defina o conteúdo transferido, as regras de aceitação e o responsável pelo modelo antes de escolher qualquer uma das APIs.

Defina o que significa um soltar bem-sucedido

A tarefa da pessoa leitora aqui é específica: permitir arrastar um círculo ou quadrado sintético de uma paleta para uma coleção no mesmo app para Mac. O destino cria um novo item; a paleta permanece inalterada. Tudo fica na memória, e fechar a janela descarta a coleção.

A Apple recomenda itens transferíveis para arrastar e soltar no Swift. A documentação também oferece suporte a modificadores baseados em provedores, então a escolha depende dos requisitos de implantação e da interface que você precisa integrar. Arrastar e soltar — Apple.

Escreva o contrato de transferência antes de implementar as visualizações:

Pergunta do contrato Decisão para este exemplo
O que atravessa o limite? Um valor com versão que descreve um tipo de forma
Quem pode receber a representação? Somente o app de origem
O que faz um soltar bem-sucedido? Acrescenta um item recém-identificado à coleção
Quem é responsável pela mutação? O estado da coleção na visualização de destino
O que acontece em caso de cancelamento ou rejeição? Nenhuma alteração no modelo
O que significa o local do soltar? Nada neste caso; os itens aceitos são acrescentados em ordem

Essas são decisões de design do app. Uma pré-visualização de arraste representa o conteúdo transferido, enquanto o destino decide como esse conteúdo altera o modelo. Manter essas responsabilidades separadas permite mudar o transporte sem reescrever as regras da coleção.

Escolha a API mais simples que expressa o contrato

Situação Ponto de partida Motivo
Origem e destino em SwiftUI, macOS 13 ou posterior Transferable, .draggable e .dropDestination tipado Expressa diretamente o tipo de modelo aceito
Integração existente com NSItemProvider .onDrag e .onDrop Mantém o registro explícito da representação e seu carregamento
Destino anterior ao macOS 13 Verifique a disponibilidade dos modificadores de provedor para esse destino A base tipada exige macOS 13; a sobrecarga de provedor usada abaixo na discussão exige macOS 11
Uma visualização existente do AppKit controla a interação com o mouse APIs de arraste do AppKit Preserva o tratamento de eventos da visualização e o ciclo de vida da sessão de arraste

A Apple documenta Transferable e o destino tipado original para arrastar e soltar a partir do macOS 13. A sobrecarga de onDrop(of:isTargeted:perform:) baseada em provedor que aceita uma matriz de valores UTType está disponível a partir do macOS 11. Verifique a sobrecarga específica em vez de presumir que todas as APIs de arrastar e soltar compartilham a mesma versão mínima. Transferable, dropDestination(for:action:isTargeted:), onDrop(of:isTargeted:perform:).

A visibilidade restrita ao próprio app está disponível nas duas abordagens. Para uma representação tipada, aplique .visibility(.ownProcess); a Apple define esse nível como visibilidade somente dentro do app de origem. Portanto, escolher um provedor de itens não é necessário apenas para manter essa transferência dentro do app. visibility(_:), ownProcess.

Crie a interação de cópia tipada

Este exemplo completo consiste em dois arquivos Swift em um novo destino de app macOS com SwiftUI. Defina o destino de implantação como macOS 13 ou posterior e use o modo de linguagem Swift 5 para esta base. Substitua o ponto de entrada de app gerado pelo segundo arquivo; mantenha apenas uma declaração @main. O código foi inspecionado, mas não foi compilado aqui porque o shell disponível não tem um toolchain Swift.

Declare o conteúdo transferido e seu tipo de conteúdo

Adicione uma declaração de tipo exportado à lista de propriedades de informações do destino. No editor de lista de propriedades do Xcode, crie UTExportedTypeDeclarations como uma matriz com um dicionário. Defina UTTypeIdentifier desse dicionário como com.example.shape-transfer, UTTypeDescription como Synthetic Shape Transfer e UTTypeConformsTo como uma matriz contendo public.data. Esse identificador é ilustrativo; use um identificador exclusivo para seu app.

A declaração Swift e o identificador da lista de propriedades precisam corresponder. A Apple exige entradas correspondentes na lista de propriedades para tipos uniformes personalizados e explica as declarações de tipos exportados para formatos definidos pelo app. Este exemplo transfere dados, não arquivos, portanto não adiciona uma extensão de nome de arquivo. Definindo tipos de arquivo e dados para seu app.

Crie ShapePayload.swift:

import Foundation
import CoreTransferable
import UniformTypeIdentifiers

extension UTType {
    static let shapeTransfer = UTType(
        exportedAs: "com.example.shape-transfer",
        conformingTo: .data
    )
}

enum ShapeKind: String, Codable, Sendable {
    case circle = "Circle"
    case square = "Square"
}

struct ShapePayload: Codable, Sendable, Transferable {
    let version: Int
    let kind: ShapeKind

    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .shapeTransfer)
            .visibility(.ownProcess)
    }
}

struct PlacedShape: Identifiable {
    let id = UUID()
    let kind: ShapeKind
}

CodableRepresentation fornece a conversão entre o conteúdo transferido e os dados transferíveis. O conteúdo contém um valor, em vez de uma visualização ou referência a uma coleção mutável. A conformidade explícita com Sendable segue a regra do Swift de que os membros armazenados de uma estrutura enviável também precisam ser enviáveis. Transferable, Sendable.

Há deliberadamente apenas uma representação. Um proxy de texto acrescentaria outra maneira de consumir o item, o que não tem utilidade para este contrato. A Apple documenta a ordem das representações e os formatos alternativos; adicione-os somente quando um destinatário realmente precisar deles. Transformando uma visualização em uma origem de arraste.

Aceite o conteúdo e confirme uma atualização da coleção

Crie ShapeTransferApp.swift:

import SwiftUI

@main
struct ShapeTransferApp: App {
    var body: some Scene {
        WindowGroup {
            ShapeTransferView()
        }
    }
}

@MainActor
struct ShapeTransferView: View {
    @State private var placed: [PlacedShape] = []
    @State private var targeted = false
    @State private var message = "Drag a shape into Collection."

    var body: some View {
        HStack(alignment: .top, spacing: 24) {
            VStack(spacing: 16) {
                Text("Palette").font(.headline)
                paletteItem(.circle)
                paletteItem(.square)
            }
            .frame(width: 160)

            VStack(alignment: .leading, spacing: 12) {
                Text("Collection").font(.headline)
                Text(message)
                ForEach(placed) { item in
                    Label(item.kind.rawValue,
                          systemImage: symbol(for: item.kind))
                }
                Spacer(minLength: 20)
            }
            .padding()
            .frame(width: 300, height: 320, alignment: .topLeading)
            .background(targeted ? Color.blue.opacity(0.15)
                                 : Color.gray.opacity(0.08))
            .contentShape(Rectangle())
            .dropDestination(
                for: ShapePayload.self,
                action: { payloads, _ in
                    guard !payloads.isEmpty,
                          payloads.allSatisfy({ $0.version == 1 })
                    else {
                        message = "Drop rejected: unsupported payload."
                        return false
                    }
                    let additions = payloads.map {
                        PlacedShape(kind: $0.kind)
                    }
                    placed.append(contentsOf: additions)
                    message = "Added \(additions.count) shape(s)."
                    return true
                },
                isTargeted: { targeted = $0 }
            )
        }
        .padding(24)
    }

    private func paletteItem(_ kind: ShapeKind) -> some View {
        Label(kind.rawValue, systemImage: symbol(for: kind))
            .padding()
            .background(Color.gray.opacity(0.12))
            .draggable(ShapePayload(version: 1, kind: kind))
    }

    private func symbol(for kind: ShapeKind) -> String {
        switch kind {
        case .circle: return "circle.fill"
        case .square: return "square.fill"
        }
    }
}

Os rótulos explícitos action: e isTargeted: selecionam a sobrecarga original do destino tipado. A Apple define o valor Booleano retornado como sucesso ou falha e o argumento de localização como pertencente ao sistema de coordenadas da visualização de destino. Aqui, a localização é intencionalmente ignorada porque a coleção acrescenta as formas, em vez de posicioná-las. dropDestination(for:action:isTargeted:).

A regra de aceitação verifica cada conteúdo recebido antes de acrescentar qualquer item. Uma versão sem suporte rejeita o lote recebido; cada conteúdo válido recebe uma nova identidade no destino. Assim, repetir o mesmo arraste da paleta cria outra cópia. Essa regra se aplica aos valores entregues à closure; falhas de decodificação ocorrem antes que eles se tornem valores ShapePayload.

O estado de direcionamento altera somente a apresentação. Ele não insere itens nem remove nada da origem. Essa separação também simplifica o cancelamento: sem um soltar aceito, a coleção permanece inalterada.

Use provedores de itens quando a integração exigir

Uma implementação com provedor pode transportar a mesma versão e o mesmo tipo de forma. Mantenha o contrato inalterado e substitua o transporte seguindo estas etapas:

  1. Na closure .onDrag, crie um NSItemProvider e registre uma representação de dados usando o mesmo identificador de tipo personalizado e a visibilidade .ownProcess.
  2. Capture um valor de conteúdo imutável ou bytes preparados no manipulador de carregamento. Codifique esse valor e invoque o manipulador de conclusão da representação com os dados ou um erro.
  3. Configure .onDrop para aceitar o tipo personalizado.
  4. Dentro da ação de soltar, comece imediatamente a carregar cada provedor aceito usando loadDataRepresentation(forTypeIdentifier:completionHandler:).
  5. Decodifique os dados retornados, valide a versão e reúna os resultados. Faça a mutação final da coleção no ator principal somente depois que os resultados necessários estiverem disponíveis.

A Apple documenta o registro explícito de dados e a visibilidade restrita ao próprio app para provedores. O método de carregamento é assíncrono e retorna um objeto de progresso. A correspondência do identificador de tipo indica que é possível tentar o carregamento; não garante que o carregamento e a decodificação serão bem-sucedidos. registerDataRepresentation(forTypeIdentifier:visibility:loadHandler:), NSItemProviderRepresentationVisibility.ownProcess, loadDataRepresentation(forTypeIdentifier:completionHandler:).

Dois limites de tempo são importantes. Primeiro, a Apple diz que o carregamento do provedor precisa começar dentro da ação .onDrop, embora possa ser concluído mais tarde. Transferir a chamada inicial de carregamento para uma tarefa posterior em outro ator viola esse requisito documentado. Segundo, a Apple alerta que encaminhar o trabalho de exportação ao ator principal ou à thread principal a partir de manipuladores de representação pode causar bloqueios. Mantenha a exportação independente da mutação da interface; atualize o estado da coleção após a conclusão da importação. onDrop(of:isTargeted:perform:), onDrag(_:).

Em uma implementação assíncrona com provedores, diferencie aceitar o trabalho de carregamento de concluir a transação do modelo. Preserve a ordem dos provedores com resultados indexados, informe falhas de carregamento ou decodificação e deixe a coleção inalterada se o contrato exigir o sucesso de todos os itens. Evite descartar falhas silenciosamente usando uma operação de filtragem. Essas regras de implementação decorrem da política de aceitação de lotes deste exemplo.

Preserve o controle de eventos do AppKit e a semântica de movimentação

Se uma visualização existente do AppKit já controla a interação com o mouse, mantenha ali o início da sessão. O método beginDraggingSession(with:event:source:) da Apple recebe o evento de pressionamento do mouse na origem, os itens de arraste cujos quadros usam as coordenadas dessa visualização e uma origem que esteja em conformidade com NSDraggingSource. Os callbacks da origem cobrem o início, o movimento e a conclusão ou o cancelamento da sessão. beginDraggingSession(with:event:source:).

O limite de representação do AppKit também é explícito: NSDraggingItem(pasteboardWriter:) exige um objeto NSPasteboardWriting. Um callback de provedor do SwiftUI e um gravador de área de transferência do AppKit são interfaces diferentes. Ao integrar uma visualização personalizada do AppKit ao SwiftUI, deixe a visualização do AppKit controlar seus eventos e a sessão; deixe o modelo compartilhado controlar as alterações aceitas na coleção. init(pasteboardWriter:).

O exemplo realiza cópias. Se a tarefa mudar para mover itens existentes entre coleções, adicione um identificador estável de item e defina exatamente um responsável pela remoção. Um armazenamento compartilhado pode realocar um item uma vez, ou o destino pode inseri-lo enquanto a origem o remove após a confirmação da conclusão da movimentação. Combinar as duas estratégias pode remover o item duas vezes.

A documentação atual da Apple inclui DragConfiguration, uma sobrecarga tipada de destino baseada em sessão e a observação de sessões de arraste no macOS 26. A origem propõe as operações compatíveis, o destino escolhe a operação e a conclusão da sessão permite que a origem reaja ao resultado. Verifique a disponibilidade de cada símbolo no SDK antes de adotar essa abordagem. DragConfiguration, dropDestination(for:isEnabled:action:), onDragSessionUpdated(_:).

Não remova um item da origem quando o ponteiro entrar no destino ou quando aparecer uma pré-visualização. Esses eventos acontecem antes da aceitação. Para uma movimentação dentro do próprio app, a invariável útil é que cada item lógico pertença a uma única coleção após a conclusão da transação.

Verifique o contrato por meio de resultados observáveis

Use esta sequência para verificar a implementação em um Mac:

  • Arraste Circle para Collection: o destino ganha um círculo e a paleta mantém sua entrada.
  • Repita o arraste: o destino ganha um segundo item com identificação independente.
  • Arraste Square para fora do destino e solte: a coleção não muda.
  • Altere temporariamente a versão do conteúdo de origem para 2: o destino informa a rejeição e não adiciona nada; depois, restaure 1.
  • Em uma implementação com provedor, retorne deliberadamente um erro de codificação ou carregamento: mostre a falha sem inserir um lote parcial.
  • Em uma implementação de movimentação, cancele um arraste e confirme que o item original continua pertencendo à coleção original.

Escolha o SwiftUI tipado para esta tarefa de cópia entre dois painéis, a menos que um requisito concreto de compatibilidade ou integração exija provedores ou AppKit. O que perdura é o contrato: um valor definido atravessa o limite, o destino o valida e um único responsável pelo modelo confirma a alteração aceita.

Fontes