Swift / SwiftUI

SwiftUI para Mac Extras

O autor do artigo sobre desenvolvimento de aplicativos Mac com SwiftUI agradeceu aos leitores que entraram em contato e destacou novas abordagens para manipulação de sheets, como o uso da propriedade de apresentação no ambiente. Além disso, ele corrigiu um erro na passagem de dados entre AppKit e SwiftUI, melhorando a interação bidirecional com NSColorWell usando um Coordinator.

Compartilhar
Email

Último ano, escrevi uma série de três artigos sobre usando SwiftUI para construir um aplicativo Mac. Gostaria de agradecer a todos que entraram em contato comigo sobre esta série. Ela foi muito bem recebida e revelou que ainda há grande interesse na programação para o Mac.

Algumas das respostas que recebi apontavam diferentes ou melhores maneiras de fazer as coisas, então vou listar aqui, adicionando a este post conforme obtenho novas informações. As seções relevantes nos posts originais terão links para os reparos sugeridos aqui, mas decidi que era mais fácil listar as alterações em um post separado, em vez de pedir às pessoas que relêem toda a série procurando por modificações.


Descartando abas

Só queria dizer que acabei de ler sua série sobre escrever aplicativos Mac com SwiftUI. Muito bom! Quero apenas adicionar que na parte 2, ao descartar abas, existem duas maneiras de fazer isso: uma delas é a que você descobriu e a outra é ter a vista descartando-se a si mesma ao pegar seu PresentationMode do ambiente. Dessa forma, não há necessidade de passar vinculações de apresentação para suas vistas de aba.

Paul Hudson da Hacking with Swift explica como usar ambos os métodos muito claramente em seu artigo sobre Como fazer uma vista descartar-se a si mesma.

A minha técnica original passava o Boolean que desencadeou a aba para aparecer, como um Binding à vista de aba. A vista de aba poderia então alternar isso para fazer a vista pai descartá-la.

  .sheet(isPresented: $sheetIsShowing) {
      SheetView(isVisible: self.$sheetIsShowing)
  }
struct SheetView: View {
    @Binding var isVisible: Bool

    var body: some View {
        VStack {
            Text("This is a sheet.")
            Button("OK") {
                self.isVisible = false
            }
        }
        .frame(width: 300, height: 150)
    }
}

A maneira de apresentação permite que a folha se encarregue de fechar-se por conta própria. A vista pai exibe a folha, mas não passa nenhuma vinculação.

  .sheet(isPresented: $sheetIsShowing) {
      SheetView()
  }

A vista da folha recebe uma propriedade do ambiente que pode usar para alterar seu modo de apresentação, fechando-se dessa maneira.

struct SheetView: View {
    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        VStack {
            Text("Este é uma folha.")
            Button("OK") {
                self.presentationMode.wrappedValue.dismiss()
            }
        }
        .frame(width: 300, height: 150)
    }
}

Acho que o método de apresentação é um pouco mais fácil de entender e isso deixa a chamada da visualização da folha menos bagunçada, o que é uma coisa boa. Mas os dois métodos são funcionalmente idênticos, então use aquele que preferir. O projeto de amostra agora contém ambos os métodos, com um comentado, então você pode trocar os comentários para testar.


Se inscrevendo na thread principal

No segundo parte desta série, criei um item de menu que causou a imagem baixada ser invertida. Selecionar o item do menu postou uma notificação:

    @IBAction func inverterImagem(_ sender: Any) {
        NotificationCenter.default.post(name: .inverterImagem, object: nil)
    }

que a visualização mostrando a imagem estava ouvindo.

    private let flipImageMenuItemSelected = NotificationCenter.default
        .publisher(for: .flipImage)

O componente mais externo do corpo da visualização usou um modificador onReceive para obter esta notificação e reagir conforme necessário.

  var body: some View {
      VStack {

      }
      .onReceive(flipImageMenuItemSelected) { _ in
          DispatchQueue.main.async {
              self.imageIsFlipped.toggle()
          }
      }
    }

Como a notificação estava acionando uma alteração na interface do usuário, eu me certifiquei de que isso acontecesse na thread principal.

No entanto, foi-me apontado que seria mais fácil garantir que o publisher fosse configurado para usar a thread principal sempre, em vez de ter que especificar a fila principal ao processar a notificação.

Então agora o publisher é definido assim:

  private let flipImageMenuItemSelected = NotificationCenter.default
      .publisher(for: .flipImage)
      .receive(on: RunLoop.main)

E o modificador onReceive pode alternar a bandeira imageIsFlipped diretamente, sem precisar se preocupar com a thread.

Isto parece-me uma solução muito melhor, pois define a thread correta uma vez ao criar o publisher e torna seu uso mais limpo e fácil.


Passe de dados de volta do AppKit

As duas alterações anteriores foram mais uma questão de estilo, mas esta é um erro real que impediria a aplicação de funcionar como deveria.

No seção Elementos da Interface do Usuário da série, usei NSViewRepresentable para embutir um NSColorWell padrão em uma visualização SwiftUI. Eu pensei que isso estava funcionando corretamente, mas então recebi este e-mail:

Percebi que o selectedColor no EmbeddedColorWell não está sendo mutado e não é usado de forma bidirecional. O selectedColor é inicializado com NSColor.blue e usado para definir a cor na view NSColorWell. Quando a cor do NSColorWell é alterada, ela muda a cor da view EmbeddedColorWell. No entanto, o valor real @State var selectedColor nunca é mutado além do valor inicial de NSColor.blue.

Isso está na aba Form do painel UI Samples. Como sugerido para mim, adicionei um background modifier para definir o fundo de uma parte da view com a cor selecionada. Embora o NSColorWell mostre novas cores selecionadas, o background permanece obstinadamente azul.

A solução foi adicionar um Coordinator ao NSViewRepresentable e tê-lo se inscrever em quaisquer alterações na cor e atualizar a cor selecionada da view embutida conforme necessário. Eu não teria chegado nisso sozinho, então muito obrigado à pessoa que me enviou.

Aqui está o código completo para o EmbeddedColorWell:

struct EmbeddedColorWell: NSViewRepresentable {
    @Binding var selectedColor: NSColor

    class Coordinator: NSObject {
        var embedded: EmbeddedColorWell
        var subscription: AnyCancellable?

        init(_ embedded: EmbeddedColorWell) {
            self.embedded = embedded
        }



        func changeColor(colorWell: NSColorWell) {
            subscription = colorWell
                .publisher(for: \.color, options: .new)
                .sink { color in
                    DispatchQueue.main.async {
                        self.embedded.selectedColor = color
                    }
            }
        }
    }
   
func makeCoordinator() -> EmbeddedColorWell.Coordinator { Coordinator(self) } func makeNSView(context: Context) -> NSColorWell { let colorWell

Fonte original

Conteúdo traduzido e adaptado pela redação do Notícias Mobile. Confira também a matéria na fonte original.

Leia a matéria completa