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.

Construindo 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 moderna do aplicativo Mac

Habilitando exibições de conteúdo de tamanho completo

Para conseguir essa aparência em um aplicativo Xamarin.Mac, o desenvolvedor desejará usar uma Visualização de conteúdo em tamanho completo, o que significa que o conteúdo se estende sob as áreas de Ferramenta e Barra de Título e será automaticamente desfocado pelo macOS.

Para habilitar esse recurso no código, crie uma classe personalizada para o NSWindowController e faça com que ele se pareça 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 ();

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

Esse recurso também pode ser ativado no Construtor de Interfaces do Xcode selecionando a Janela e marcando a Visualização de conteúdo de tamanho completo:

Editando o storyboard principal no Construtor de Interface do Xcode

Ao usar uma Exibição de Conteúdo de 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 elas.

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 no qual o aplicativo está sendo executado.

Como resultado, simplesmente codificar o deslocamento ao dispor a Interface do Usuário não funcionará. O desenvolvedor precisará adotar uma abordagem dinâmica.

A Apple incluiu a propriedade Key-Value ObservableContentLayoutRect 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 Auto Layout e Classes de Tamanho para posicionar os elementos da interface do usuário no código ou no Construtor de Interfaces.

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 o controle que precisa ser restrito será acessada e convertida em um NSLayoutGuide:

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

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

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

Habilitando barras de ferramentas simplificadas

Uma Janela normal do macOS inclui uma Barra de Título padrão na 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 cima na posição da Barra de Título, em linha com os botões Fechar, Minimizar e Maximizar da Janela:

Uma barra de ferramentas simplificada do Mac

A barra de ferramentas simplificada é habilitada substituindo o ViewWillAppear método do NSViewController e tornando-o semelhante ao 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 exibição 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 Título/Barra de Ferramentas para fornecer controles contextuais 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 Accessory View será automaticamente desfocado e redimensionado pelo sistema sem a intervenção do desenvolvedor.

Para adicionar um Controlador de Exibição de Acessório, 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. Layout da interface do usuário do Modo de Exibição de Acessório:

    Projetando o novo modo de exibição

  4. Exponha o Modo de Exibição de Acessórios como uma Tomada 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 ele 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 ();

            // 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 Interface 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 Left propriedades e RightNSLayoutAttribute foram preteridas e devem ser substituídas por Leading e Trailing.

Usando o Windows com guias

Além disso, o sistema macOS pode adicionar Controladores de Visualizaçã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 do Mac com guias

Normalmente, o desenvolvedor precisará tomar medidas limitadas para usar o Windows com guias em seus aplicativos Xamarin.Mac, o sistema os manipulará automaticamente da seguinte maneira:

  • O Windows será automaticamente tabulado quando o OrderFront método for chamado.
  • O Windows será automaticamente Untabbed quando o OrderOut método for chamado.
  • No código, todas as janelas com abas ainda são consideradas "visíveis", no entanto, quaisquer 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 que está sendo adicionado à Barra de Guias) sem qualquer ação do desenvolvedor.
  • NSDocument Os 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 de um aplicativo que queria usar o AppDelegate Windows com guias baseado no sistema poderia ter a seguinte aparência:

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 método de substituição a seguir 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

Core Animation é um motor de renderização gráfica de alta potência que está integrado no macOS. O Core Animation foi otimizado para aproveitar a GPU (Unidade de Processamento Gráfico) disponível no hardware moderno do macOS, 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 para o usuário, como:

  • Content - Pode ser um NSImage ou CGImage que forneça 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 Core Graphics na interface do usuário do aplicativo, ele deve estar usando Layer Backed Views, que a Apple sugere que o desenvolvedor sempre habilite na Visualização de conteúdo da janela. Dessa forma, todas as exibições filhas também herdarão automaticamente o Layer Back.

Além disso, a Apple sugere o uso de Layer Backed Views em vez de adicionar um novo CALayer como subcamada, pois o sistema lidará automaticamente com várias das configurações necessárias (como as exigidas por um Retina Display).

O Layer Backing pode ser ativado definindo o WantsLayer de um NSView para true ou dentro do Construtor de Interface do Xcode no View Effects Inspector verificando Core Animation Layer:

O Inspetor de Efeitos de Exibição

Redesenhando modos de exibição com camadas

Outra etapa importante ao usar Layer Backed Views em um aplicativo Xamarin.Mac é definir o LayerContentsRedrawPolicyNSView do para 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 sua origem de quadro for alterada, o que não é desejado por motivos de desempenho. Com essa propriedade definida para OnSetNeedsDisplay o desenvolvedor terá que definir NeedsDisplay manualmente para true forçar o conteúdo a redesenhar, no entanto.

Quando um Modo de Exibição é marcado como sujo, o sistema verifica a WantsUpdateLayer propriedade do Modo de Exibição. Se ele retornar true , o UpdateLayer método será chamado, caso contrário, o DrawRect método do View será chamado para atualizar o conteúdo do View.

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

  • A Apple prefere usar UpdateLater sempre que DrawRect possível, pois proporciona um aumento significativo de desempenho.
  • Use o mesmo layer.Contents para elementos da interface do usuário que parecem semelhantes.
  • A Apple também prefere que o desenvolvedor componha sua interface do usuário usando visualizaçõ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 tenha a seguinte aparência:

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 de arrastar e soltar moderno

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 de seu aplicativo. Arrastar Flocking é onde cada arquivo individual ou item que está sendo arrastado aparece inicialmente como um elemento individual que 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 removidos e retornarão aos seus locais originais.

