Compartilhar via


Compilando aplicativos macOS modernos

Este artigo aborda várias dicas, recursos e técnicas que um desenvolvedor pode usar para criar um aplicativo macOS moderno no Xamarin.Mac.

Criando looks modernos com vistas modernas

Uma aparência moderna incluirá uma aparência moderna de Janela e Barra de Ferramentas, como o aplicativo de exemplo mostrado abaixo:

Um exemplo de uma interface do usuário de aplicativo Mac moderno

Habilitando exibições de conteúdo em tamanho real

Para obter essa aparência em um aplicativo Xamarin.Mac, o desenvolvedor desejará usar um Modo de Exibição de Conteúdo em Tamanho Completo, o que significa que o conteúdo se estende sob as áreas da Barra de Ferramentas e Título e será desfocado automaticamente pelo macOS.

Para habilitar esse recurso no código, crie uma classe personalizada para o NSWindowController e faça com que ela tenha a seguinte aparência:

using System;
using Foundation;
using AppKit;

namespace MacModern
{
    public partial class MainWindowController : NSWindowController
    {
        #region Constructor
        public MainWindowController (IntPtr handle) : base (handle)
        {
        }
        #endregion

        #region Override Methods
        public override void WindowDidLoad ()
        {
            base.WindowDidLoad ();

            // Set window to use Full Size Content View
            Window.StyleMask = NSWindowStyle.FullSizeContentView;
        }
        #endregion
    }
}

Esse recurso também pode ser habilitado no Construtor de Interfaces do Xcode selecionando a Janela e marcando a Exibição de Conteúdo em Tamanho Completo:

Editando o storyboard principal no Construtor de Interfaces do Xcode

Ao usar um Modo de Exibição de Conteúdo em Tamanho Completo, o desenvolvedor pode precisar deslocar o conteúdo abaixo das áreas de título e barra de ferramentas para que o conteúdo específico (como rótulos) não deslize sob eles.

Para complicar esse problema, as áreas Título e Barra de Ferramentas podem ter uma altura dinâmica com base na ação que o usuário está executando no momento, na versão do macOS que o usuário instalou e/ou no hardware do Mac em que o aplicativo está sendo executado.

Como resultado, simplesmente codificar o deslocamento ao definir o layout da interface do usuário não funcionará. O desenvolvedor precisará adotar uma abordagem dinâmica.

A Apple incluiu a propriedade Observable ContentLayoutRect de Valor-Chave da NSWindow classe para obter a Área de Conteúdo atual no código. O desenvolvedor pode usar esse valor para posicionar manualmente os elementos necessários quando a Área de Conteúdo for alterada.

A melhor solução é usar o Layout Automático e as Classes de Tamanho para posicionar os elementos da interface do usuário no código ou no Construtor de Interfaces.

Um código como o exemplo a seguir pode ser usado para posicionar elementos da interface do usuário usando AutoLayout e classes de tamanho no controlador de exibição do aplicativo:

using System;
using AppKit;
using Foundation;

namespace MacModern
{
    public partial class ViewController : NSViewController
    {
        #region Computed Properties
        public NSLayoutConstraint topConstraint { get; set; }
        #endregion

        ...

        #region Override Methods
        public override void UpdateViewConstraints ()
        {
            // Has the constraint already been set?
            if (topConstraint == null) {
                // Get the top anchor point
                var contentLayoutGuide = ItemTitle.Window?.ContentLayoutGuide as NSLayoutGuide;
                var topAnchor = contentLayoutGuide.TopAnchor;

                // Found?
                if (topAnchor != null) {
                    // Assemble constraint and activate it
                    topConstraint = topAnchor.ConstraintEqualToAnchor (topAnchor, 20);
                    topConstraint.Active = true;
                }
            }

            base.UpdateViewConstraints ();
        }
        #endregion
    }
}

Esse código cria armazenamento para uma restrição superior que será aplicada a um Rótulo (ItemTitle) para garantir que ele não deslize sob a área Título e Barra de Ferramentas:

public NSLayoutConstraint topConstraint { get; set; }

Ao substituir o método do UpdateViewConstraints View Controller, o desenvolvedor pode testar para ver se a restrição necessária já foi criada e criá-la, se necessário.

Se uma nova restrição precisar ser criada, a ContentLayoutGuide propriedade da Janela que o controle precisa ser restrito será acessada e convertida em um NSLayoutGuide:

var contentLayoutGuide = ItemTitle.Window?.ContentLayoutGuide as NSLayoutGuide;

A propriedade TopAnchor do é acessada NSLayoutGuide e, se estiver disponível, é usada para criar uma nova restrição com a quantidade de deslocamento desejada e a nova restrição é ativada para aplicá-la:

// Assemble constraint and activate it
topConstraint = topAnchor.ConstraintEqualToAnchor (topAnchor, 20);
topConstraint.Active = true;

