Share via


Creación de aplicaciones modernas de macOS

En este artículo se tratan varias sugerencias, características y técnicas que un desarrollador puede usar para crear una aplicación macOS moderna en Xamarin.Mac.

Compilación de apariencias modernas con vistas modernas

Un aspecto moderno incluirá una apariencia moderna de ventana y barra de herramientas, como la aplicación de ejemplo que se muestra a continuación:

Un ejemplo de una interfaz de usuario moderna de la aplicación Mac

Habilitación de vistas de contenido de tamaño completo

Para lograr este aspecto en una aplicación de Xamarin.Mac, el desarrollador querrá usar una Vista de contenido de tamaño completo, lo que significa que el contenido se extiende en las áreas de herramientas y barra de título y se desenfocará automáticamente por macOS.

Para habilitar esta característica en el código, cree una clase personalizada para el NSWindowController y haga que tenga un aspecto similar al siguiente:

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
    }
}

Esta característica también se puede habilitar en Interface Builder de Xcode seleccionando la ventana y comprobando Vista de contenido de tamaño completo:

Edición del guion gráfico principal en el Interface Builder de Xcode

Al usar una vista de contenido de tamaño completo, es posible que el desarrollador tenga que desplazar el contenido debajo de las áreas de título y barra de herramientas para que el contenido específico (como etiquetas) no se deslice debajo de ellos.

Para complicar este problema, las áreas Título y Barra de herramientas pueden tener un alto dinámico en función de la acción que el usuario está realizando actualmente, la versión de macOS en la que el usuario ha instalado o el hardware Mac en el que se ejecuta la aplicación.

Como resultado, simplemente codificar de forma rígida el desplazamiento al diseñar la interfaz de usuario no funcionará. El desarrollador tendrá que adoptar un enfoque dinámico.

Apple ha incluido la propiedad Observable de clave-valorContentLayoutRect de la clase NSWindow para obtener el área de contenido actual en el código. El desarrollador puede usar este valor para colocar manualmente los elementos necesarios cuando cambia el área de contenido.

La mejor solución consiste en usar clases de diseño automático y tamaño para colocar los elementos de la interfaz de usuario en el código o en Interface Builder.

El código como el ejemplo siguiente se puede usar para colocar elementos de la interfaz de usuario mediante Autolayout y clases de tamaño en el controlador de vista de la aplicación:

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
    }
}

Este código crea almacenamiento para una restricción superior que se aplicará a una etiqueta (ItemTitle) para asegurarse de que no se desliza bajo el área Título y Barra de herramientas:

public NSLayoutConstraint topConstraint { get; set; }

Al invalidar el método UpdateViewConstraints del controlador de vista, el desarrollador puede probar para ver si la restricción necesaria ya se ha compilado y crearla si es necesario.

Si es necesario crear una nueva restricción, se tiene acceso a la propiedad ContentLayoutGuide del control Window al que se debe restringir y convertir en un NSLayoutGuide:

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

Se obtiene acceso a la NSLayoutGuide propiedad TopAnchor de y, si está disponible, se usa para crear una nueva restricción con la cantidad de desplazamiento deseada y la nueva restricción se activa para aplicarla:

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

Habilitación de barras de herramientas simplificadas

Una ventana macOS normal incluye una barra de título estándar en las ejecuciones a lo largo del borde superior de la ventana. Si la ventana también incluye una barra de herramientas, se mostrará en este área barra de título:

Barra de herramientas estándar de Mac

Cuando se usa una barra de herramientas simplificada, el área de título desaparece y la barra de herramientas se mueve hacia arriba a la posición de la barra de título, en línea con los botones Cerrar ventana, Minimizar y Maximizar:

Una barra de herramientas de Mac simplificada

La barra de herramientas simplificada está habilitada reemplazando ViewWillAppear el método del NSViewController y haciendo que parezca similar al siguiente:

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

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

Este efecto se usa normalmente para Aplicaciones de Shoebox (aplicaciones de una ventana), como Mapas, Calendario, Notas y Preferencias del sistema.

Uso de controladores de vistas de accesorios

En función del diseño de la aplicación, es posible que el desarrollador también quiera complementar el área Barra de título con un controlador de vista accesorio que aparece justo debajo del área Título/Barra de herramientas para proporcionar controles contextuales al usuario en función de la actividad en la que se dedica actualmente:

Un controlador de vista accesorio de ejemplo

El controlador de vista accesorio se desenfoque y cambiará automáticamente el tamaño del sistema sin intervención del desarrollador.

