Condividi tramite


Creazione di app macOS moderne

Questo articolo illustra diversi suggerimenti, funzionalità e tecniche che uno sviluppatore può usare per creare un'app macOS moderna in Xamarin.Mac.

Costruzione di look moderni con viste moderne

Un aspetto moderno includerà un aspetto moderno della finestra e della barra degli strumenti, ad esempio l'app di esempio illustrata di seguito:

Esempio di interfaccia utente di un'app Mac moderna

Abilitazione delle visualizzazioni contenuto con dimensioni complete

Per ottenere questo risultato in un'app Xamarin.Mac, lo sviluppatore vuole usare una visualizzazione contenuto a dimensioni complete, ovvero il contenuto si estende nelle aree Strumento e Barra del titolo e verrà sfocato automaticamente da macOS.

Per abilitare questa funzionalità nel codice, creare una classe personalizzata per NSWindowController e renderla simile alla seguente:

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

Questa funzionalità può essere abilitata anche in Interface Builder di Xcode selezionando la finestra e selezionando Visualizzazione contenuto con dimensioni complete:

Modifica dello storyboard principale in Interface Builder di Xcode

Quando si usa una visualizzazione contenuto a dimensioni complete, lo sviluppatore potrebbe dover compensare il contenuto sotto le aree del titolo e della barra degli strumenti, in modo che contenuto specifico (ad esempio le etichette) non venga spostato sotto di essi.

Per complicare questo problema, le aree Titolo e Barra degli strumenti possono avere un'altezza dinamica in base all'azione attualmente eseguita dall'utente, alla versione di macOS installata dall'utente e/o all'hardware Mac su cui è in esecuzione l'app.

Di conseguenza, è sufficiente impostare come hardcoded l'offset quando si dispone l'interfaccia utente non funzionerà. Lo sviluppatore dovrà adottare un approccio dinamico.

Apple ha incluso la proprietà Key-Value Observable ContentLayoutRect della NSWindow classe per ottenere l'area di contenuto corrente nel codice. Lo sviluppatore può usare questo valore per posizionare manualmente gli elementi necessari quando viene modificata l'area di contenuto.

La soluzione migliore consiste nell'usare classi layout automatico e dimensioni per posizionare gli elementi dell'interfaccia utente nel codice o in Interface Builder.

Il codice simile all'esempio seguente può essere usato per posizionare gli elementi dell'interfaccia utente usando classi AutoLayout e Size nel controller di visualizzazione dell'app:

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

Questo codice crea l'archiviazione per un vincolo principale che verrà applicato a un'etichetta (ItemTitle) per assicurarsi che non venga inserito nell'area Titolo e Barra degli strumenti:

public NSLayoutConstraint topConstraint { get; set; }

Eseguendo l'override del metodo del UpdateViewConstraints controller di visualizzazione, lo sviluppatore può testare per verificare se il vincolo necessario è già stato compilato e crearlo, se necessario.

Se è necessario compilare un nuovo vincolo, la ContentLayoutGuide proprietà del controllo Window a cui è necessario limitare l'accesso e il cast in un oggetto NSLayoutGuide:

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

La proprietà TopAnchor di è accessibile e, se disponibile, viene usata per compilare un nuovo vincolo con la quantità di NSLayoutGuide offset desiderata e il nuovo vincolo viene reso attivo per applicarlo:

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

Abilitazione delle barre degli strumenti semplificate

Una normale finestra macOS include una barra del titolo standard in esecuzione lungo il bordo superiore della finestra. Se la finestra include anche una barra degli strumenti, verrà visualizzata sotto questa area della barra del titolo:

Barra degli strumenti Mac standard

Quando si usa una barra degli strumenti semplificata, l'area del titolo scompare e la barra degli strumenti si sposta verso l'alto nella posizione della barra del titolo, in linea con i pulsanti Chiudi finestra, Riduci a icona e Ingrandisci:

Barra degli strumenti Mac semplificata

La barra degli strumenti semplificata è abilitata eseguendo l'override del ViewWillAppear metodo di NSViewController e rendendola simile alla seguente:

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

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