Ativando barras de ferramentas simplificadas

Uma janela normal do macOS inclui uma barra de título padrão que vai até a borda superior da janela. Se a janela também incluir uma barra de ferramentas, ela será exibida nesta área da barra de título:

Uma barra de ferramentas padrão do Mac

Ao usar uma barra de ferramentas simplificada, a área de título desaparece e a barra de ferramentas se move para a posição da barra de título, alinhada com os botões Fechar janela, Minimizar e Maximizar:

Uma barra de ferramentas simplificada do Mac

A barra de ferramentas simplificada é ativada substituindo o ViewWillAppear método do NSViewController e fazendo com que ele se pareça com o seguinte:

public override void ViewWillAppear ()
{
    base.ViewWillAppear ();

    // Enable streamlined Toolbars
    View.Window.TitleVisibility = NSWindowTitleVisibility.Hidden;
}

Esse efeito é normalmente usado para aplicativos Shoebox (aplicativos de uma janela) como Mapas, Calendário, Notas e Preferências do Sistema.

Usando controladores de vista de acessórios

Dependendo do design do aplicativo, o desenvolvedor também pode querer complementar a área da Barra de Título com um Controlador de Exibição de Acessórios que aparece logo abaixo da área da Barra de Título/Ferramentas para fornecer controles sensíveis ao contexto ao usuário com base na atividade em que ele está envolvido no momento:

Um exemplo de Controlador de Exibição de Acessórios

O controlador de exibição de acessórios será automaticamente desfocado e redimensionado pelo sistema sem intervenção do desenvolvedor.

Para adicionar um Controlador de Exibição de Acessórios, faça o seguinte:

  1. No Gerenciador de Soluções, clique duas vezes no arquivo Main.storyboard para abri-lo para edição.

  2. Arraste um Controlador de Exibição Personalizado para a hierarquia da Janela:

    Adicionando um novo controlador de exibição personalizado

  3. Faça o layout da interface do usuário da Visualização de Acessórios:

    Projetando a nova visualização

  4. Exponha a Visualização de Acessórios como uma Saída e quaisquer outras Ações ou Saídas para sua interface do usuário:

    Adicionando o OUtlet necessário

  5. Salve as alterações.

  6. Retorne ao Visual Studio para Mac para sincronizar as alterações.

Edite o NSWindowController e faça com que fique parecido com o seguinte:

using System;
using Foundation;
using AppKit;

namespace MacModern
{
    public partial class MainWindowController : NSWindowController
    {
        #region Constructor
        public MainWindowController (IntPtr handle) : base (handle)
        {
        }
        #endregion

        #region Override Methods
        public override void WindowDidLoad ()
        {
            base.WindowDidLoad ();

            // Create a title bar accessory view controller and attach
            // the view created in Interface Builder
            var accessoryView = new NSTitlebarAccessoryViewController ();
            accessoryView.View = AccessoryViewGoBar;

            // Set the location and attach the accessory view to the
            // titlebar to be displayed
            accessoryView.LayoutAttribute = NSLayoutAttribute.Bottom;
            Window.AddTitlebarAccessoryViewController (accessoryView);

        }
        #endregion
    }
}

Os pontos-chave desse código são onde a exibição é definida como a exibição personalizada que foi definida no Construtor de Interfaces e exposta como uma saída:

accessoryView.View = AccessoryViewGoBar;

E o LayoutAttribute que define onde o acessório será exibido:

accessoryView.LayoutAttribute = NSLayoutAttribute.Bottom;

Como o macOS agora está totalmente localizado, as propriedades and NSLayoutAttribute Right foram descontinuadas Left e devem ser substituídas por Leading e Trailing.

Usando janelas com guias

Além disso, o sistema macOS pode adicionar Controladores de Exibição de Acessórios à Janela do aplicativo. Por exemplo, para criar Janelas com guias em que várias janelas do aplicativo são mescladas em uma janela virtual:

Um exemplo de uma janela Mac com guias

Normalmente, o desenvolvedor precisará executar uma ação limitada usando o Windows com guias em seus aplicativos Xamarin.Mac, o sistema os tratará automaticamente da seguinte maneira:

  • O Windows será automaticamente tabulado quando o OrderFront método for invocado.
  • O Windows será automaticamente destabulado quando o OrderOut método for invocado.
  • No código, todas as janelas com guias ainda são consideradas "visíveis", no entanto, todas as guias não frontais são ocultadas pelo sistema usando CoreGraphics.
  • Use a propriedade de NSWindow para agrupar o TabbingIdentifier Windows em Guias.
  • Se for um NSDocument aplicativo baseado, vários desses recursos serão ativados automaticamente (como o botão de adição sendo adicionado à barra de guias) sem nenhuma ação do desenvolvedor.
  • NSDocument Aplicativos não baseados podem habilitar o botão "mais" no Grupo de guias para adicionar um novo documento substituindo o GetNewWindowForTab método do NSWindowsController.