Para agregar un controlador de vista accesorio, haga lo siguiente:

  1. Haga doble clic en el archivo Main.storyboard en el Explorador de soluciones para abrirlo para su edición.

  2. Arrastre un Controlador de vista personalizado a la jerarquía de la ventana:

    Adición de un nuevo controlador de vista personalizado

  3. Diseño de la interfaz de usuario de la vista accesorio:

    Diseño de la nueva vista

  4. Exponga la vista accesorio como una Salida y cualquier otras Acciones o Salidas para su interfaz de usuario:

    Agregar el OUtlet necesario

  5. Guarde los cambios.

  6. Vuelva a Visual Studio para Mac para sincronizar los cambios.

Edite el NSWindowController y fíjese en lo siguiente:

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
    }
}

Los puntos clave de este código son donde la vista se establece en la vista personalizada definida en Interface Builder y expuesta como una Salida:

accessoryView.View = AccessoryViewGoBar;

Y el LayoutAttribute que define donde se mostrará el accesorio:

accessoryView.LayoutAttribute = NSLayoutAttribute.Bottom;

Dado que macOS ahora está totalmente localizado, las propiedades Left y RightNSLayoutAttribute han quedado en desuso y deben reemplazarse por Leading y Trailing.

Uso de Ventanas con pestañas

Además, el sistema macOS podría agregar controladores de vista accesorio a la ventana de la aplicación. Por ejemplo, para crear Ventanas con pestañas en las que varias de las ventanas de la aplicación se combinan en una ventana virtual:

Ejemplo de una ventana de Mac con pestañas

Normalmente, el desarrollador tendrá que realizar acciones limitadas para usar Windows con pestañas en sus aplicaciones de Xamarin.Mac, el sistema los controlará automáticamente de la siguiente manera:

  • Windows se tabulará automáticamente cuando se invoque el método OrderFront.
  • Windows se anulará automáticamente cuando se invoque el método OrderOut.
  • En el código, todas las ventanas con pestañas todavía se consideran "visibles", pero el sistema oculta las pestañas no frontales mediante CoreGraphics.
  • Use la TabbingIdentifierpropiedad de NSWindow para agrupar Windows en pestañas.
  • Si es una aplicación basada en NSDocument, varias de estas características se habilitarán automáticamente (como el botón más que se agrega a la barra de pestañas) sin ninguna acción de desarrollador.
  • Las aplicaciones NSDocument no basadas en pueden habilitar el botón "más" en el grupo de pestañas para agregar un nuevo documento reemplazando el GetNewWindowForTabmétodo del NSWindowsController.

Reunir todas las piezas, el AppDelegate de una aplicación que quería usar Windows con pestañas basada en el sistema podría ser similar a la siguiente:

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
    }
}

Donde la propiedad NewDocumentNumber realiza un seguimiento del número de nuevos documentos creados y el método NewDocument crea un nuevo documento y lo muestra.

A continuación, el NSWindowController podría tener el siguiente aspecto:

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
    }
}

Donde la propiedad de App estática proporciona un acceso directo para ir al AppDelegate. El método SetDefaultDocumentTitle establece un nuevo título de documentos en función del número de documentos nuevos creados.

El código siguiente indica a macOS que la aplicación prefiere usar pestañas y proporciona una cadena que permite que Windows de la aplicación se agrupe en pestañas:

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

Y el siguiente método de invalidación agrega un botón más a la barra de pestañas que creará un nuevo documento cuando el usuario haga clic en él:

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

Uso de la animación principal

Core Animation es un motor de representación de gráficos de alta potencia integrado en macOS. Core Animation se ha optimizado para aprovechar las ventajas de la GPU (unidad de procesamiento de gráficos) disponible en el hardware macOS moderno, en lugar de ejecutar las operaciones de gráficos en la CPU, lo que puede ralentizar la máquina.

El CALayer, proporcionado por Core Animation, se puede usar para tareas como desplazamiento rápido y fluido y animaciones. La interfaz de usuario de una aplicación debe estar compuesta de varias subvistas y capas para aprovechar completamente Core Animation.

Un objeto CALayer proporciona varias propiedades que permiten al desarrollador controlar lo que se presenta en pantalla al usuario, como:

  • Content: puede ser un NSImage o CGImage que proporciona el contenido de la capa.
  • BackgroundColor: Establece el color de fondo de la capa como un CGColor
  • BorderWidth: Establece el ancho del borde.
  • BorderColor: Establece el color del borde.

