Aaron [email protected]@abock
Writing Native Mac Apps in C# with Xamarin.Mac
2.6 Billion Devices
Mac OS XThe world’s most advanced desktop operating system
C#
It’s all about sharing code
Shared C# CodeXamarin.Mobile
Business Logic
Cloud Access
Database Access
DesktopDesktop MobileMobileMobile
Windows iOS Windows Phone Android
It’s all about sharing code
Shared C# CodeXamarin.Mobile
Business Logic
Cloud Access
Database Access
DesktopDesktop MobileMobileMobile
Windows iOS Windows Phone AndroidMac
TouchDraw runs on iPad, Android, and Mac, achieving over 70% code reuse across
the platforms.
39%
61%
TouchDrawfor iPad
24%
76%
TouchDrawfor Mac
28%
72%
TouchDrawfor Android
Shared CodePlatform Specific
Xamarin.Mac at a glance
• Write native Mac applications in C#• Access Mac OS X APIs for rich integration• Leverage the full power of C# and .NET• Integrated with the Xamarin experience• Deploy directly to the Mac AppStore
Xamarin.Mac at a glance
Xamarin.Mac Frameworks
Xamarin Toolsand SDK
• Binder • Bundler • Linker • Packager Mono Runtime
.NET Base Class Libraries System Libraries
Darwin OS
Cocoa Frameworks
Xcode(UI designer)
Xamarin Studio IDE
Xamarin.Mac at a glance
Xamarin.Mac Frameworks
Xamarin Toolsand SDK
• Binder • Bundler • Linker • Packager Mono Runtime
.NET Base Class Libraries System Libraries
Darwin OS
Cocoa Frameworks
Xcode(UI designer)
Xamarin Studio IDE
Xamarin.Mac Frameworks
GraphicsGraphicsCoreGraphics ImageKitCoreImage ImageIOCoreText OpenGLCoreVideo PDFKit
User InterfaceUser InterfaceAppKit QuickLookCoreAnima?on SceneKitQCComposer WebKit
Audio and VideoAudio and VideoAVFounda?on CoreMidiAudioToolbox CoreMediaAudioUnit OpenAL
System ServicesSystem ServicesAddressBook CoreWLANBluetooth Scrip?ngBridgeCoreLoca?on StoreKitCoreServices
InfrastructureInfrastructureCoreData Founda?onCoreFounda?on ObjCRun?meDarwin Security
Many are shared with Xamarin.iOS
GraphicsGraphicsCoreGraphics ImageKitCoreImage ImageIOCoreText OpenGLCoreVideo PDFKit
User InterfaceUser InterfaceAppKit QuickLookCoreAnima?on SceneKitQCComposer WebKit
Audio and VideoAudio and VideoAVFounda?on CoreMidiAudioToolbox CoreMediaAudioUnit OpenAL
System ServicesSystem ServicesAddressBook CoreWLANBluetooth Scrip?ngBridgeCoreLoca?on StoreKitCoreServices
InfrastructureInfrastructureCoreData Founda?onCoreFounda?on ObjCRun?meDarwin Security
The Basics
GraphicsGraphicsCoreGraphics ImageKitCoreImage ImageIOCoreText OpenGLCoreVideo PDFKit
User InterfaceUser InterfaceAppKit QuickLookCoreAnima?on SceneKitQCComposer WebKit
Audio and VideoAudio and VideoAVFounda?on CoreMidiAudioToolbox CoreMediaAudioUnit OpenAL
System ServicesSystem ServicesAddressBook CoreWLANBluetooth Scrip?ngBridgeCoreLoca?on StoreKitCoreServices
InfrastructureInfrastructureCoreData Founda5onCoreFounda?on ObjCRun?meDarwin Security
How does Xamarin.Mac work?
How does Xamarin.Mac work?
• OS X APIs are projected into C#1:1 mapping for full platform coverage
How does Xamarin.Mac work?
• OS X APIs are projected into C#1:1 mapping for full platform coverage
• 80% are Objective-CFull object system is mappedSubclassing and overriding supported
How does Xamarin.Mac work?
• OS X APIs are projected into C#1:1 mapping for full platform coverage
• 80% are Objective-CFull object system is mappedSubclassing and overriding supported
• 20% are CExposed as C# structs/classes/methodsNo support for subclassing or overriding
How does Xamarin.Mac work?
• OS X APIs are projected into C#1:1 mapping for full platform coverage
Check out “Binding Objective-C Libraries” for a
deep dive at 1:30!
• 80% are Objective-CFull object system is mappedSubclassing and overriding supported
• 20% are CExposed as C# structs/classes/methodsNo support for subclassing or overriding
DIVE IN
Anatomy of a Xamarin.Mac application
Anatomy of a Xamarin.Mac application
Info.plistApplication metadata, used by Mac OS (app name, version, publisher, etc.)
Anatomy of a Xamarin.Mac application
Info.plistApplication metadata, used by Mac OS (app name, version, publisher, etc.)
Resources FolderPlace any resources (images, icons, static data) to be bundled with the app
Anatomy of a Xamarin.Mac application
Application DelegateCalled with application events such as FinishedLaunching or OpenFiles
Info.plistApplication metadata, used by Mac OS (app name, version, publisher, etc.)
Resources FolderPlace any resources (images, icons, static data) to be bundled with the app
Anatomy of a Xamarin.Mac application
Application DelegateCalled with application events such as FinishedLaunching or OpenFiles
Info.plistApplication metadata, used by Mac OS (app name, version, publisher, etc.)
Application Main MenuInterface definition pre-populated with many common defaults
Resources FolderPlace any resources (images, icons, static data) to be bundled with the app
Anatomy of a Xamarin.Mac application
Application DelegateCalled with application events such as FinishedLaunching or OpenFiles
Info.plistApplication metadata, used by Mac OS (app name, version, publisher, etc.)
Application Main MenuInterface definition pre-populated with many common defaults
Main Window User InterfaceThe primary window definition editable in the Xcode UI Builder
Resources FolderPlace any resources (images, icons, static data) to be bundled with the app
Anatomy of a Xamarin.Mac application
Application DelegateCalled with application events such as FinishedLaunching or OpenFiles
Info.plistApplication metadata, used by Mac OS (app name, version, publisher, etc.)
Application Main MenuInterface definition pre-populated with many common defaults
Main Window User InterfaceThe primary window definition editable in the Xcode UI Builder
Main Window ImplementationClass for implementing the features of your Main Window
Resources FolderPlace any resources (images, icons, static data) to be bundled with the app
First Run
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Building out our NSWindowpublic override void WindowControllerDidLoadNib (NSWindowController
windowController){ base.WindowControllerDidLoadNib (windowController);
var button = new NSButton (new RectangleF (10, 10, 200, 32)) { Title = "Hello Mac" BezelStyle = NSBezelStyle.Rounded }; button.Activated += (sender, e) => new NSAlert { MessageText = "You clicked me!" } .BeginSheet (windowController.Window); windowController.Window.ContentView.AddSubview (button);}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Building out our NSWindowpublic override void WindowControllerDidLoadNib (NSWindowController
windowController){ base.WindowControllerDidLoadNib (windowController);
var button = new NSButton (new RectangleF (10, 10, 200, 32)) { Title = "Hello Mac" BezelStyle = NSBezelStyle.Rounded }; button.Activated += (sender, e) => new NSAlert { MessageText = "You clicked me!" } .BeginSheet (windowController.Window); windowController.Window.ContentView.AddSubview (button);}
Call the base class’ version
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Building out our NSWindowpublic override void WindowControllerDidLoadNib (NSWindowController
windowController){ base.WindowControllerDidLoadNib (windowController);
var button = new NSButton (new RectangleF (10, 10, 200, 32)) { Title = "Hello Mac" BezelStyle = NSBezelStyle.Rounded }; button.Activated += (sender, e) => new NSAlert { MessageText = "You clicked me!" } .BeginSheet (windowController.Window); windowController.Window.ContentView.AddSubview (button);}
Call the base class’ version
Allocate and configure a button
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Building out our NSWindowpublic override void WindowControllerDidLoadNib (NSWindowController
windowController){ base.WindowControllerDidLoadNib (windowController);
var button = new NSButton (new RectangleF (10, 10, 200, 32)) { Title = "Hello Mac" BezelStyle = NSBezelStyle.Rounded }; button.Activated += (sender, e) => new NSAlert { MessageText = "You clicked me!" } .BeginSheet (windowController.Window); windowController.Window.ContentView.AddSubview (button);}
Call the base class’ version
Allocate and configure a button
Connect a lambda to run when activated (clicked)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Building out our NSWindowpublic override void WindowControllerDidLoadNib (NSWindowController
windowController){ base.WindowControllerDidLoadNib (windowController);
var button = new NSButton (new RectangleF (10, 10, 200, 32)) { Title = "Hello Mac" BezelStyle = NSBezelStyle.Rounded }; button.Activated += (sender, e) => new NSAlert { MessageText = "You clicked me!" } .BeginSheet (windowController.Window); windowController.Window.ContentView.AddSubview (button);}
Call the base class’ version
Allocate and configure a button
Connect a lambda to run when activated (clicked)
Add the button to the document window
Hello Mac!
Hello Mac!
Hello Mac!
Designing the UI: Xcode Interface Builder
Double click .xib files to open in
Xcode
Designing the UI: Xcode Interface Builder
Designing the UI: Xcode Interface Builder
InspectorsApplies to the selected control:• Properties• Sizing• Connections
Designing the UI: Xcode Interface Builder
InspectorsApplies to the selected control:• Properties• Sizing• Connections
Object LibraryDrag controls to your window
Connecting the UI
• UI built in Interface Builder connects to code in two waysActionsOutlets
Connecting the UI: Actions
• Methods defined in C#; invoked directly by controls• Partial methods and always have the same signature
Connecting the UI: Actions
• Methods defined in C#; invoked directly by controls• Partial methods and always have the same signature
-‐ (IBAction)IncreaseButtonActivated:(id)sender;
partial void IncreaseButtonActivated (NSObject sender);
Objective-C
C#
Connecting the UI: Outlets
• Surface controls from Interface Builder to C# properties• The property type is that of the connected control
Connecting the UI: Outlets
• Surface controls from Interface Builder to C# properties• The property type is that of the connected control
@property (assign) IBOutlet NSButton *IncreaseButton;
[Outlet] NSButton IncreaseButton { get; set; }
Objective-C
C#
Connecting the UI
Switch to the Assistant editor (aka Butler mode)
Connecting the UI
Switch to the Assistant editor (aka Butler mode)
Connecting the UI
Control-Drag from the selected control into the @interface inside the header file to create an outlet or action
Connecting the UI
Control-Drag from the selected control into the @interface inside the header file to create an outlet or action
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Connecting the UI@interface MyDocument : NSDocument {
NSTextField *CounterLabel;}
@property (assign) IBOutlet NSTextField *CounterLabel;-‐ (IBAction)IncreaseButtonActivated:(id)sender;
@end
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Behind the Scenes in Xamarin.Mac[Register ("MyDocument")]
partial class MyDocument{ [Outlet] TextField CounterLabel { get; set; }
[Action ("IncreaseButtonActivated:")] partial void IncreaseButtonActivated (NSObject sender);}
Events and Callbacks
• In C#, events are a very common communication pattern:
var window = new NSWindow ();window.DidBecomeKey += HandleDidBecomeKey;window.DidResignKey += HandleDidResignKey;
NSWindowHandleDidBecomeKey
HandleDidResignKey
Events and Callbacks
• With Apple’s pattern however, objects send interesting events, or messages, to a delegate:
new NSWindow { Delegate = new MyWindowDelegate ()};
NSWindow MyWindowDelegate
class MyWindowDelegate : NSWindowDelegate{ override void DidBecomeKey (...) override void DidResignKey (...)}
Events and Callbacks
Events and Callbacks
• Xamarin.Mac supports both modelsYou are free to choose on a per-instance basis
Events and Callbacks
• Xamarin.Mac supports both modelsYou are free to choose on a per-instance basis
• The Apple Delegate pattern maps to C# eventsInternally we create the Delegate class and map it to subscribed event handlers
Events and Callbacks
• Xamarin.Mac supports both modelsYou are free to choose on a per-instance basis
• The Apple Delegate pattern maps to C# eventsInternally we create the Delegate class and map it to subscribed event handlers
• One pattern replaces the other
AppKit
AppKit
• Pervasive use of Model-View-ControllerAll logic goes in the controller classUnless writing a custom control (NSView)Controller orchestrates the work of views
AppKit
• Pervasive use of Model-View-ControllerAll logic goes in the controller classUnless writing a custom control (NSView)Controller orchestrates the work of views
• Goes well beyond the basics of UIHigh-level NSDocument does the heavy li!ingFull menus, saving, loading, window restoration, multi-window support: all for free
NSDocument
override void WindowControllerDidLoadNib (NSWindowController windowController)
NSDocument
override void WindowControllerDidLoadNib (NSWindowController windowController)
override bool ReadFromUrl (NSUrl url, string typeName, out NSError outError)
NSDocument
override void WindowControllerDidLoadNib (NSWindowController windowController)
override bool ReadFromUrl (NSUrl url, string typeName, out NSError outError)
override bool SaveToUrl (NSUrl url, string typeName, NSSaveOperationType saveOperation, out NSError outError)
NSDocument
Structure of the UI
• NSWindowTop-level window
• NSWindow.ContentViewAn NSView that contains controls for the application
• NSView is a class from which most controls deriveCan contain any number of children (NSView.Subviews)
Structure of the UI
• Some controls have many faces and rolesNSTextField implements both “label” and “entry” rolesInstead of being discrete controls, properties are modified to fit the desired roleXcode provides pre-set properties for controls
NSView: a powerful container
• Can contain (and perform layout of) subviews• Handles events• Paints itself• Can be backed by a CALayer
CALayers are GPU accelerated• Its properties can be animated
SHIP IT
Ship Your App: by yourself
• The app is unrestricted and has full access to the system• Shipped as a “bundle”
Typically a zipped up .app folderFully self-containedXamarin Studio generates this by default
Gatekeeper
• Apple recently turned on Gatekeeper which prevents unsigned apps from running by default
☹
Gatekeeper
• Apple recently turned on Gatekeeper which prevents unsigned apps from running by default
☹
BADText
Gatekeeper
• Code sign your app for the best user experience• Xamarin Studio supports this for Xamarin.Mac
☺
Gatekeeper
• Code sign your app for the best user experience• Xamarin Studio supports this for Xamarin.Mac
Project Options → Mac OS X Packaging
☺
Ship Your App: in the App Store
Ship Your App: in the App Store
• Must apply for the Mac developer program through Apple• Must sign app• App is submitted for review
Ship Your App: in the App Store
• Must apply for the Mac developer program through Apple• Must sign app• App is submitted for review
• The app will be sandboxed
Ship Your App: in the App Store
• Must apply for the Mac developer program through Apple• Must sign app• App is submitted for review
• The app will be sandboxed
• Xamarin Studio signs, packages, and submits the app• Shipping to the App Store is the best end-user experience
Mac OS X Sandbox
• Kernel-enforced sandbox• Limits access to the system
Limitations on filesystem accessUse of special open/save panelsLimits access to some services and APIs
Mac OS X Sandbox
• Edit Sandbox entitlements in Info.plist in Xamarin Studio
?
Xamarin makes for Happy Devices
Learn more:xamarin.com/mac
docs.xamarin.com/mac
github.com/xamarin/mac-samples
Aaron Bockover@abock