Juntando todas as peças, o AppDelegate de um aplicativo que queria usar janelas com guias baseadas no sistema poderia ser semelhante ao seguinte:

using AppKit;
using Foundation;

namespace MacModern
{
    [Register ("AppDelegate")]
    public class AppDelegate : NSApplicationDelegate
    {
        #region Computed Properties
        public int NewDocumentNumber { get; set; } = 0;
        #endregion

        #region Constructors
        public AppDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override void DidFinishLaunching (NSNotification notification)
        {
            // Insert code here to initialize your application
        }

        public override void WillTerminate (NSNotification notification)
        {
            // Insert code here to tear down your application
        }
        #endregion

        #region Custom Actions
        [Export ("newDocument:")]
        public void NewDocument (NSObject sender)
        {
            // Get new window
            var storyboard = NSStoryboard.FromName ("Main", null);
            var controller = storyboard.InstantiateControllerWithIdentifier ("MainWindow") as NSWindowController;

            // Display
            controller.ShowWindow (this);
        }
        #endregion
    }
}

Onde a NewDocumentNumber propriedade controla o número de novos documentos criados e o NewDocument método cria um novo documento e o exibe.

O NSWindowController poderia então se parecer com:

using System;
using Foundation;
using AppKit;

namespace MacModern
{
    public partial class MainWindowController : NSWindowController
    {
        #region Application Access
        /// <summary>
        /// A helper shortcut to the app delegate.
        /// </summary>
        /// <value>The app.</value>
        public static AppDelegate App {
            get { return (AppDelegate)NSApplication.SharedApplication.Delegate; }
        }
        #endregion

        #region Constructor
        public MainWindowController (IntPtr handle) : base (handle)
        {
        }
        #endregion

        #region Public Methods
        public void SetDefaultDocumentTitle ()
        {
            // Is this the first document?
            if (App.NewDocumentNumber == 0) {
                // Yes, set title and increment
                Window.Title = "Untitled";
                ++App.NewDocumentNumber;
            } else {
                // No, show title and count
                Window.Title = $"Untitled {App.NewDocumentNumber++}";
            }
        }
        #endregion

        #region Override Methods
        public override void WindowDidLoad ()
        {
            base.WindowDidLoad ();

            // Prefer Tabbed Windows
            Window.TabbingMode = NSWindowTabbingMode.Preferred;
            Window.TabbingIdentifier = "Main";

            // Set default window title
            SetDefaultDocumentTitle ();

            // Set window to use Full Size Content View
            // Window.StyleMask = NSWindowStyle.FullSizeContentView;

            // Create a title bar accessory view controller and attach
            // the view created in Interface Builder
            var accessoryView = new NSTitlebarAccessoryViewController ();
            accessoryView.View = AccessoryViewGoBar;

            // Set the location and attach the accessory view to the
            // titlebar to be displayed
            accessoryView.LayoutAttribute = NSLayoutAttribute.Bottom;
            Window.AddTitlebarAccessoryViewController (accessoryView);

        }

        public override void GetNewWindowForTab (NSObject sender)
        {
            // Ask app to open a new document window
            App.NewDocument (this);
        }
        #endregion
    }
}

Onde a propriedade static App fornece um atalho para chegar ao AppDelegate. O SetDefaultDocumentTitle método define um novo título de documentos com base no número de novos documentos criados.

O código a seguir informa ao macOS que o aplicativo prefere usar guias e fornece uma cadeia de caracteres que permite que o Windows do aplicativo seja agrupado em guias:

// Prefer Tabbed Windows
Window.TabbingMode = NSWindowTabbingMode.Preferred;
Window.TabbingIdentifier = "Main";

E o seguinte método de substituição adiciona um botão de adição à barra de guias que criará um novo documento quando clicado pelo usuário:

public override void GetNewWindowForTab (NSObject sender)
{
    // Ask app to open a new document window
    App.NewDocument (this);
}

Usando a animação principal

O Core Animation é um mecanismo de renderização gráfica de alta potência integrado ao macOS. A animação principal foi otimizada para aproveitar a GPU (Unidade de Processamento Gráfico) disponível no hardware macOS moderno, em vez de executar as operações gráficas na CPU, o que pode tornar a máquina mais lenta.

O CALayer, fornecido pela Core Animation, pode ser usado para tarefas como rolagem rápida e fluida e animações. A interface do usuário de um aplicativo deve ser composta por várias subexibições e camadas para aproveitar totalmente a animação principal.