Para usar gráficos principales en la interfaz de usuario de la aplicación, debe usar vistas con Respaldo de capas, lo que Apple sugiere que el desarrollador siempre debe habilitar en la vista de contenido de la ventana. De este modo, todas las vistas secundarias también heredarán automáticamente la copia de seguridad de la capa.

Además, Apple sugiere usar vistas respaldadas por capas en lugar de agregar un nuevo CALayer como una subcapa porque el sistema controlará automáticamente varias de las configuraciones necesarias (como las requeridas por una pantalla de Retina).

La copia de seguridad de capas se puede habilitar estableciendo el WantsLayer de un NSView a true o dentro del Interface Builder de Xcode en el Inspector de efectos de vista comprobando Capa de Core Animation:

El Inspector de efectos de vista

Volver a dibujar vistas con capas

Otro paso importante al usar vistas respaldadas por capas en una aplicación de Xamarin.Mac consiste en establecer el LayerContentsRedrawPolicy de la NSView a OnSetNeedsDisplay en el NSViewController. Por ejemplo:

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

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

Si el desarrollador no establece esta propiedad, la vista se volverá a dibujar cada vez que cambie su origen de marco, lo que no se desea por motivos de rendimiento. Con esta propiedad establecida en OnSetNeedsDisplay el desarrollador tendrá que establecer manualmente NeedsDisplay a true para obligar al contenido a volver a dibujar, sin embargo.

Cuando una vista está marcada como desfasada, el sistema comprueba la propiedad WantsUpdateLayer de la vista. Si devuelve true se llama al método UpdateLayer, de lo contrario, se llama al método DrawRect de la vista para actualizar el contenido de la vista.

Apple tiene las siguientes sugerencias para actualizar un contenido de Vistas cuando sea necesario:

  • Apple prefiere usar UpdateLater sobre DrawRect siempre que sea posible, ya que proporciona un aumento significativo del rendimiento.
  • Use la misma layer.Contents para los elementos de la interfaz de usuario que tienen un aspecto similar.
  • Apple también prefiere que el desarrollador componga su interfaz de usuario mediante vistas estándar como NSTextField, de nuevo siempre que sea posible.

Para usar UpdateLayer, cree una clase personalizada para el NSView y haga que el código tenga un aspecto similar al siguiente:

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
    }
}

Uso de arrastrar y colocar modernos

Para presentar una experiencia moderna de arrastrar y colocar para el usuario, el desarrollador debe adoptar Drag Flocking en las operaciones de arrastrar y colocar de su aplicación. Drag Flocking es donde cada archivo o elemento individual que se arrastra inicialmente aparece como un elemento individual que se agrupa (agrupa en el cursor con un recuento del número de elementos) a medida que el usuario continúa la operación de arrastre.

Si el usuario finaliza la operación de arrastrar, los elementos individuales desbloquearán y volverán a sus ubicaciones originales.

El código de ejemplo siguiente habilita Drag Flocking en una vista 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
    }
}

El efecto de bandada se logra enviando cada elemento que se arrastra al BeginDraggingSession método del NSView como un elemento independiente de una matriz.

Al trabajar con una NSTableView o NSOutlineView, use el método PastboardWriterForRow de la clase NSTableViewDataSource para iniciar la operación de arrastre:

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
    }
}

Esto permite al desarrollador proporcionar una NSDraggingItem individual para cada elemento de la tabla que se está arrastrando en lugar del método anterior WriteRowsWith que escriben todas las filas como un único grupo en el panel de pegado.

Al trabajar con NSCollectionViews, use de nuevo el método PasteboardWriterForItemAt en lugar del método de WriteItemsAt cuando comience arrastrar.

El desarrollador siempre debe evitar colocar archivos grandes en el panel de pegado. Novedad de macOS Sierra, Promesas de archivo permiten al desarrollador colocar referencias a archivos dados en el panel de pegado que se cumplirán más adelante cuando el usuario finalice la operación Drop con las nuevas clases de NSFilePromiseProvider y NSFilePromiseReceiver.

Uso del seguimiento de eventos modernos

Para un elemento de interfaz de usuario (como un NSButton) que se ha agregado a un área título o barra de herramientas, el usuario debe poder hacer clic en el elemento y hacer que active un evento como normal (como mostrar una ventana emergente). Sin embargo, dado que el elemento también está en el área Título o Barra de herramientas, el usuario debería poder hacer clic y arrastrar el elemento para mover también la ventana.

