Data binding and key-value coding in Xamarin.Mac
This article covers using key-value coding and key-value observing to allow for data binding to UI elements in Xcode's Interface Builder.
Overview
When working with C# and .NET in a Xamarin.Mac application, you have access to the same key-value coding and data binding techniques that a developer working in Objective-C and Xcode does. Because Xamarin.Mac integrates directly with Xcode, you can use Xcode's Interface Builder to Data Bind with UI elements instead of writing code.
By using key-value coding and data binding techniques in your Xamarin.Mac application, you can greatly decrease the amount of code that you have to write and maintain to populate and work with UI elements. You also have the benefit of further decoupling your backing data (Data Model) from your front end User Interface (Model-View-Controller), leading to easier to maintain, more flexible application design.
In this article, we'll cover the basics of working with key-value coding and data binding in a Xamarin.Mac application. It is highly suggested that you work through the Hello, Mac article first, specifically the Introduction to Xcode and Interface Builder and Outlets and Actions sections, as it covers key concepts and techniques that we'll be using in this article.
You may want to take a look at the Exposing C# classes / methods to Objective-C section of the Xamarin.Mac Internals document as well, it explains the Register
and Export
attributes used to wire up your C# classes to Objective-C objects and UI elements.
What is key-value coding
Key-value coding (KVC) is a mechanism for accessing an object’s properties indirectly, using keys (specially formatted strings) to identify properties instead of accessing them through instance variables or accessor methods (get/set
). By implementing key-value coding compliant accessors in your Xamarin.Mac application, you gain access to other macOS (formerly known as OS X) features such as key-value observing (KVO), data binding, Core Data, Cocoa bindings, and scriptability.
By using key-value coding and data binding techniques in your Xamarin.Mac application, you can greatly decrease the amount of code that you have to write and maintain to populate and work with UI elements. You also have the benefit of further decoupling your backing data (Data Model) from your front end User Interface (Model-View-Controller), leading to easier to maintain, more flexible application design.
For example, let's look at the following class definition of a KVC compliant object:
using System;
using Foundation;
namespace MacDatabinding
{
[Register("PersonModel")]
public class PersonModel : NSObject
{
private string _name = "";
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
}
}
public PersonModel ()
{
}
}
}
First, the [Register("PersonModel")]
attribute registers the class and exposes it to Objective-C. Then, the class needs to inherit from NSObject
(or a subclass that inherits from NSObject
), this adds several base method that allow to the class to be KVC compliant. Next, the [Export("Name")]
attribute exposes the Name
property and defines the Key value that will later be used to access the property through KVC and KVO techniques.
Finally, to be able to be Key-Value Observed changes to the property's value, the accessor must wrap changes to its value in WillChangeValue
and DidChangeValue
method calls (specifying the same Key as the Export
attribute). For example:
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
}
This step is very important for data binding in Xcode's Interface Builder (as we will see later in this article).
For more information, please see Apple's Key-Value Coding Programming Guide.
Keys and key paths
A Key is a string that identifies a specific property of an object. Typically, a key corresponds to the name of an accessor method in a key-value coding compliant object. Keys must use ASCII encoding, usually begin with a lowercase letter, and may not contain whitespace. So given the example above, Name
would be a Key Value of Name
property of the PersonModel
class. The Key and the name of the property that they expose don't have to be the same, however in most cases they are.
A Key Path is a string of dot separated Keys used to specify a hierarchy of object properties to traverse. The property of the first key in the sequence is relative to the receiver, and each subsequent key is evaluated relative to the value of the previous property. In the same way you use dot notation to traverse an object and its properties in a C# class.
For example, if you expanded the PersonModel
class and added Child
property:
using System;
using Foundation;
namespace MacDatabinding
{
[Register("PersonModel")]
public class PersonModel : NSObject
{
private string _name = "";
private PersonModel _child = new PersonModel();
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
}
}
[Export("Child")]
public PersonModel Child {
get { return _child; }
set {
WillChangeValue ("Child");
_child = value;
DidChangeValue ("Child");
}
}
public PersonModel ()
{
}
}
}
The Key Path to the child's name would be self.Child.Name
or simply Child.Name
(based on how the Key Value was being used).
Getting values using key-value coding
The ValueForKey
method returns the value for the specified Key (as a NSString
), relative to the instance of the KVC class receiving the request. For example, if Person
is an instance of the PersonModel
class defined above:
// Read value
var name = Person.ValueForKey (new NSString("Name"));
This would return the value of the Name
property for that instance of PersonModel
.
Setting values using key-value coding
Similarly, the SetValueForKey
set the value for the specified Key (as a NSString
), relative to the instance of the KVC class receiving the request. So again, using an instance of the PersonModel
class, as shown below:
// Write value
Person.SetValueForKey(new NSString("Jane Doe"), new NSString("Name"));
Would change the value of the Name
property to Jane Doe
.
Observing value changes
Using key-value observing (KVO), you can attach an observer to a specific Key of a KVC compliant class and be notified any time the value for that Key is modified (either using KVC techniques or directly accessing the given property in C# code). For example:
// Watch for the name value changing
Person.AddObserver ("Name", NSKeyValueObservingOptions.New, (sender) => {
// Inform caller of selection change
Console.WriteLine("New Name: {0}", Person.Name)
});
Now, any time the Name
property of the Person
instance of the PersonModel
class is modified, the new value is written out to the console.
For more information, please see Apple's Introduction to Key-Value Observing Programming Guide.
Data binding
The following sections will show how you can use a key-value coding and key-value observing compliant class to bind data to UI elements in Xcode's Interface Builder, instead of reading and writing values using C# code. In this way you separate your Data Model from the views that are used to display them, making the Xamarin.Mac application more flexible and easier to maintain. You also greatly decrease the amount of code that has to be written.
Defining your data model
Before you can Data Bind a UI element in Interface Builder, you must have a KVC/KVO compliant class defined in your Xamarin.Mac application to act as the Data Model for the binding. The Data Model provides all of the data that will be displayed in the User Interface and receives any modifications to the data that the user makes in the UI while running the application.
For example, if you were writing an application that managed a group of employees, you could use the following class to define the Data Model:
using System;
using Foundation;
using AppKit;
namespace MacDatabinding
{
[Register("PersonModel")]
public class PersonModel : NSObject
{
#region Private Variables
private string _name = "";
private string _occupation = "";
private bool _isManager = false;
private NSMutableArray _people = new NSMutableArray();
#endregion
#region Computed Properties
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
}
}
[Export("Occupation")]
public string Occupation {
get { return _occupation; }
set {
WillChangeValue ("Occupation");
_occupation = value;
DidChangeValue ("Occupation");
}
}
[Export("isManager")]
public bool isManager {
get { return _isManager; }
set {
WillChangeValue ("isManager");
WillChangeValue ("Icon");
_isManager = value;
DidChangeValue ("isManager");
DidChangeValue ("Icon");
}
}
[Export("isEmployee")]
public bool isEmployee {
get { return (NumberOfEmployees == 0); }
}
[Export("Icon")]
public NSImage Icon {
get {
if (isManager) {
return NSImage.ImageNamed ("group.png");
} else {
return NSImage.ImageNamed ("user.png");
}
}
}
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
[Export("NumberOfEmployees")]
public nint NumberOfEmployees {
get { return (nint)_people.Count; }
}
#endregion
#region Constructors
public PersonModel ()
{
}
public PersonModel (string name, string occupation)
{
// Initialize
this.Name = name;
this.Occupation = occupation;
}
public PersonModel (string name, string occupation, bool manager)
{
// Initialize
this.Name = name;
this.Occupation = occupation;
this.isManager = manager;
}
#endregion
#region Array Controller Methods
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
#endregion
}
}
Most of the features of this class were covered in the What is key-value coding section above. However, let's look at a few specific elements and some additions that were made to allow this class to act as a Data Model for Array Controllers and Tree Controllers (which we'll be using later to Data bind Tree Views, Outline Views and Collection Views).
First, because an employee might be a manager, we've used a NSArray
(specifically a NSMutableArray
so the values can be modified) to allow the employees that they managed to be attached to them:
private NSMutableArray _people = new NSMutableArray();
...
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
Two things to note here:
- We used a
NSMutableArray
instead of a standard C# array or collection since this is a requirement to Data Bind to AppKit controls such as Table Views, Outline Views and Collections. - We exposed the array of employees by casting it to a
NSArray
for data binding purposes and changed its C# formatted name,People
, to one that data binding expects,personModelArray
in the form {class_name}Array (note that the first character has been made lower case).
Next, we need to add some specially name public methods to support Array Controllers and Tree Controllers:
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
These allow the controllers to request and modify the data that they display. Like the exposed NSArray
above, these have a very specific naming convention (that differs from the typical C# naming conventions):
addObject:
- Adds an object to the array.insertObject:in{class_name}ArrayAtIndex:
- Where{class_name}
is the name of your class. This method inserts an object into the array at a given index.removeObjectFrom{class_name}ArrayAtIndex:
- Where{class_name}
is the name of your class. This method removes the object in the array at a given index.set{class_name}Array:
- Where{class_name}
is the name of your class. This method allows you to replace the existing carry with a new one.
Inside of these methods, we've wrapped changes to the array in WillChangeValue
and DidChangeValue
messages for KVO compliance.
Finally, since the Icon
property relies on the value of the isManager
property, changes to the isManager
property might not be reflected in the Icon
for Data Bound UI elements (during KVO):
[Export("Icon")]
public NSImage Icon {
get {
if (isManager) {
return NSImage.ImageNamed ("group.png");
} else {
return NSImage.ImageNamed ("user.png");
}
}
}
To correct that, we use the following code:
[Export("isManager")]
public bool isManager {
get { return _isManager; }
set {
WillChangeValue ("isManager");
WillChangeValue ("Icon");
_isManager = value;
DidChangeValue ("isManager");
DidChangeValue ("Icon");
}
}
Note that in addition to its own Key, the isManager
accessor is also sending the WillChangeValue
and DidChangeValue
messages for the Icon
Key so it will see the change as well.
We'll be using the PersonModel
Data Model throughout the rest of this article.
Simple data binding
With our Data Model defined, let's look at a simple example of data binding in Xcode's Interface Builder. For example, let's add a form to our Xamarin.Mac application that can be used to edit the PersonModel
that we defined above. We'll add a few Text Fields and a Check Box to display and edit properties of our model.
First, let's add a new View Controller to our Main.storyboard file in Interface Builder and name its class SimpleViewController
:
Next, return to Visual Studio for Mac, edit the SimpleViewController.cs file (that was automatically added to our project) and expose an instance of the PersonModel
that we will be data binding our form to. Add the following code:
private PersonModel _person = new PersonModel();
...
[Export("Person")]
public PersonModel Person {
get {return _person; }
set {
WillChangeValue ("Person");
_person = value;
DidChangeValue ("Person");
}
}
Next when the View is loaded, let's create an instance of our PersonModel
and populate it with this code:
public override void ViewDidLoad ()
{
base.AwakeFromNib ();
// Set a default person
var Craig = new PersonModel ("Craig Dunn", "Documentation Manager");
Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
Person = Craig;
}
Now we need to create our form, double-click the Main.storyboard file to open it for editing in Interface Builder. Layout the form to look something like the following:
To Data Bind the form to the PersonModel
that we exposed via the Person
Key, do the following:
Select the Employee Name Text Field and switch to the Bindings Inspector.
Check the Bind to box and select Simple View Controller from the dropdown. Next enter
self.Person.Name
for the Key Path:Select the Occupation Text Field and check the Bind to box and select Simple View Controller from the dropdown. Next enter
self.Person.Occupation
for the Key Path:Select the Employee is a Manager Checkbox and check the Bind to box and select Simple View Controller from the dropdown. Next enter
self.Person.isManager
for the Key Path:Select the Number of Employees Managed Text Field and check the Bind to box and select Simple View Controller from the dropdown. Next enter
self.Person.NumberOfEmployees
for the Key Path:If the employee is not a manager, we want to hide the Number of Employees Managed Label and Text Field.
Select the Number of Employees Managed Label, expand the Hidden turndown and check the Bind to box and select Simple View Controller from the dropdown. Next enter
self.Person.isManager
for the Key Path:Select
NSNegateBoolean
from the Value Transformer dropdown:This tells data binding that the label will be hidden if the value of the
isManager
property isfalse
.Repeat steps 7 and 8 for the Number of Employees Managed Text Field.
Save your changes and return to Visual Studio for Mac to sync with Xcode.
If you run the application, the values from the Person
property will automatically populate our form:
Any changes that the users makes to the form will be written back to the Person
property in the View Controller. For example, unselecting Employee is a Manager updates the Person
instance of our PersonModel
and the Number of Employees Managed Label and Text Field are hidden automatically (via data binding):
Table view data binding
Now that we have the basics of data binding out of the way, let's look at a more complex data binding task by using an Array Controller and data binding to a Table View. For more information on working with Table Views, please see our Table Views documentation.
First, let's add a new View Controller to our Main.storyboard file in Interface Builder and name its class TableViewController
:
Next, let's edit the TableViewController.cs file (that was automatically added to our project) and expose an array (NSArray
) of PersonModel
classes that we will be data binding our form to. Add the following code:
private NSMutableArray _people = new NSMutableArray();
...
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
...
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
Just like we did on the PersonModel
class above in the Defining your Data Model section, we've exposed four specially named public methods so that the Array Controller and read and write data from our collection of PersonModels
.
Next when the View is loaded, we need to populate our array with this code:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Build list of employees
AddPerson (new PersonModel ("Craig Dunn", "Documentation Manager", true));
AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
AddPerson (new PersonModel ("Larry O'Brien", "API Documentation Manager", true));
AddPerson (new PersonModel ("Mike Norman", "API Documenter"));
}
Now we need to create our Table View, double-click the Main.storyboard file to open it for editing in Interface Builder. Layout the table to look something like the following:
We need to add an Array Controller to provide bound data to our table, do the following:
Drag an Array Controller from the Library Inspector onto the Interface Editor:
Select Array Controller in the Interface Hierarchy and switch to the Attribute Inspector:
Enter
PersonModel
for the Class Name, click the Plus button and add three Keys. Name themName
,Occupation
andisManager
:This tells the Array Controller what it is managing an array of, and which properties it should expose (via Keys).
Switch to the Bindings Inspector and under Content Array select Bind to and Table View Controller. Enter a Model Key Path of
self.personModelArray
:This ties the Array Controller to the array of
PersonModels
that we exposed on our View Controller.
Now we need to bind our Table View to the Array Controller, do the following:
Select the Table View and the Binding Inspector:
Under the Table Contents turndown, select Bind to and Array Controller. Enter
arrangedObjects
for the Controller Key field:Select the Table View Cell under the Employee column. In the Bindings Inspector under the Value turndown, select Bind to and Table Cell View. Enter
objectValue.Name
for the Model Key Path:objectValue
is the currentPersonModel
in the array being managed by the Array Controller.Select the Table View Cell under the Occupation column. In the Bindings Inspector under the Value turndown, select Bind to and Table Cell View. Enter
objectValue.Occupation
for the Model Key Path:Save your changes and return to Visual Studio for Mac to sync with Xcode.
If we run the application, the table will be populated with our array of PersonModels
:
Outline view data binding
data binding against an Outline View is very similar to binding against a Table View. The key difference is that we'll be using a Tree Controller instead of an Array Controller to provide the bound data to the Outline View. For more information on working with Outline Views, please see our Outline Views documentation.
First, let's add a new View Controller to our Main.storyboard file in Interface Builder and name its class OutlineViewController
:
Next, let's edit the OutlineViewController.cs file (that was automatically added to our project) and expose an array (NSArray
) of PersonModel
classes that we will be data binding our form to. Add the following code:
private NSMutableArray _people = new NSMutableArray();
...
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
...
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
Just like we did on the PersonModel
class above in the Defining your Data Model section, we've exposed four specially named public methods so that the Tree Controller and read and write data from our collection of PersonModels
.
Next when the View is loaded, we need to populate our array with this code:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Build list of employees
var Craig = new PersonModel ("Craig Dunn", "Documentation Manager");
Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
AddPerson (Craig);
var Larry = new PersonModel ("Larry O'Brien", "API Documentation Manager");
Larry.AddPerson (new PersonModel ("Mike Norman", "API Documenter"));
AddPerson (Larry);
}
Now we need to create our Outline View, double-click the Main.storyboard file to open it for editing in Interface Builder. Layout the table to look something like the following:
We need to add an Tree Controller to provide bound data to our outline, do the following:
Drag an Tree Controller from the Library Inspector onto the Interface Editor:
Select Tree Controller in the Interface Hierarchy and switch to the Attribute Inspector:
Enter
PersonModel
for the Class Name, click the Plus button and add three Keys. Name themName
,Occupation
andisManager
:This tells the Tree Controller what it is managing an array of, and which properties it should expose (via Keys).
Under the Tree Controller section, enter
personModelArray
for Children, enterNumberOfEmployees
under the Count and enterisEmployee
under Leaf:This tells the Tree Controller where to find any child nodes, how many child nodes there are and if the current node has child nodes.
Switch to the Bindings Inspector and under Content Array select Bind to and File's Owner. Enter a Model Key Path of
self.personModelArray
:This ties the Tree Controller to the array of
PersonModels
that we exposed on our View Controller.
Now we need to bind our Outline View to the Tree Controller, do the following:
Select the Outline View and in the Binding Inspector select :
Under the Outline View Contents turndown, select Bind to and Tree Controller. Enter
arrangedObjects
for the Controller Key field:Select the Table View Cell under the Employee column. In the Bindings Inspector under the Value turndown, select Bind to and Table Cell View. Enter
objectValue.Name
for the Model Key Path:objectValue
is the currentPersonModel
in the array being managed by the Tree Controller.Select the Table View Cell under the Occupation column. In the Bindings Inspector under the Value turndown, select Bind to and Table Cell View. Enter
objectValue.Occupation
for the Model Key Path:Save your changes and return to Visual Studio for Mac to sync with Xcode.
If we run the application, the outline will be populated with our array of PersonModels
:
Collection view data binding
Data binding with a Collection View is very much like binding with a Table View, as an Array Controller is used to provide data for the collection. Since the collection view doesn't have a preset display format, more work is required to provide user interaction feedback and to track user selection.
Important
Due to an issue in Xcode 7 and macOS 10.11 (and greater), Collection Views are unable to be used inside of a Storyboard (.storyboard) files. As a result, you will need to continue to use .xib files to define your Collection Views for your Xamarin.Mac apps. Please see our Collection Views documentation for more information.
Debugging native crashes
Making a mistake in your data bindings can result in a Native Crash in unmanaged code and cause your Xamarin.Mac application to fail completely with a SIGABRT
error:
There are typically four major causes for native crashes during data binding:
- Your Data Model does not inherit from
NSObject
or a subclass ofNSObject
. - You did not expose your property to Objective-C using the
[Export("key-name")]
attribute. - You did not wrap changes to the accessor's value in
WillChangeValue
andDidChangeValue
method calls (specifying the same Key as theExport
attribute). - You have a wrong or mistyped Key in the Binding Inspector in Interface Builder.
Decoding a crash
Let's cause a native crash in our data binding so we can show how to locate and fix it. In Interface Builder, let's change our binding of first Label in the Collection View example from Name
to Title
:
Let's save the change, switch back to Visual Studio for Mac to sync with Xcode and run our application. When the Collection View is displayed, the application will momentarily crash with a SIGABRT
error (as shown in the Application Output in Visual Studio for Mac) since the PersonModel
does not expose a property with the Key Title
:
If we scroll to the very top of the error in the Application Output we can see the key to solving the issue:
This line is telling us that the key Title
doesn't exist on the object that we are binding to. If we change the binding back to Name
in Interface Builder, save, sync, rebuild and run, the application will run as expected without issue.
Summary
This article has taken a detailed look at working with data binding and key-value coding in a Xamarin.Mac application. First, it looked at exposing a C# class to Objective-C by using key-value coding (KVC) and key-value observing (KVO). Next, it showed how to use a KVO compliant class and Data Bind it to UI elements in Xcode's Interface Builder. Finally, it showed complex data binding using Array Controllers and Tree Controllers.
Related Links
- Hello, Mac
- Standard controls
- Table views
- Outline views
- Collection views
- Key-Value Coding Programming Guide
- Introduction to Key-Value Observing Programming Guide
- Introduction to Cocoa Bindings Programming Topics
- Introduction to Cocoa Bindings Reference
- NSCollectionView
- macOS Human Interface Guidelines