Um CALayer objeto fornece várias propriedades que permitem ao desenvolvedor controlar o que é apresentado na tela ao usuário, como:

  • Content - Pode ser um NSImage ou CGImage que fornece o conteúdo da camada.
  • BackgroundColor - Define a cor de fundo da camada como um CGColor
  • BorderWidth - Define a largura da borda.
  • BorderColor - Define a cor da borda.

Para utilizar os gráficos principais na interface do usuário do aplicativo, ele deve usar exibições com suporte de camada, que a Apple sugere que o desenvolvedor sempre habilite na exibição de conteúdo da janela. Dessa forma, todas as visualizações filhas também herdarão automaticamente o Layer Backing

Além disso, a Apple sugere o uso de visualizações com suporte de camada em vez de adicionar uma nova CALayer como uma subcamada, porque o sistema lidará automaticamente com várias das configurações necessárias (como as exigidas por uma tela Retina).

O Layer Backing pode ser habilitado definindo o WantsLayer de a NSView para true ou dentro do Construtor de Interfaces do Xcode no Inspetor de Efeitos de Exibição, verificando a Camada de Animação Principal:

O Inspetor de efeitos de exibição

Redesenhar vistas com camadas

Outra etapa importante ao usar Exibições com Suporte de Camada em um aplicativo Xamarin.Mac é definir o LayerContentsRedrawPolicy de NSView como OnSetNeedsDisplay no NSViewController. Por exemplo:

public override void ViewWillAppear ()
{
    base.ViewWillAppear ();

    // Set the content redraw policy
    View.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
}

Se o desenvolvedor não definir essa propriedade, a Exibição será redesenhada sempre que a origem do quadro for alterada, o que não é desejado por motivos de desempenho. Com essa propriedade definida como OnSetNeedsDisplay o desenvolvedor terá que definir NeedsDisplay manualmente como true para forçar o conteúdo a ser redesenhado.

Quando uma exibição é marcada como suja, o sistema verifica a WantsUpdateLayer propriedade da exibição. Se ele retornar true , o UpdateLayer método será chamado, caso contrário, o DrawRect método do Modo de Exibição será chamado para atualizar o conteúdo do Modo de Exibição.

A Apple tem as seguintes sugestões para atualizar o conteúdo de um Views quando necessário:

  • A Apple prefere usar UpdateLater sempre DrawRect que possível, pois fornece um aumento significativo de desempenho.
  • Use o mesmo layer.Contents para elementos de interface do usuário que parecem semelhantes.
  • A Apple também prefere que o desenvolvedor componha sua interface do usuário usando exibições padrão, como NSTextField, novamente sempre que possível.

Para usar UpdateLayero , crie uma classe personalizada para o NSView e faça com que o código se pareça com o seguinte:

using System;
using Foundation;
using AppKit;

namespace MacModern
{
    public partial class MainView : NSView
    {
        #region Computed Properties
        public override bool WantsLayer {
            get { return true; }
        }

        public override bool WantsUpdateLayer {
            get { return true; }
        }
        #endregion

        #region Constructor
        public MainView (IntPtr handle) : base (handle)
        {
        }
        #endregion

        #region Override Methods
        public override void DrawRect (CoreGraphics.CGRect dirtyRect)
        {
            base.DrawRect (dirtyRect);

        }

        public override void UpdateLayer ()
        {
            base.UpdateLayer ();

            // Draw view
            Layer.BackgroundColor = NSColor.Red.CGColor;
        }
        #endregion
    }
}

Usando o recurso moderno de arrastar e soltar

Para apresentar uma experiência moderna de arrastar e soltar para o usuário, o desenvolvedor deve adotar o Drag Flocking nas operações de arrastar e soltar do aplicativo. Arrastar Agrupamento é onde cada arquivo ou item individual que está sendo arrastado aparece inicialmente como um elemento individual que se agrupa (agrupa sob o cursor com uma contagem do número de itens) à medida que o usuário continua a operação de arrastar.

Se o usuário encerrar a operação Arrastar, os elementos individuais serão desagrupados e retornarão aos seus locais originais.

O código de exemplo a seguir habilita o Drag Flocking em uma exibição personalizada:

using System;
using System.Collections.Generic;
using Foundation;
using AppKit;

namespace MacModern
{
    public partial class MainView : NSView, INSDraggingSource, INSDraggingDestination
    {
        #region Constructor
        public MainView (IntPtr handle) : base (handle)
        {
        }
        #endregion

        #region Override Methods
        public override void MouseDragged (NSEvent theEvent)
        {
            // Create group of string to be dragged
            var string1 = new NSDraggingItem ((NSString)"Item 1");
            var string2 = new NSDraggingItem ((NSString)"Item 2");
            var string3 = new NSDraggingItem ((NSString)"Item 3");

            // Drag a cluster of items
            BeginDraggingSession (new [] { string1, string2, string3 }, theEvent, this);
        }
        #endregion
    }
}