Para ello en el código, cree una clase personalizada para el elemento (como NSButton) e invalide el evento MouseDown de la siguiente manera:

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);
    }
}

Este código usa el TrackEventsMatching método del NSWindow que el elemento de interfaz de usuario está asociado para interceptar los eventos LeftMouseUp y LeftMouseDragged. Para un evento de LeftMouseUp, el elemento de la interfaz de usuario responde como normal. Para el LeftMouseDragged evento, el evento se pasa al NSWindowmétodo del PerformWindowDrag objeto para mover la ventana en pantalla.

Llamar al método PerformWindowDrag de la clase NSWindow proporciona las siguientes ventajas:

  • Permite que la ventana se mueva, incluso si la aplicación está bloqueada (por ejemplo, al procesar un bucle profundo).
  • El cambio de espacio funcionará según lo previsto.
  • La barra de espacios se mostrará como normal.
  • El ajuste de ventanas y la alineación funcionan de forma normal.

Uso de controles modernos de vista de contenedor

macOS Sierra proporciona muchas mejoras modernas a los controles de vista de contenedor existentes disponibles en la versión anterior del sistema operativo.

Mejoras en la vista de tabla

El desarrollador siempre debe usar la nueva NSView versión basada de controles de vista de contenedor, como NSTableView. Por ejemplo:

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
    }
}

Esto permite adjuntar acciones de fila de tabla personalizadas a filas dadas de la tabla (por ejemplo, deslizar el botón derecho para eliminar la fila). Para habilitar este comportamiento, invalide el RowActions método del 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
    }
}

La estática NSTableViewRowAction.FromStyle se usa para crear una nueva acción de fila de tabla de los estilos siguientes:

  • Regular: Realiza una acción no destructiva estándar, como editar el contenido de la fila.
  • Destructive: Realiza una acción destructiva, como eliminar la fila de la tabla. Estas acciones se representarán con un fondo rojo.

Mejoras en la vista de desplazamiento

Cuando se usa directamente una vista de desplazamiento (NSScrollView) o como parte de otro control (como NSTableView), el contenido de la vista de desplazamiento puede deslizarse debajo de las áreas Título y Barra de herramientas de una aplicación de Xamarin.Mac mediante una vista y vistas modernas.

Como resultado, el primer elemento del área de contenido Vista de desplazamiento puede ocultarse parcialmente por el área Título y Barra de herramientas.

Para corregir este problema, Apple ha agregado dos nuevas propiedades a la clase NSScrollView:

  • ContentInsets: Permite al desarrollador proporcionar un objeto NSEdgeInsets que define el desplazamiento que se aplicará a la parte superior de la vista de desplazamiento.
  • AutomaticallyAdjustsContentInsets: Si true la vista de desplazamiento controlará automáticamente el ContentInsets para el desarrollador.

Mediante el uso del ContentInsets desarrollador puede ajustar el inicio de la vista de desplazamiento para permitir la inclusión de accesorios como:

  • Indicador de ordenación como el que se muestra en la aplicación Correo.
  • Un campo de búsqueda.
  • Un botón Actualizar o Actualizar.

Diseño automático y localización en aplicaciones modernas

Apple ha incluido varias tecnologías en Xcode que permiten al desarrollador crear fácilmente una aplicación macOS internacionalizada. Xcode ahora permite al desarrollador separar el texto orientado al usuario del diseño de la interfaz de usuario de la aplicación en sus archivos storyboard y proporciona herramientas para mantener esta separación si cambia la interfaz de usuario.

Para obtener más información, vea Guía de internacionalización y localización de Apple.

Implementación de la internacionalización base

Al implementar la internacionalización base, el desarrollador puede proporcionar un único archivo de Guión gráfico para representar la interfaz de usuario de la aplicación y separar todas las cadenas orientadas al usuario.

Cuando el desarrollador crea el archivo gráfico inicial (o archivos) que definen la interfaz de usuario de la aplicación, se compilarán en la Internacionalización base (el idioma que habla el desarrollador).

A continuación, el desarrollador puede exportar las localización y las cadenas de internacionalización base (en el diseño de la interfaz de usuario de Guión gráfico) que se pueden traducir a varios idiomas.

Más adelante, estas localización se pueden importar y Xcode generará los archivos de cadena específicos del idioma para storyboard.

Implementación del diseño automático para admitir la localización

Dado que las versiones localizadas de valores de cadena pueden tener tamaños y/o direcciones de lectura muy diferentes, el desarrollador debe usar el diseño automático para colocar y ajustar el tamaño de la interfaz de usuario de la aplicación en un archivo Storyboard.