O código de exemplo a seguir habilita o Drag Flocking em um modo de exibição personalizado:

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 agrupamento foi obtido enviando cada item sendo arrastado para o BeginDraggingSessionNSView método do como um elemento separado em uma matriz.

Ao trabalhar com um NSTableView ou NSOutlineView, use o PastboardWriterForRowNSTableViewDataSource método da classe para iniciar a operação Arrastando:

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 WriteItemsAt método quando Dragging começar.

O desenvolvedor deve sempre evitar colocar arquivos grandes na área de trabalho. Novo no macOS Sierra, o File Promises permite que o desenvolvedor coloque referências a determinados arquivos na área de trabalho que serão posteriormente cumpridas quando o usuário terminar a operação Drop usando as classes new NSFilePromiseProvider e NSFilePromiseReceiver .

Usando o rastreamento 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 método do elemento que o NSWindow elemento UI está anexado para interceptar os LeftMouseUp eventos e LeftMouseDragged . Para um LeftMouseUp evento, o elemento UI responde normalmente. Para o LeftMouseDragged evento, o evento é passado para o NSWindowmétodo 's PerformWindowDrag para mover a janela na tela.

Chamar o PerformWindowDragNSWindow método da 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 troca de espaço funcionará conforme o esperado.
  • A Barra de Espaços será exibida normalmente.
  • O encaixe 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 do modo de exibição de tabela

O desenvolvedor sempre deve usar a nova NSView versão baseada de 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 as Ações de Linha da Tabela personalizadas sejam anexadas a determinadas linhas na tabela (como passar o dedo 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 plano de fundo vermelho.

Aprimoramentos do Modo de 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 Exibições Modernas.

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 Scroll View.
  • AutomaticallyAdjustsContentInsets - Se true o Scroll View irá lidar automaticamente com o ContentInsets para o desenvolvedor.

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

  • Um indicador de Ordenação como o apresentado na aplicação Correio.
  • Um campo de pesquisa.
  • Um botão Atualizar ou Atualizar.

Layout e localização automáticos 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 de 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.

Implementando a Internacionalização de Base

Ao implementar a Internacionalização Base, o desenvolvedor pode fornecer um único arquivo de 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) inicial do Storyboard que definem a Interface do Usuário do aplicativo, eles serão criados na Internacionalização Base (a linguagem 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 oferecer 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 de 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. O Modo de Exibiçã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 se ajustarem ao 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 o 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 e RightLeft existentes. Leading e Trailing ajustará automaticamente com base na direção dos idiomas.
  • Fixar Modos de Exibição em Modos de Exibição Adjacentes - Isso permite que os Modos de Exibição sejam reposicionados e redimensionados à medida que os Modos de Exibição 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 mudanças de layout constantemente - Durante o desenvolvimento no aplicativo deve ser testado constantemente em diferentes idiomas. Consulte a documentação Testando seu aplicativo internacionalizado da Apple 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 conteúdo mude de tamanho com base no idioma selecionado.

Localizando no Construtor de Interfaces do Xcode

A Apple forneceu vários recursos no Construtor de Interface do Xcode que o desenvolvedor pode usar ao projetar ou editar a interface do usuário de um aplicativo para oferecer 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 um Modo de Exibição Baseado em Texto selecionado (comoNSTextField):

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 diz 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 baseados na 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 tinha que usar 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 modernos do macOS 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 da janela do Mac

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 Aparições do Sistema funcionará automaticamente corretamente para os usuários que ativaram os recursos de Acessibilidade do aplicativo Preferências do Sistema. Como resultado, a Apple sugere que o desenvolvedor sempre use as aparências do sistema em seus aplicativos do 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 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 abstrata e remove o típico "código de cola" 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 cenas:

  • As dependências de dados devem sempre ser em cascata para baixo através da hierarquia.
  • Evite dependências estruturais da interface do usuário codificadas, pois isso limita a flexibilidade da interface do usuário.
  • Use interfaces C# para fornecer dependências de dados genéricas.

O View Controller, 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 View Controller 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 .

Propagando ações

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 geralmente é verdadeiro para Menus e Itens de Menu que vivem em sua própria cena, separados do resto 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 da cadeia de respondentes. 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 que o desenvolvedor aproveite 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 suportar HandOff, onde uma atividade iniciada em um dos dispositivos do usuário poderia ser pega e continuada em outro dispositivo. NSUserActivity funciona da mesma forma no macOS que 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 encerra um aplicativo no macOS e depois o reinicia, o aplicativo será automaticamente retornado ao seu 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 ao 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 o RestorableNSWindow da classe como true.
  • Documentos na nuvem - Antes do macOS Sierra, um aplicativo tinha que aceitar explicitamente trabalhar com documentos no iCloud Drive do usuário. No macOS Sierra, as pastas Desktop 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.