O efeito de flocagem foi obtido enviando cada item que está sendo arrastado para o BeginDraggingSession método do NSView como um elemento separado em uma matriz.

Ao trabalhar com um NSTableView ou NSOutlineView, use o PastboardWriterForRow NSTableViewDataSource método da classe para iniciar a operação de arrastar:

using System;
using System.Collections.Generic;
using Foundation;
using AppKit;

namespace MacModern
{
    public class ContentsTableDataSource: NSTableViewDataSource
    {
        #region Constructors
        public ContentsTableDataSource ()
        {
        }
        #endregion

        #region Override Methods
        public override INSPasteboardWriting GetPasteboardWriterForRow (NSTableView tableView, nint row)
        {
            // Return required pasteboard writer
            ...

            // Pasteboard writer failed
            return null;
        }
        #endregion
    }
}

Isso permite que o desenvolvedor forneça um indivíduo NSDraggingItem para cada item na tabela que está sendo arrastado, em oposição ao método WriteRowsWith mais antigo que grava todas as linhas como um único grupo na área de trabalho.

Ao trabalhar com NSCollectionViewso , use novamente o PasteboardWriterForItemAt método em vez do método quando Arrastar WriteItemsAt começa.

O desenvolvedor deve sempre evitar colocar arquivos grandes na área de trabalho. Novidade no macOS Sierra, as Promessas de Arquivo permitem que o desenvolvedor coloque referências a determinados arquivos na área de trabalho que serão posteriormente cumpridas quando o usuário concluir a operação Soltar usando as classes new NSFilePromiseProvider and NSFilePromiseReceiver .

Usando o acompanhamento de eventos moderno

Para um elemento da interface do usuário (como um NSButton) que foi adicionado a uma área de título ou barra de ferramentas, o usuário deve ser capaz de clicar no elemento e fazer com que ele dispare um evento normalmente (como exibir uma janela pop-up). No entanto, como o item também está na área Título ou Barra de Ferramentas, o usuário deve ser capaz de clicar e arrastar o elemento para mover a janela também.

Para fazer isso no código, crie uma classe personalizada para o elemento (como NSButton) e substitua o MouseDown evento da seguinte maneira:

public override void MouseDown (NSEvent theEvent)
{
    var shouldCallSuper = false;

    Window.TrackEventsMatching (NSEventMask.LeftMouseUp, 2000, NSRunLoop.NSRunLoopEventTracking, (NSEvent evt, ref bool stop) => {
        // Handle event as normal
        stop = true;
        shouldCallSuper = true;
    });

    Window.TrackEventsMatching(NSEventMask.LeftMouseDragged, 2000, NSRunLoop.NSRunLoopEventTracking, (NSEvent evt, ref bool stop) => {
        // Pass drag event to window
        stop = true;
        Window.PerformWindowDrag (evt);
    });

    // Call super to handle mousedown
    if (shouldCallSuper) {
        base.MouseDown (theEvent);
    }
}

Esse código usa o TrackEventsMatching NSWindow método do elemento de interface do usuário que está anexado para interceptar os LeftMouseUp eventos and LeftMouseDragged . Para um LeftMouseUp evento, o elemento de interface do usuário responde normalmente. Para o LeftMouseDragged evento, o evento é passado para o NSWindowmétodo do PerformWindowDrag para mover a janela na tela.

Chamar o PerformWindowDrag método da NSWindow classe fornece os seguintes benefícios:

  • Ele permite que a janela se mova, mesmo se o aplicativo estiver travado (como ao processar um loop profundo).
  • A comutação de espaço funcionará conforme o esperado.
  • A barra de espaços será exibida normalmente.
  • O ajuste e o alinhamento da janela funcionam normalmente.

Usando controles de exibição de contêiner modernos

O macOS Sierra fornece muitas melhorias modernas para os controles de exibição de contêiner existentes disponíveis na versão anterior do sistema operacional.

Aprimoramentos da exibição de tabela

O desenvolvedor deve sempre usar a nova NSView versão baseada dos Controles de Exibição de Contêiner, como NSTableView. Por exemplo:

using System;
using System.Collections.Generic;
using Foundation;
using AppKit;

namespace MacModern
{
    public class ContentsTableDelegate : NSTableViewDelegate
    {
        #region Constructors
        public ContentsTableDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
        {
            // Build new view
            var view = new NSView ();
            ...

            // Return the view representing the item to display
            return view;
        }
        #endregion
    }
}

Isso permite que ações de linha de tabela personalizadas sejam anexadas a determinadas linhas na tabela (como deslizar para a direita para excluir a linha). Para habilitar esse comportamento, substitua o RowActions método do NSTableViewDelegate:

using System;
using System.Collections.Generic;
using Foundation;
using AppKit;