Apple sugiere hacer lo siguiente:

  • Quitar restricciones de ancho fijo: Todas las vistas basadas en texto deben tener permiso para cambiar el tamaño en función de su contenido. La vista de ancho fijo puede recortar su contenido en idiomas específicos.
  • Usar tamaños de contenido intrínsecos: De forma predeterminada, las vistas basadas en texto ajustarán automáticamente su contenido. Para ver la vista basada en texto que no ajuste el tamaño correctamente, selecciónelas en Interface Builder de Xcode y, a continuación, elija Editar>Tamaño para ajustar contenido.
  • Aplicar atributos iniciales y finales: Dado que la dirección del texto puede cambiar en función del idioma del usuario, use los atributos nuevos Leading y Trailing de restricción en lugar de los atributosRight y Left existentes. Leading y Trailing se ajustarán automáticamente en función de la dirección de los idiomas.
  • Anclar vistas a vistas adyacentes: Esto permite que las vistas cambien de posición y cambio de tamaño a medida que cambien las vistas en respuesta al idioma seleccionado.
  • No establecer un tamaño mínimo o máximo de Windows: Permitir que Windows cambie el tamaño a medida que el idioma seleccionado cambie el tamaño de sus áreas de contenido.
  • Cambios de diseño de prueba constantemente: Durante el desarrollo en la aplicación se debe probar constantemente en diferentes lenguajes. Consulte la documentación de Prueba de su aplicación internacionalizada de Apple para obtener más información.
  • Usar NSStackViews para anclar vistas juntas - NSStackViews permite que su contenido cambie de forma predecible y el tamaño del cambio de contenido en función del idioma seleccionado.

Localizar en Interface Builder de Xcode

Apple ha proporcionado varias características en Interface Builder de Xcode que el desarrollador puede usar al diseñar o editar la interfaz de usuario de una aplicación para admitir la localización. La sección Dirección de texto del Inspector de atributos permite al desarrollador proporcionar sugerencias sobre cómo se debe usar y actualizar la dirección en una vista basada en texto seleccionada (por ejemplo, NSTextField):

Las opciones de dirección de texto

Hay tres valores posibles para la Dirección del texto:

  • Natural: El diseño se basa en la cadena asignada al control.
  • De izquierda a derecha: El diseño siempre se ve obligado a izquierda a derecha.
  • De derecha a izquierda: El diseño siempre se ve obligado a derecha a izquierda.

Hay dos valores posibles para el Diseño:

  • De izquierda a derecha: El diseño siempre es de izquierda a derecha.
  • De derecha a izquierda: El diseño siempre está de derecha a izquierda.

Normalmente, estos no deben cambiarse a menos que se requiera una alineación específica.

La propiedad Mirror indica al sistema que voltee propiedades de control específicas (como la posición de la imagen de celda). Tiene tres valores posibles:

  • Automáticamente: La posición cambiará automáticamente en función de la dirección del idioma seleccionado.
  • En la interfaz de derecha a izquierda: La posición solo se cambiará en idiomas basados en derecha a izquierda.
  • Nunca: La posición nunca cambiará.

Si el desarrollador ha especificado Centrar, Justificar o Completa alineación en el contenido de una vista basada en texto, nunca se voltearán en función del idioma seleccionado.

Antes de macOS Sierra, los controles creados en el código no se reflejarían automáticamente. El desarrollador tenía que usar código como el siguiente para controlar la creación de reflejos:

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;
    }
}

Donde se establecen los Alignment y ImagePosition en funciónUserInterfaceLayoutDirection del control.

macOS Sierra agrega varios constructores de conveniencia nuevos (a través del método estático CreateButton) que toman varios parámetros (como Title, Image y Action) y se reflejarán automáticamente correctamente. Por ejemplo:

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

Uso de apariencias del sistema

Las aplicaciones modernas de macOS pueden adoptar una nueva apariencia de interfaz oscura que funcione bien para la creación, edición o presentación de aplicaciones de imagen:

Ejemplo de una interfaz de usuario de ventana de Mac oscura

Para ello, agregue una línea de código antes de que se presente la ventana. Por ejemplo:

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
    }
}

El método estático GetAppearance de la clase NSAppearance se usa para obtener una apariencia con nombre del sistema (en este caso NSAppearance.NameVibrantDark).