Questo effetto viene in genere usato per le applicazioni Shoebox (un'app per finestre), ad esempio Mappe, Calendario, Note e Preferenze di sistema.

Uso dei controller di visualizzazione accessori

A seconda della progettazione dell'app, lo sviluppatore potrebbe anche voler integrare l'area della barra del titolo con un controller di visualizzazione accessorio visualizzato proprio sotto l'area della barra degli strumenti/titolo per fornire controlli sensibili al contesto all'utente in base all'attività in cui sono attualmente coinvolti:

Esempio di controller di visualizzazione accessori

Il controller Di visualizzazione accessorio verrà automaticamente sfocato e ridimensionato dal sistema senza l'intervento dello sviluppatore.

Per aggiungere un controller di visualizzazione accessori, eseguire le operazioni seguenti:

  1. In Esplora soluzioni fare doppio clic sul file Main.storyboard per aprirlo e modificarlo.

  2. Trascinare un controller di visualizzazione personalizzato nella gerarchia della finestra:

    Aggiunta di un nuovo controller di visualizzazione personalizzato

  3. Layout dell'interfaccia utente della visualizzazione accessoria:

    Progettazione della nuova visualizzazione

  4. Esporre la visualizzazione accessoria come outlet e qualsiasi altra azione o outlet per l'interfaccia utente:

    Aggiunta dell'OUtlet richiesto

  5. Salvare le modifiche.

  6. Tornare a Visual Studio per Mac per sincronizzare le modifiche.

Modificare e NSWindowController renderlo simile al seguente:

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

I punti chiave di questo codice sono la posizione in cui la visualizzazione è impostata sulla visualizzazione personalizzata definita in Interface Builder ed esposta come outlet:

accessoryView.View = AccessoryViewGoBar;

E che LayoutAttribute definisce dove verrà visualizzato l'accessorio:

accessoryView.LayoutAttribute = NSLayoutAttribute.Bottom;

Poiché macOS è ora completamente localizzato, le Left proprietà e NSLayoutAttribute Right sono state deprecate e devono essere sostituite con Leading e Trailing.

Uso di finestre a schede

Inoltre, il sistema macOS potrebbe aggiungere controller di visualizzazione accessori alla finestra dell'app. Ad esempio, per creare finestre a schede in cui diverse finestre dell'app vengono unite in una finestra virtuale:

Esempio di finestra mac a schede

In genere, lo sviluppatore dovrà eseguire azioni limitate usando Windows a schede nelle app Xamarin.Mac, il sistema li gestirà automaticamente come segue:

  • Windows verrà automaticamente tabulato quando viene richiamato il OrderFront metodo .
  • Windows verrà automaticamente untabbed quando viene richiamato il OrderOut metodo .
  • Nel codice tutte le finestre a schede sono ancora considerate "visibili", tuttavia tutte le schede non iniziali sono nascoste dal sistema tramite CoreGraphics.
  • Utilizzare la TabbingIdentifier proprietà di NSWindow per raggruppare Le finestre in Schede.
  • Se si tratta di un'app NSDocument basata, diverse di queste funzionalità verranno abilitate automaticamente (ad esempio il pulsante più aggiunto alla barra delle schede) senza alcuna azione dello sviluppatore.
  • NSDocument Le app non basate possono abilitare il pulsante "più" nel gruppo di schede per aggiungere un nuovo documento eseguendo l'override NSWindowsControllerdel GetNewWindowForTab metodo di .

Riunire tutte le parti, di AppDelegate un'app che voleva usare windows a schede basate sul sistema potrebbe essere simile alla seguente:

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

Dove la NewDocumentNumber proprietà tiene traccia del numero di nuovi documenti creati e il NewDocument metodo crea un nuovo documento e lo visualizza.

L'oggetto NSWindowController potrebbe quindi essere simile al seguente:

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

Dove la proprietà statica App fornisce un collegamento per passare a AppDelegate. Il SetDefaultDocumentTitle metodo imposta un nuovo titolo dei documenti in base al numero di nuovi documenti creati.

Il codice seguente indica a macOS che l'app preferisce usare le schede e fornisce una stringa che consente il raggruppamento di Windows dell'app in Schede:

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

Il metodo di override seguente aggiunge un pulsante più alla barra delle schede che creerà un nuovo documento quando si fa clic sull'utente:

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

Uso dell'animazione core

Core Animation è un motore di rendering grafico ad alta potenza integrato in macOS. L'animazione core è stata ottimizzata per sfruttare i vantaggi della GPU (unità di elaborazione grafica) disponibile nell'hardware macOS moderno anziché eseguire le operazioni grafiche sulla CPU, che può rallentare il computer.

L'oggetto CALayer, fornito da Core Animation, può essere usato per attività quali scorrimento rapido e fluido e animazioni. L'interfaccia utente di un'app deve essere costituita da più visualizzazioni secondarie e livelli per sfruttare appieno l'animazione principale.

Un CALayer oggetto fornisce diverse proprietà che consentono allo sviluppatore di controllare ciò che viene presentato sullo schermo all'utente, ad esempio:

  • Content - Può essere un oggetto NSImage o CGImage che fornisce il contenuto del livello.
  • BackgroundColor - Imposta il colore di sfondo del livello come oggetto CGColor
  • BorderWidth - Imposta la larghezza del bordo.
  • BorderColor - Imposta il colore del bordo.

Per usare Core Graphics nell'interfaccia utente dell'app, deve usare le visualizzazioni supportate dal livello, che Apple suggerisce che lo sviluppatore deve sempre abilitare nella visualizzazione contenuto della finestra. In questo modo, anche tutte le visualizzazioni figlio erediteranno automaticamente il backup del livello.

Inoltre, Apple suggerisce l'uso di visualizzazioni supportate dal livello anziché aggiungere un nuovo CALayer sottostrato come sottostrato, perché il sistema gestirà automaticamente diverse delle impostazioni necessarie (ad esempio quelle richieste da un display Retina).

Il backup dei livelli può essere abilitato impostando su WantsLayer NSView true o all'interno di Interface Builder di Xcode sotto Controllo effetti visualizzazione controllando il livello di animazione principale:

Controllo effetti visualizzazione

Ridisegno delle visualizzazioni con livelli

Un altro passaggio importante quando si usano le viste supportate dal livello in un'app Xamarin.Mac è l'impostazione LayerContentsRedrawPolicy NSView OnSetNeedsDisplay di su in NSViewController. Ad esempio:

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

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

Se lo sviluppatore non imposta questa proprietà, la visualizzazione verrà ridisegnata ogni volta che cambia l'origine dei fotogrammi, che non è desiderata per motivi di prestazioni. Con questa proprietà impostata sullo OnSetNeedsDisplay sviluppatore sarà necessario impostare NeedsDisplay manualmente su true per forzare il ridisegno del contenuto.

Quando una visualizzazione è contrassegnata come dirty, il sistema controlla la WantsUpdateLayer proprietà della visualizzazione. Se restituisce true , viene chiamato il UpdateLayer metodo , altrimenti viene chiamato il DrawRect metodo della vista per aggiornare il contenuto di View.

Apple offre i suggerimenti seguenti per l'aggiornamento di un contenuto delle visualizzazioni quando necessario:

  • Apple preferisce usare UpdateLater il più DrawRect possibile in quanto offre un aumento significativo delle prestazioni.
  • Usa la stessa opzione layer.Contents per gli elementi dell'interfaccia utente simili.
  • Apple preferisce anche lo sviluppatore di comporre l'interfaccia utente usando visualizzazioni standard, NSTextFieldad esempio , quando possibile.

Per usare UpdateLayer, creare una classe personalizzata per NSView e rendere il codice simile al seguente:

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 del trascinamento e della selezione moderni

Per presentare un'esperienza di trascinamento e rilascio moderna per l'utente, lo sviluppatore deve adottare drag flocking nelle operazioni di trascinamento della selezione dell'app. Il trascinamento di Flocking è il punto in cui ogni singolo file o elemento trascinato viene inizialmente visualizzato come singolo elemento che esegue il raggruppamento (raggruppato sotto il cursore con un conteggio del numero di elementi) man mano che l'utente continua l'operazione di trascinamento.

Se l'utente termina l'operazione di trascinamento, i singoli elementi ribloccheranno e torneranno alle posizioni originali.

Il codice di esempio seguente abilita Drag Flocking in una visualizzazione personalizzata:

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

L'effetto flocking è stato ottenuto inviando ogni elemento trascinato al BeginDraggingSession metodo di NSView come elemento separato in una matrice.

Quando si utilizza un NSTableView oggetto o NSOutlineView, usare il PastboardWriterForRow metodo della NSTableViewDataSource classe per avviare l'operazione Di trascinamento:

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

In questo modo lo sviluppatore può fornire un singolo elemento NSDraggingItem per ogni elemento della tabella trascinato anziché il metodo WriteRowsWith precedente che scrive tutte le righe come singolo gruppo nella lavagna.

Quando si lavora con NSCollectionViews, usare di nuovo il PasteboardWriterForItemAt metodo anziché il metodo all'inizio WriteItemsAt del trascinamento.

Lo sviluppatore deve sempre evitare di inserire file di grandi dimensioni nella lavagna. Novità di macOS Sierra, Le promesse di file consentono allo sviluppatore di inserire riferimenti ai file specificati nella lavagna che verrà soddisfatta in un secondo momento quando l'utente completa l'operazione Drop usando le nuove NSFilePromiseProvider classi e NSFilePromiseReceiver .

Uso del rilevamento eventi moderno

Per un elemento dell'interfaccia utente ( ad esempio un NSButton) aggiunto a un'area Titolo o Barra degli strumenti, l'utente deve essere in grado di fare clic sull'elemento e di attivare un evento come di consueto, ad esempio la visualizzazione di una finestra popup. Tuttavia, poiché l'elemento si trova anche nell'area Titolo o Barra degli strumenti, l'utente dovrebbe essere in grado di fare clic e trascinare anche l'elemento per spostare la finestra.

A tale scopo nel codice, creare una classe personalizzata per l'elemento (ad esempio NSButton) ed eseguire l'override dell'evento MouseDown come indicato di seguito:

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

Questo codice usa il TrackEventsMatching metodo dell'elemento NSWindow associato all'interfaccia utente per intercettare gli LeftMouseUp eventi e LeftMouseDragged . Per un LeftMouseUp evento, l'elemento dell'interfaccia utente risponde come di consueto. Per l'evento, l'evento LeftMouseDragged viene passato al NSWindowmetodo del PerformWindowDrag per spostare la finestra sullo schermo.

La chiamata al PerformWindowDrag metodo della NSWindow classe offre i vantaggi seguenti:

  • Consente lo spostamento della finestra, anche se l'app è bloccata, ad esempio durante l'elaborazione di un ciclo profondo.
  • Il cambio di spazio funzionerà come previsto.
  • La barra spazi verrà visualizzata normalmente.
  • L'allineamento e l'allineamento della finestra funzionano normalmente.

Uso di controlli di visualizzazione contenitori moderni

macOS Sierra offre molti miglioramenti moderni ai controlli visualizzazione contenitore esistenti disponibili nella versione precedente del sistema operativo.

Miglioramenti alla visualizzazione tabella

Lo sviluppatore deve usare sempre la nuova NSView versione basata dei controlli visualizzazione contenitore, NSTableViewad esempio . Ad esempio:

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

In questo modo è possibile allegare azioni di riga di tabella personalizzate alle righe della tabella, ad esempio scorrendo a destra per eliminare la riga. Per abilitare questo comportamento, eseguire l'override NSTableViewDelegatedel RowActions metodo di :

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

L'oggetto statico NSTableViewRowAction.FromStyle viene usato per creare una nuova azione di riga di tabella degli stili seguenti:

  • Regular - Esegue un'azione standard non distruttiva, ad esempio la modifica del contenuto della riga.
  • Destructive - Esegue un'azione distruttiva, ad esempio eliminare la riga dalla tabella. Il rendering di queste azioni verrà eseguito con uno sfondo rosso.

Miglioramenti alla visualizzazione scorrimento

Quando si usa direttamente una visualizzazione di scorrimento (NSScrollView) o come parte di un altro controllo (ad esempio NSTableView), il contenuto della visualizzazione di scorrimento può scorrere nelle aree Titolo e Barra degli strumenti in un'app Xamarin.Mac usando un aspetto e visualizzazioni moderne.

Di conseguenza, il primo elemento nell'area contenuto Visualizzazione scorrimento può essere parzialmente nascosto dall'area Titolo e Barra degli strumenti.

Per risolvere questo problema, Apple ha aggiunto due nuove proprietà alla NSScrollView classe :

  • ContentInsets - Consente allo sviluppatore di fornire un NSEdgeInsets oggetto che definisce l'offset che verrà applicato alla parte superiore della visualizzazione di scorrimento.
  • AutomaticallyAdjustsContentInsets - Se true la visualizzazione di scorrimento gestirà automaticamente l'oggetto ContentInsets per lo sviluppatore.

Usando lo ContentInsets sviluppatore può regolare l'inizio della visualizzazione di scorrimento per consentire l'inclusione di accessori come:

  • Indicatore di ordinamento simile a quello visualizzato nell'app Posta.
  • Campo di ricerca.
  • Pulsante Aggiorna o Aggiorna.

Layout automatico e localizzazione nelle app moderne

Apple ha incluso diverse tecnologie in Xcode che consentono allo sviluppatore di creare facilmente un'app macOS internazionalizzata. Xcode consente ora allo sviluppatore di separare il testo rivolto all'utente dalla progettazione dell'interfaccia utente dei file storyboard dell'app e fornisce strumenti per mantenere questa separazione se l'interfaccia utente cambia.

Per altre informazioni, vedere la Guida alla localizzazione e all'internazionalizzazione di Apple.

Implementazione dell'internazionalizzazione di base

Implementando l'internazionalizzazione di base, lo sviluppatore può fornire un singolo file Storyboard per rappresentare l'interfaccia utente dell'app e separare tutte le stringhe rivolte all'utente.

Quando lo sviluppatore crea il file storyboard iniziale (o i file) che definiscono l'interfaccia utente dell'app, verranno compilati nell'internazionalizzazione di base (il linguaggio parlato dallo sviluppatore).

Successivamente, lo sviluppatore può esportare le localizzazioni e le stringhe di internazionalizzazione di base (nella progettazione dell'interfaccia utente storyboard) che possono essere tradotte in più lingue.

Successivamente, queste localizzazioni possono essere importate e Xcode genererà i file di stringa specifici della lingua per lo Storyboard.

Implementazione del layout automatico per supportare la localizzazione

Poiché le versioni localizzate dei valori stringa possono avere dimensioni molto diverse e/o direzione di lettura, lo sviluppatore deve usare layout automatico per posizionare e ridimensionare l'interfaccia utente dell'app in un file Storyboard.

Apple consiglia di eseguire le operazioni seguenti:

  • Rimuovi vincoli di larghezza fissa: tutte le visualizzazioni basate su testo devono essere ridimensionate in base al contenuto. Visualizzazione a larghezza fissa può ritagliare il contenuto in lingue specifiche.
  • Usa dimensioni intrinseche del contenuto: per impostazione predefinita, le visualizzazioni basate su testo verranno ridimensionate automaticamente per adattarne il contenuto. Per la visualizzazione basata su testo che non ridimensionano correttamente, selezionarle in Generatore interfacce di Xcode e quindi scegliere Modifica>dimensioni per adattare il contenuto.
  • Applica attributi iniziali e finali: poiché la direzione del testo può cambiare in base alla lingua dell'utente, usare gli attributi nuovi Leading e Trailing vincoli anziché gli attributi e Left esistentiRight. Leading e Trailing regola automaticamente in base alla direzione delle lingue.
  • Aggiungi visualizzazioni a visualizzazioni adiacenti: consente alle visualizzazioni di riposizionare e ridimensionare quando le visualizzazioni vengono modificate in risposta alla lingua selezionata.
  • Non impostare dimensioni minime e/o massime di Windows: consente a Windows di modificare le dimensioni quando la lingua selezionata ridimensiona le aree di contenuto.
  • Modifiche al layout dei test in modo costante: durante lo sviluppo in un'app deve essere testato costantemente in linguaggi diversi. Per altri dettagli, vedere la documentazione relativa al test dell'app internazionalizzata di Apple.
  • Usare NSStackViews per aggiungere visualizzazioni insieme - NSStackViews consente al contenuto di spostarsi e crescere in modi prevedibili e le dimensioni del contenuto cambiano in base alla lingua selezionata.

Localizzazione in Interface Builder di Xcode

Apple ha fornito diverse funzionalità in Interface Builder di Xcode che lo sviluppatore può usare durante la progettazione o la modifica dell'interfaccia utente di un'app per supportare la localizzazione. La sezione Direzione del testo di Controllo attributi consente allo sviluppatore di fornire suggerimenti su come usare e aggiornare la direzione in una visualizzazione basata su testo selezionata, ad esempio NSTextField:

Opzioni Direzione testo

Esistono tre valori possibili per La direzione del testo:

  • Naturale : il layout si basa sulla stringa assegnata al controllo.
  • Da sinistra a destra : il layout viene sempre forzato a sinistra verso destra.
  • Da destra a sinistra : il layout viene sempre forzato da destra a sinistra.

Per Layout sono disponibili due valori possibili:

  • Da sinistra a destra : il layout è sempre da sinistra a destra.
  • Da destra a sinistra : il layout è sempre da destra a sinistra.

In genere, questi non devono essere modificati a meno che non sia necessario un allineamento specifico.

La proprietà Mirror indica al sistema di capovolgere proprietà di controllo specifiche, ad esempio la posizione dell'immagine della cella. Ha tre possibili valori:

  • Automatico: la posizione verrà modificata automaticamente in base alla direzione della lingua selezionata.
  • In Interfaccia da destra a sinistra - La posizione verrà modificata solo in lingue basate su destra a sinistra.
  • Mai - La posizione non cambierà mai.

Se lo sviluppatore ha specificato Center, Justify o Full alignment on the content of a text-based View, these will never be flipped based on language selected.If the developer has specified Center, Justify or Full alignment on the content of a text-based View, these will never flipped based language.

Prima di macOS Sierra, i controlli creati nel codice non verrebbero con mirroring automatico. Lo sviluppatore doveva usare codice simile al seguente per gestire il mirroring:

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

Posizione in Alignment cui e ImagePosition vengono impostati in base all'oggetto UserInterfaceLayoutDirection del controllo .

macOS Sierra aggiunge diversi nuovi costruttori pratici (tramite il metodo statico CreateButton ) che accettano diversi parametri (ad esempio Title, Image e Action) e eseguiranno automaticamente il mirroring. Ad esempio:

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

Uso degli aspetti di sistema

Le app macOS moderne possono adottare un nuovo aspetto dell'interfaccia scura che funziona bene per la creazione di immagini, la modifica o le app di presentazione:

Esempio di interfaccia utente della finestra Mac scura

Questa operazione può essere eseguita aggiungendo una riga di codice prima che venga presentata la finestra. Ad esempio:

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

Il metodo statico GetAppearance della NSAppearance classe viene usato per ottenere un aspetto denominato dal sistema (in questo caso NSAppearance.NameVibrantDark).

Apple offre i suggerimenti seguenti per l'uso degli aspetti del sistema:

  • Preferisce i colori denominati rispetto ai valori hardcoded (ad esempio LabelColor e SelectedControlColor).
  • Usa lo stile di controllo standard di sistema, se possibile.

Un'app macOS che usa l'aspetto del sistema funzionerà automaticamente per gli utenti che hanno abilitato le funzionalità di accessibilità dall'app Preferenze di sistema. Di conseguenza, Apple suggerisce che lo sviluppatore deve usare sempre aspetto di sistema nelle app macOS.

Progettazione di interfacce utente con storyboard

Gli storyboard consentono allo sviluppatore di progettare non solo i singoli elementi che costituiscono l'interfaccia utente di un'app, ma di visualizzare e progettare il flusso dell'interfaccia utente e la gerarchia degli elementi specificati.

I controller consentono allo sviluppatore di raccogliere gli elementi in un'unità di composizione e Segues astratti e rimuovere il tipico "codice glue" necessario per spostarsi in tutta la gerarchia di visualizzazione:

Modifica dell'interfaccia utente in Interface Builder di Xcode

Per altre informazioni, vedere la documentazione introduttiva agli storyboard .

Esistono molte istanze in cui una determinata scena definita in uno storyboard richiederà i dati di una scena precedente nella gerarchia di visualizzazione. Apple offre i suggerimenti seguenti per passare informazioni tra le scene:

  • Le dipendenze dei dati devono sempre scorrere verso il basso attraverso la gerarchia.
  • Evitare l'hardcoding delle dipendenze strutturali dell'interfaccia utente, perché questo limita la flessibilità dell'interfaccia utente.
  • Usare le interfacce C# per fornire dipendenze di dati generici.

Il controller di visualizzazione che funge da origine di Segue può eseguire l'override del PrepareForSegue metodo ed eseguire qualsiasi inizializzazione necessaria (ad esempio il passaggio di dati) prima dell'esecuzione di Segue per visualizzare il controller di visualizzazione di destinazione. Ad esempio:

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

Per altre informazioni, vedere la documentazione di Segues .

Propagazione di azioni

In base alla progettazione dell'app macOS, potrebbe verificarsi un momento in cui il gestore migliore per un'azione su un controllo dell'interfaccia utente potrebbe trovarsi altrove nella gerarchia dell'interfaccia utente. Questo è in genere vero dei menu e delle voci di menu che risiedono nella propria scena, separati dal resto dell'interfaccia utente dell'app.

Per gestire questa situazione, lo sviluppatore può creare un'azione personalizzata e passare l'azione fino alla catena del risponditore. Per altre informazioni, vedere la documentazione Sull'uso delle azioni finestra personalizzate.

Funzionalità Mac moderne

Apple ha incluso diverse funzionalità rivolte agli utenti in macOS Sierra che consentono allo sviluppatore di sfruttare al meglio la piattaforma Mac, ad esempio:

  • NSUserActivity : consente all'app di descrivere l'attività attualmente coinvolta dall'utente. NSUserActivity è stato inizialmente creato per supportare HandOff, in cui un'attività avviata in uno dei dispositivi dell'utente potrebbe essere prelevata e continuata in un altro dispositivo. NSUserActivity funziona allo stesso modo in macOS come in iOS, quindi vedere la documentazione introduttiva a Handoff iOS per altri dettagli.
  • Siri su Mac : Siri usa l'attività corrente (NSUserActivity) per fornire contesto ai comandi che un utente può eseguire.
  • Ripristino dello stato: quando l'utente chiude un'app in macOS e quindi la riavvia in un secondo momento, l'app verrà ripristinata automaticamente allo stato precedente. Lo sviluppatore può usare l'API ripristino dello stato per codificare e ripristinare gli stati temporanei dell'interfaccia utente prima che l'interfaccia utente venga visualizzata all'utente. Se l'app è NSDocument basata, il ripristino dello stato viene gestito automaticamente. Per abilitare il ripristino dello stato per le app nonNSDocument basate, impostare la Restorable proprietà della NSWindow classe su true.
  • Documenti nel cloud : prima di macOS Sierra, un'app doveva acconsentire esplicitamente all'uso dei documenti nell'iCloud Drive dell'utente. In macOS Sierra le cartelle Desktop e Documenti dell'utente possono essere sincronizzate automaticamente con il proprio iCloud Drive dal sistema. Di conseguenza, le copie locali dei documenti possono essere eliminate per liberare spazio nel computer dell'utente. NSDocument le app basate gestiranno automaticamente questa modifica. Tutti gli altri tipi di app dovranno usare per NSFileCoordinator sincronizzare la lettura e la scrittura di documenti.

Riepilogo

Questo articolo ha illustrato diversi suggerimenti, funzionalità e tecniche che uno sviluppatore può usare per creare un'app macOS moderna in Xamarin.Mac.