namespace MacModern
{
    public class ContentsTableDelegate : NSTableViewDelegate
    {
        #region Constructors
        public ContentsTableDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
        {
            // Build new view
            var view = new NSView ();
            ...

            // Return the view representing the item to display
            return view;
        }

        public override NSTableViewRowAction [] RowActions (NSTableView tableView, nint row, NSTableRowActionEdge edge)
        {
            // Take action based on the edge
            if (edge == NSTableRowActionEdge.Trailing) {
                // Create row actions
                var editAction = NSTableViewRowAction.FromStyle (NSTableViewRowActionStyle.Regular, "Edit", (action, rowNum) => {
                    // Handle row being edited
                    ...
                });

                var deleteAction = NSTableViewRowAction.FromStyle (NSTableViewRowActionStyle.Destructive, "Delete", (action, rowNum) => {
                    // Handle row being deleted
                    ...
                });

                // Return actions
                return new [] { editAction, deleteAction };
            } else {
                // No matching actions
                return null;
            }
        }
        #endregion
    }
}

A estática NSTableViewRowAction.FromStyle é usada para criar uma nova Ação de Linha de Tabela dos seguintes estilos:

  • Regular - Executa uma ação não destrutiva padrão, como editar o conteúdo da linha.
  • Destructive - Executa uma ação destrutiva, como excluir a linha da tabela. Essas ações serão renderizadas com um fundo vermelho.

Aprimoramentos da exibição de rolagem

Ao usar um Modo de Exibição de Rolagem (NSScrollView) diretamente ou como parte de outro controle (como NSTableView), o conteúdo do Modo de Exibição de Rolagem pode deslizar sob as áreas Título e Barra de Ferramentas em um aplicativo Xamarin.Mac usando uma Aparência e Modos de Exibição Modernos.

Como resultado, o primeiro item na área de conteúdo do Modo de Exibição de Rolagem pode ser parcialmente obscurecido pela área Título e Barra de Ferramentas.

Para corrigir esse problema, a NSScrollView Apple adicionou duas novas propriedades à classe:

  • ContentInsets - Permite que o desenvolvedor forneça um NSEdgeInsets objeto definindo o deslocamento que será aplicado à parte superior da Visualização de rolagem.
  • AutomaticallyAdjustsContentInsets - Se true a Visualização de rolagem manipulará automaticamente o ContentInsets para o desenvolvedor.

Ao usar o ContentInsets o desenvolvedor pode ajustar o início da visualização de rolagem para permitir a inclusão de acessórios como:

  • Um indicador de classificação como o mostrado no aplicativo Email.
  • Um campo de pesquisa.
  • Um botão Atualizar ou Atualizar.

Layout automático e localização em aplicativos modernos

A Apple incluiu várias tecnologias no Xcode que permitem ao desenvolvedor criar facilmente um aplicativo macOS internacionalizado. O Xcode agora permite que o desenvolvedor separe o texto voltado para o usuário do design da interface do usuário do aplicativo em seus arquivos Storyboard e fornece ferramentas para manter essa separação se a interface do usuário for alterada.

Para obter mais informações, consulte o Guia de Internacionalização e Localização da Apple.

Implementação da internacionalização da base

Ao implementar a Internacionalização Base, o desenvolvedor pode fornecer um único arquivo Storyboard para representar a interface do usuário do aplicativo e separar todas as cadeias de caracteres voltadas para o usuário.

Quando o desenvolvedor estiver criando o arquivo (ou arquivos) de Storyboard inicial que definem a Interface do Usuário do aplicativo, eles serão criados na Internacionalização Base (o idioma que o desenvolvedor fala).

Em seguida, o desenvolvedor pode exportar localizações e as cadeias de caracteres de internacionalização base (no design da interface do usuário do storyboard) que podem ser traduzidas para vários idiomas.

Posteriormente, essas localizações podem ser importadas e o Xcode gerará os arquivos de cadeia de caracteres específicos do idioma para o Storyboard.

Implementando o layout automático para dar suporte à localização

Como as versões localizadas de valores de cadeia de caracteres podem ter tamanhos e/ou direção de leitura muito diferentes, o desenvolvedor deve usar o Layout Automático para posicionar e dimensionar a Interface do Usuário do aplicativo em um arquivo Storyboard.