Apple tiene las siguientes sugerencias para usar apariencias del sistema:

  • Prefiere colores con nombre sobre valores codificados de forma rígida (como LabelColor y SelectedControlColor).
  • Use el estilo de control estándar del sistema siempre que sea posible.

Una aplicación macOS que usa las apariencias del sistema funcionará automáticamente para los usuarios que han habilitado características de accesibilidad desde la aplicación Preferencias del sistema. Como resultado, Apple sugiere que el desarrollador siempre debe usar Apariencias del sistema en sus aplicaciones macOS.

Diseño de interfaces de usuario con guiones gráficos

Los guiones gráficos permiten al desarrollador no solo diseñar los elementos individuales que componen la interfaz de usuario de una aplicación, sino visualizar y diseñar el flujo de la interfaz de usuario y la jerarquía de los elementos especificados.

Los controladores permiten al desarrollador recopilar elementos en una unidad de composición y Segues abstracta y quitar el típico "código de pegamento" necesario para moverse a lo largo de la jerarquía de vistas:

Edición de la interfaz de usuario en Interface Builder de Xcode

Para obtener más información, vea nuestra documentación Introducción a guiones gráficos.

Hay muchas instancias en las que una escena determinada definida en un guión gráfico requerirá datos de una escena anterior en la jerarquía de vistas. Apple tiene las siguientes sugerencias para pasar información entre escenas:

  • Las dependencias de datos siempre deben estar en cascada hacia abajo a través de la jerarquía.
  • Evite la codificación de dependencias estructurales de la interfaz de usuario, ya que esto limita la flexibilidad de la interfaz de usuario.
  • Use interfaces de C# para proporcionar dependencias de datos genéricas.

El controlador de vista que actúa como origen de Segue, puede invalidar el método PrepareForSegue y realizar cualquier inicialización necesaria (como pasar datos) antes de que se ejecute Segue para mostrar el controlador de vista de destino. Por ejemplo:

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 obtener más información, vea nuestra documentación de Segues.

Propagación de acciones

En función del diseño de la aplicación macOS, puede haber ocasiones en las que el mejor controlador para una acción en un control de interfaz de usuario podría estar en otro lugar de la jerarquía de la interfaz de usuario. Esto suele ser cierto en menús y elementos de menú que residen en su propia escena, separados del resto de la interfaz de usuario de la aplicación.

Para controlar esta situación, el desarrollador puede crear una acción personalizada y pasar la acción a la cadena del respondedor. Para obtener más información, vea nuestra documentación de Trabajar con acciones de ventana personalizadas.

Características modernas de Mac

Apple ha incluido varias características orientadas al usuario en macOS Sierra que permiten al desarrollador sacar el máximo partido a la plataforma Mac, como:

  • NSUserActivity: Esto permite a la aplicación describir la actividad en la que está implicado el usuario. NSUserActivity se creó inicialmente para admitir HandOff, donde una actividad iniciada en uno de los dispositivos del usuario se podía recoger y continuar en otro dispositivo. NSUserActivity funciona igual en macOS que en iOS, por lo que vea nuestra documentación introducción a Handoff de iOS para obtener más detalles.
  • Siri en Mac: Siri usa la actividad actual (NSUserActivity) para proporcionar contexto a los comandos que un usuario puede emitir.
  • Restauración de estado: Cuando el usuario abandona una aplicación en macOS y, después, vuelve a iniciarla, la aplicación se devolverá automáticamente a su estado anterior. El desarrollador puede usar la API de Restauración de estado para codificar y restaurar estados de interfaz de usuario transitorios antes de que se muestre la interfaz de usuario al usuario. Si la aplicación está basada en NSDocument, Restauración de estado se controla automáticamente. Para habilitar Restauración de estado para aplicaciones no basadas enNSDocument, establezca el Restorable de la clase NSWindow en true.
  • Documentos en la nube: Antes de macOS Sierra, una aplicación tenía que optar explícitamente por trabajar con documentos en iCloud Drive del usuario. En macOS Sierra, el Escritoriodel usuario y carpetas de Documentos se pueden sincronizar automáticamente con su iCloud Drive. Como resultado, se pueden eliminar copias locales de documentos para liberar espacio en el equipo del usuario. NSDocument aplicaciones basadas en se controlarán automáticamente este cambio. Todos los demás tipos de aplicaciones deberán usar una NSFileCoordinator para sincronizar la lectura y escritura de documentos.

Resumen

En este artículo se han tratado varias sugerencias, características y técnicas que un desarrollador puede usar para crear una aplicación macOS moderna en Xamarin.Mac.