This article covers several tips, features and techniques a developer can use to build a modern macOS app in Xamarin.Mac.
![]() -->
NSButton not displaying alt image when disabled - cocoa. Mentions of this on the inter webs but never an answer. Is this an known issue with recent Mac OS? That way you can continue to use the Dashboard if you like it, but it won’t be annoying you all the time. To do that, just open System Preferences - Mission Control, and un-check the option for “Show Dashboard as a Space”. Of course, this doesn’t disable the dashboard, it just makes it a lot less noticeable.
Building Modern Looks with Modern Views
A modern look will include a modern Window and Toolbar appearance such as the example app shown below:
Enabling Full Sized Content Views
To achieve this looks in a Xamarin.Mac app, the developer will want to use a Full Size Content View, meaning the content extends under the Tool and Title Bar areas and will be automatically blurred by macOS.
To enable this feature in code, create a custom class for the
NSWindowController and make it look like the following:
![]()
This feature can also be enabled in Xcode's Interface Builder by selecting the Window and checking Full Sized Content View:
When using a Full Size Content View, the developer may need to offset the content beneath the title and tool bar areas so that specific content (such as labels) doesn't slide under them.
To complicate this issue, the Title and Tool Bar areas can have a dynamic height based on the action that the user is currently performing, the version of macOS the user has installed and/or the Mac hardware that the app is running on.
As a result, simply hard coding the offset when laying out the User Interface will not work. The developer will need to take a dynamic approach.
Apple has included the Key-Value Observable
ContentLayoutRect property of the NSWindow class to get the current Content Area in code. The developer can use this value to manually position the required elements when the Content Area changes.
The better solution is to use Auto Layout and Size Classes to position the UI elements in either code or Interface Builder.
Code like the following example can be used to position UI elements using AutoLayout and Size Classes in the app's View Controller:
This code creates storage for a top constraint that will be applied to a Label (
ItemTitle ) to ensure that it doesn't slip under the Title and Tool Bar area:
By overriding the View Controller's
UpdateViewConstraints method, the developer can test to see if the needed constraint has already been built and create it if needed.
If a new constraint needs to be built, the
ContentLayoutGuide property of the Window the control that needs to be constrained is accessed and cast into a NSLayoutGuide :
The TopAnchor property of the
NSLayoutGuide is accessed and if it is available, it is used to build a new constraint with the desired offset amount and the new constraint is made active to apply it:
Enabling Streamlined Toolbars
A normal macOS Window includes a standard Title Bar at runs along to top edge of the Window. If the Window also includes a Tool Bar, it will be displayed under this Title Bar area:
When using a Streamlined Toolbar, the Title Area disappears and the Tool Bar moves up into the Title Bar's position, in-line with the Window Close, Minimize and Maximize buttons:
The Streamlined Toolbar is enabled by overriding the
ViewWillAppear method of the NSViewController and making it look like the following:
![]()
This effect is typically used for Shoebox Applications (one window apps) like Maps, Calendar, Notes and System Preferences.
Using Accessory View Controllers
Depending on the design of the app, the developer might also want to complement the Title Bar area with an Accessory View Controller that appears right below the Title/Tool Bar area to provide context sensitive controls to the user based on the activity they are currently engaged in:
The Accessory View controller will automatically be blurred and resized by the system without developer intervention.
To add an Accessory View Controller, do the following:
Edit the
NSWindowController and make it look like the following:
The key points of this code are where the View is set to the custom View that was defined in Interface Builder and exposed as an Outlet:
And the
LayoutAttribute that defines where the accessory will be displayed:
Because macOS is now fully localized, the
Left and Right NSLayoutAttribute properties have been deprecated and should be replaced with Leading and Trailing .
Using Tabbed Windows
Additionally, the macOS system might add Accessory View Controllers to the app's Window. For example, to create Tabbed Windows where several of the App's Windows are merged into one virtual Window:
Typically, the developer will need to take limited action use Tabbed Windows in their Xamarin.Mac apps, the system will handle them automatically as follows:
Bringing all of the pieces together, the
AppDelegate of an app that wanted to use system based Tabbed Windows could look like the following:
Where the
NewDocumentNumber property keeps track of the number of new documents created and the NewDocument method creates a new document and displays it.
The
NSWindowController could then look like:
Where the static
App property provides a shortcut to get to the AppDelegate . The SetDefaultDocumentTitle method sets a new documents title based on the number of new documents created.
The following code, tells macOS that the app prefers to use tabs and provides a string that allows the app's Windows to be grouped into Tabs:
And the following override method adds a plus button to the Tab Bar that will create a new document when clicked by the user:
Using Core Animation
Core Animation is a high powered graphics rendering engine that is built into macOS. Core Animation has been optimized to take advantage of the GPU (Graphics Processing Unit) available in modern macOS hardware as opposed to running the graphics operations on the CPU, which can slow down the machine.
The
CALayer , provided by Core Animation, can be used for tasks such as fast and fluid scrolling and animations. An app's User Interface should be composed of multiple subviews and layers to fully take advantage of Core Animation.
A
CALayer object provides several properties that allow the developer to control what is presented onscreen to the user such as:
To utilize Core Graphics in the app's UI, it must be using Layer Backed Views, which Apple suggests that the developer should always enable in the Window's Content View. This way, all child views will automatically inherit Layer Backing as well.
Additionally, Apple suggests using Layer Backed Views as opposed to adding a new
CALayer as a sublayer because the system will automatically handle several of the required settings (such as those required by a Retina Display).
Layer Backing can be enabled by setting the
WantsLayer of a NSView to true or inside of Xcode's Interface Builder under the View Effects Inspector by checking Core Animation Layer:
Redrawing Views with Layers
Another important step when using Layer Backed Views in a Xamarin.Mac app is setting the
LayerContentsRedrawPolicy of the NSView to OnSetNeedsDisplay in the NSViewController . For example:
If the developer doesn't set this property, the View will be redrawn whenever its frame origin changes, which is not desired for performance reasons. With this property set to
OnSetNeedsDisplay the developer will manually have to set NeedsDisplay to true to force the content to redraw, however.
When a View is marked as dirty, the system checks the
WantsUpdateLayer property of the View. If it returns true then the UpdateLayer method is called, else the DrawRect method of the View is called to update the View's contents.
Apple has the following suggestions for updating a Views contents when required:
To use
UpdateLayer , create a custom class for the NSView and make the code look like the following:
Using Modern Drag and Drop
To present a modern Drag and Drop experience for the user, the developer should adopt Drag Flocking in their app's Drag and Drop operations. Drag Flocking is where each individual file or item being dragged initially appears as an individual element that flocks (group together under the cursor with a count of the number of items) as the user continues the drag operation.
If the user terminates the Drag operation, the individual elements will Unflock and return to their original locations.
The following example code enables Drag Flocking on a custom View:
The flocking effect was achieved by sending each item being dragged to the
BeginDraggingSession method of the NSView as a separate element in an array.
When working with a
NSTableView or NSOutlineView , use the PastboardWriterForRow method of the NSTableViewDataSource class to start the Dragging operation:
This allows the developer to provide an individual
NSDraggingItem for every item in the table that is being dragged as opposed to the older method WriteRowsWith that write all of the rows as a single group to the pasteboard.
When working with
NSCollectionViews , again use the PasteboardWriterForItemAt method as opposed to the WriteItemsAt method when Dragging begins.
The developer should always avoid putting large files on the pasteboard. New to macOS Sierra, File Promises allow the developer to place references to given files on the pasteboard that will later be fulfilled when the user finishes the Drop operation using the new
NSFilePromiseProvider and NSFilePromiseReceiver classes.
Using Modern Event Tracking
For a User Interface element (such as a
NSButton ) that has been added to a Title or Tool Bar area, the user should be able to click the element and have it fire an event as normal (such as displaying a popup window). However, since the item is also in the Title or Tool Bar area, the user should be able to click and drag the element to move the window as well.
To accomplish this in code, create a custom class for the element (such as
NSButton ) and override the MouseDown event as follows:
This code uses the
TrackEventsMatching method of the NSWindow that the UI element is attached to intercept the LeftMouseUp and LeftMouseDragged events. For a LeftMouseUp event, the UI element responds as normal. For the LeftMouseDragged event, the event is passed to the NSWindow 's PerformWindowDrag method to move the window on screen.
Calling the
PerformWindowDrag method of the NSWindow class provides the following benefits:
Using Modern Container View Controls
macOS Sierra provides many modern improvements to the existing Container View Controls available in previous version of the OS.
Table View Enhancements
The developer should always use the new
NSView based version of Container View Controls such as NSTableView . For example:
This allows for custom Table Row Actions to be attached to given rows in the table (such as swiping right to delete the row). To enable this behavior, override the
RowActions method of the NSTableViewDelegate :
The static
NSTableViewRowAction.FromStyle is used to create a new Table Row Action of the following styles:
Scroll View Enhancements
When using a Scroll View (
NSScrollView ) directly, or as part of another control (such as NSTableView ), the contents of the Scroll View can slide under the Title and Tool Bar areas in a Xamarin.Mac app using a Modern Look and Views.
As a result, the first item in the Scroll View content area can be partially obscured by the Title and Tool Bar area.
To correct this issue, Apple has added two new properties to the
NSScrollView class:
By using the
ContentInsets the developer can adjust the start of the Scroll View to allow for the inclusion of accessories such as:
Auto Layout and Localization in Modern Apps
Apple has included several technologies in Xcode that allow the developer to easily create an internationalized macOS app. Xcode now allows the developer to separate user-facing text from the app's User Interface design in its Storyboard files and provides tools to maintain this separation if the UI changes.
For more information, please see Apple's Internationalization and Localization Guide.
Implementing Base Internationalization
By implementing Base Internationalization, the developer can provide a single Storyboard file to represent the app's UI and separate out all of the user-facing strings.
When the developer is creating the initial Storyboard file (or files) that define the app's User Interface, they will be built in the Base Internationalization (the language that the developer speaks).
Next, the developer can export localizations and the Base Internationalization strings (in the Storyboard UI design) that can be translated into multiple languages.
Later, these localizations can be imported and Xcode will generate the language-specific string files for the Storyboard.
Implementing Auto Layout to Support Localization
Because localized versions of string values can have vastly different sizes and/or reading direction, the developer should use Auto Layout to position and size the app's User Interface in a Storyboard file.
Apple suggest doing the following:
Localizing in Xcode's Interface Builder
Apple has provided several features in Xcode's Interface Builder that the developer can use when designing or editing an app's UI to support localization. The Text Direction section of the Attribute Inspector allows the developer to provide hints on how direction should be used and updated on a select Text-Based View (such as
NSTextField ):
There are three possible values for the Text Direction:
There are two possible values for the Layout:
Typically these should not be changed unless a specific alignment is required.
The Mirror property tells the system to flip specific control properties (such as the Cell Image Position). It has three possible values:
If the developer has specified Center, Justify or Full alignment on the content of a text-based View, these will never be flipped based on language selected.
Prior to macOS Sierra, controls created in code would not be mirrored automatically. The developer had to use code like the following to handle mirroring:
Where the
Alignment and ImagePosition are being set based on the UserInterfaceLayoutDirection of the control.
macOS Sierra adds several new convenience constructors (via the static
CreateButton method) that take several parameters (such as Title, Image and Action) and will automatically mirror correctly. For example:
Using System Appearances
Modern macOS apps can adopt a new Dark Interface Appearance that works well for image creation, editing or presentation apps:
This can be done by adding one line of code before the Window is presented. For example:
The static
GetAppearance method of the NSAppearance class is used to get a named appearance from the system (in this case NSAppearance.NameVibrantDark ).
Apple has the following suggestions for using System Appearances:
A macOS app that uses the System Appearances will automatically work correctly for users that have enabled Accessibility features from the System Preferences app. As a result, Apple suggests that the developer should always use System Appearances in their macOS apps.
Designing UIs with Storyboards
Storyboards allow the developer to not only design the individual elements that make up an app's User Interface, but to visualize and design the UI flow and the hierarchy of the given elements.
Controllers allow the developer to collect elements into a unit of composition and Segues abstract and remove the typical 'glue code' required to move throughout the View Hierarchy:
For more information, please see our Introduction to Storyboards documentation.
There are many instances where a given scene defined in a storyboard will require data from a previous scene in the View Hierarchy. Apple has the following suggestions for passing information between scenes:
The View Controller that is acting as the source of the Segue, can override the
PrepareForSegue method and do any initialization required (such as passing data) before the Segue is executed to display the target View Controller. For example:
For more information, please see our Segues documentation.
Propagating Actions
Based on the design of the macOS app, there might be times when the best handler for an Action on a UI control might be in elsewhere in the UI Hierarchy. This is typically true of Menus and Menu Items that live in their own scene, separate from the rest of the app's UI.
To handle this situation, the developer can create a Custom Action and pass the Action up the responder chain. For more information please see our Working with Custom Window Actions documentation.
Modern Mac Features
Apple has included several user-facing features in macOS Sierra that allow the developer to make the most of the Mac platform, such as:
Summary
This article has covered several tips, features and techniques a developer can use to build a modern macOS app in Xamarin.Mac.
Related Links![]() Comments are closed.
|
AuthorWrite something about yourself. No need to be fancy, just an overview. Archives
January 2023
Categories |