A Apple sugere fazer o seguinte:

  • Remover restrições de largura fixa - Todas as exibições baseadas em texto devem ter permissão para redimensionar com base em seu conteúdo. A visualização de largura fixa pode cortar seu conteúdo em idiomas específicos.
  • Usar tamanhos de conteúdo intrínsecos - Por padrão, as exibições baseadas em texto serão dimensionadas automaticamente para caber em seu conteúdo. Para exibição baseada em texto que não está sendo dimensionada corretamente, selecione-as no Construtor de Interfaces do Xcode e escolha Editar>Tamanho para Ajustar ao Conteúdo.
  • Aplicar atributos à esquerda e à direita - Como a direção do texto pode mudar com base no idioma do usuário, use os atributos new Leading e Trailing constraint em vez dos atributos and existentes Right Left . Leading e Trailing será ajustado automaticamente com base na direção dos idiomas.
  • Fixar vistas em vistas adjacentes - Isso permite que as vistas sejam reposicionadas e redimensionadas à medida que as vistas ao seu redor mudam em resposta ao idioma selecionado.
  • Não definir tamanhos mínimos e/ou máximos do Windows - Permitir que o Windows altere o tamanho à medida que o idioma selecionado redimensiona suas áreas de conteúdo.
  • Teste as alterações de layout constantemente - Durante o desenvolvimento no aplicativo deve ser testado constantemente em diferentes idiomas. Consulte a documentação da Apple Testando seu aplicativo internacionalizado para obter mais detalhes.
  • Usar NSStackViews para fixar exibições juntas - NSStackViews permite que seu conteúdo mude e cresça de maneiras previsíveis e o tamanho do conteúdo mude com base no idioma selecionado.

Localização no Construtor de Interfaces do Xcode

A Apple forneceu vários recursos no Construtor de Interfaces do Xcode que o desenvolvedor pode usar ao projetar ou editar a interface do usuário de um aplicativo para dar suporte à localização. A seção Direção do texto do Inspetor de atributos permite que o desenvolvedor forneça dicas sobre como a direção deve ser usada e atualizada em uma exibição baseada em texto selecionada (como NSTextField):

As opções de Direção do texto

Há três valores possíveis para a Direção do texto:

  • Natural - O layout é baseado na cadeia de caracteres atribuída ao controle.
  • Da esquerda para a direita - O layout é sempre forçado da esquerda para a direita.
  • Da direita para a esquerda - O layout é sempre forçado da direita para a esquerda.

Há dois valores possíveis para o Layout:

  • Da esquerda para a direita - O layout é sempre da esquerda para a direita.
  • Da direita para a esquerda - O layout é sempre da direita para a esquerda.

Normalmente, eles não devem ser alterados, a menos que um alinhamento específico seja necessário.

A propriedade Mirror informa ao sistema para inverter propriedades de controle específicas (como a Posição da Imagem da Célula). Ele tem três valores possíveis:

  • Automaticamente - A posição mudará automaticamente com base na direção do idioma selecionado.
  • Na interface da direita para a esquerda - A posição só será alterada em idiomas da direita para a esquerda.
  • Nunca - A posição nunca mudará.

Se o desenvolvedor tiver especificado o alinhamento Central, Justificar ou Total no conteúdo de uma exibição baseada em texto, eles nunca serão invertidos com base no idioma selecionado.

Antes do macOS Sierra, os controles criados no código não eram espelhados automaticamente. O desenvolvedor teve que usar um código como o seguinte para lidar com o espelhamento:

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();

    // Setting a button's mirroring based on the layout direction
    var button = new NSButton ();
    if (button.UserInterfaceLayoutDirection == NSUserInterfaceLayoutDirection.LeftToRight) {
        button.Alignment = NSTextAlignment.Right;
        button.ImagePosition = NSCellImagePosition.ImageLeft;
    } else {
        button.Alignment = NSTextAlignment.Left;
        button.ImagePosition = NSCellImagePosition.ImageRight;
    }
}

Onde o Alignment e ImagePosition estão sendo definidos com base no UserInterfaceLayoutDirection do controle.

O macOS Sierra adiciona vários novos construtores de conveniência (por meio do método estático CreateButton ) que usam vários parâmetros (como Título, Imagem e Ação) e espelham automaticamente corretamente. Por exemplo:

var button2 = NSButton.CreateButton (myTitle, myImage, () => {
    // Take action when the button is pressed
    ...
});

Usando aparências do sistema

Os aplicativos macOS modernos podem adotar uma nova aparência de interface escura que funciona bem para aplicativos de criação, edição ou apresentação de imagens:

Um exemplo de uma interface do usuário escura do Mac Window

Isso pode ser feito adicionando uma linha de código antes que a janela seja apresentada. Por exemplo:

using System;
using AppKit;
using Foundation;

namespace MacModern
{
    public partial class ViewController : NSViewController
    {
        ...

        #region Override Methods
        public override void ViewWillAppear ()
        {
            base.ViewWillAppear ();

            // Apply the Dark Interface Appearance
            View.Window.Appearance = NSAppearance.GetAppearance (NSAppearance.NameVibrantDark);

            ...
        }
        #endregion
    }
}

O método estático GetAppearance da NSAppearance classe é usado para obter uma aparência nomeada do sistema (neste caso NSAppearance.NameVibrantDark).

A Apple tem as seguintes sugestões para usar as Aparências do Sistema:

  • Prefira cores nomeadas em vez de valores codificados (como LabelColor e SelectedControlColor).
  • Use o estilo de controle padrão do sistema sempre que possível.

Um aplicativo macOS que usa as Aparências do Sistema funcionará automaticamente corretamente para usuários que ativaram os recursos de Acessibilidade no aplicativo Preferências do Sistema. Como resultado, a Apple sugere que o desenvolvedor sempre use as aparências do sistema em seus aplicativos macOS.

Projetando interfaces do usuário com storyboards

Os storyboards permitem que o desenvolvedor não apenas projete os elementos individuais que compõem a interface do usuário de um aplicativo, mas também visualize e projete o fluxo da interface do usuário e a hierarquia dos elementos fornecidos.

Os controladores permitem que o desenvolvedor colete elementos em uma unidade de composição e Segues abstratos e remova o "código de cola" típico necessário para se mover por toda a hierarquia de exibição:

Editando a interface do usuário no Construtor de Interfaces do Xcode

Para obter mais informações, consulte nossa documentação de Introdução aos Storyboards .

Há muitos casos em que uma determinada cena definida em um storyboard exigirá dados de uma cena anterior na Hierarquia de Exibição. A Apple tem as seguintes sugestões para passar informações entre as cenas:

  • As dependências de dados devem sempre se espalhar para baixo na hierarquia.
  • Evite codificar dependências estruturais da interface do usuário, pois isso limita a flexibilidade da interface do usuário.
  • Use interfaces C# para fornecer dependências de dados genéricas.

O Controlador de Exibição que está atuando como a origem do Segue pode substituir o PrepareForSegue método e fazer qualquer inicialização necessária (como passar dados) antes que o Segue seja executado para exibir o Controlador de Exibição de destino. Por exemplo:

public override void PrepareForSegue (NSStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue (segue, sender);

    // Take action based on Segue ID
    switch (segue.Identifier) {
    case "MyNamedSegue":
        // Prepare for the segue to happen
        ...
        break;
    }
}

Para obter mais informações, consulte nossa documentação do Segues .

Ações de propagação

Com base no design do aplicativo macOS, pode haver momentos em que o melhor manipulador para uma ação em um controle de interface do usuário pode estar em outro lugar na hierarquia da interface do usuário. Isso normalmente é verdadeiro para Menus e Itens de Menu que residem em sua própria cena, separados do restante da interface do usuário do aplicativo.

Para lidar com essa situação, o desenvolvedor pode criar uma Ação Personalizada e passar a Ação para cima na cadeia de resposta. Para obter mais informações, consulte nossa documentação Trabalhando com Ações de Janela Personalizadas .

Recursos modernos do Mac

A Apple incluiu vários recursos voltados para o usuário no macOS Sierra que permitem ao desenvolvedor aproveitar ao máximo a plataforma Mac, como:

  • NSUserActivity - Isso permite que o aplicativo descreva a atividade na qual o usuário está envolvido no momento. NSUserActivity foi inicialmente criado para oferecer suporte ao HandOff, em que uma atividade iniciada em um dos dispositivos do usuário pode ser selecionada e continuada em outro dispositivo. NSUserActivity funciona da mesma forma no macOS e no iOS, portanto, consulte nossa documentação de Introdução ao Handoff iOS para obter mais detalhes.
  • Siri no Mac - A Siri usa a Atividade Atual (NSUserActivity) para fornecer contexto aos comandos que um usuário pode emitir.
  • Restauração de estado - Quando o usuário sai de um aplicativo no macOS e o reinicia posteriormente, o aplicativo retorna automaticamente ao estado anterior. O desenvolvedor pode usar a API de restauração de estado para codificar e restaurar estados transitórios da interface do usuário antes que a interface do usuário seja exibida para o usuário. Se o aplicativo for NSDocument baseado, a Restauração de Estado será tratada automaticamente. Para habilitar a Restauração de Estado para aplicativos nãoNSDocument baseados, defina a Restorable NSWindow classe como true.
  • Documentos na nuvem - Antes do macOS Sierra, um aplicativo precisava aceitar explicitamente trabalhar com documentos no iCloud Drive do usuário. No macOS Sierra, as pastas Mesa e Documentos do usuário podem ser sincronizadas com o iCloud Drive automaticamente pelo sistema. Como resultado, cópias locais de documentos podem ser excluídas para liberar espaço na máquina do usuário. NSDocument Os aplicativos baseados lidarão automaticamente com essa alteração. Todos os outros tipos de aplicativo precisarão usar um NSFileCoordinator para sincronizar a leitura e a gravação de documentos.

Resumo

Este artigo abordou várias dicas, recursos e técnicas que um desenvolvedor pode usar para criar um aplicativo macOS moderno no Xamarin.Mac.