UPDATE
GUI Building Video Complete!
Watch it here
Creating a “Book List” Project
In this tutorial we are going to create an application that displays a list of books with title, author and read or unread status. We will be using mostly Objective-C methods written in AppleScriptObjC. Here is a look at the finished project.
Final Project
Assumptions
a) You have read AppleScriptObjC Release Notes.
b) You have read AppleScriptObjC Part 1
About this Tutorial
I am going to list out the entire code upfront and go through it a line at a time. You can watch a short video, a little over eight minutes, that walks you through creating the GUI, making all the connections, setting the bindings, adding the images to the project and adding the image cell to the table view.
On to the Code!
(*
We will be using both NSMutableArray and NSImage classes
so we create a reference to them here.
Any Objective-C class you wish to access in your code
must be set up as a property.
*)
property NSMutableArray : class "NSMutableArray"
property NSImage : class "NSImage"
script PartTwoAppDelegate
-- Inheritance
-- Our class is a sub-class of NSObject
property parent : class "NSObject"
-- IBOutlets
-- Interface Builder considers an outlet as any
-- property with "missing value" as its initial value
property aTableView : missing value
property aWindow : missing value
property bookTitleField : missing value
property authorField : missing value
property statusField : missing value
-- Bindings
(*
We connect these in the bindings inspector
to the corresponding text field values.
Note:
You cannot connect bindings directly
to the NSTextFields above so we create
new properties to hold the "value" of
the NSTextFields.
*)
property theAuthor : ""
property bookTitle : ""
property theStatus : ""
-- Other properties
(*
theDataSource will be an NSMutableArray
We set this up in the awakeFromNib handler
*)
property theDataSource : {}
-- IBActions (button clicks)
-- Interface Builder considers an action as any
-- single parameter method ending with an underscore
on addData_(sender)
(*
Here we are creating a normal AppleScript record but we are using
bindings to gather the information from text fields and
radio buttons.
Observation:
I don't know if this is a bug or not.
When you launch the application the radio
button for value "Read" is checked but its
value is "". Only after you click "Unread"
and then "Read" again does it have the value
of 0.
This poses a problem when we want to sort by
theStatus field and one or more of the items
contains an empty string instead of an integer.
We fix that by setting the value here before
adding it to our array.
*)
if theStatus is "" then
set theStatus to 0
end if
set newData to {bookTitle:bookTitle, theAuthor:theAuthor, theStatus:theStatus}
(*
This next method is calling the addObject_() method of NSMutableArray.
We are adding the newData record to the NSMutableArray "theDataSource"
as an array of dictionaries.
*)
theDataSource's addObject_(newData)
(*
Here we call the reloadData method of NSTableView
*)
aTableView's reloadData()
-- Clear fields
(*
Clear the text fields for next data entry
*)
bookTitleField's setStringValue_("")
authorField's setStringValue_("")
(*
If you comment out the following line, the author field
will have focus after entering data. This is not the
expected behavior.
In IB I set bookTitleField as first
responder by control dragging from the
window to the text field and choosing
"initialFirstResponder" but this method is
required here if we want this behavior
to continue.
Note:
This function requires a pointer to the
window and ours is "aWindow."
*)
aWindow's makeFirstResponder_(bookTitleField)
end addData_
##################################################
# TableView
(*
Below are three NSTableView methods of which two are mandatory.
Mandatory methods:
These can be found in NSTableViewDataSource.
tableView_objectValueForTableColumn_row_
numberOfRowsInTableView_
Optional method:
This is found in NSTableViewDelegate.
tableView_sortDescriptorsDidChange_
*)
on tableView_objectValueForTableColumn_row_(aTableView, aColumn, aRow)
(*
Check theDataSource's array size. If it is 0 then no need
to go further in the code.
Notice the use of "|" around count. Count is an AppleScript
reserved word so we surround it with vertical bars.
*)
if theDataSource's |count|() is equal to 0 then return end
(*
The column identifier is the value you set IB
*)
set ident to aColumn's identifier
(*
NSMutableArray methods
objectAtIndex_ returns the "record" from the array at
the current iteration of aRow.
objectForKey_ returns the specified item from the
record. If ident is "age" then what is returned
is the value of age.
Note:
The nice thing about this is that it is dynamic.
We can add or remove as many columns as we like
and never have to visit this code. The only
unique area is where we are inserting an image.
Take that out and this is boiler plate code.
*)
set theRecord to theDataSource's objectAtIndex_(aRow)
set theValue to theRecord's objectForKey_(ident)
(*
isEqualToString_ is required to test the equivalency
of ident against "theStatus."
*)
if ident's isEqualToString_("theStatus") then
(*
Same thing goes with testing theValue against 1.
We need the intValue of theValue. Radio button
groups return integer values.
Note:
How many of you have wanted to insert images
into your AppleScript Studio table views?
Look how easy it is to do now!
Don't forget:
To use an Objective-C class you must make
a property reference first. We did this at
the top with the following:
property NSImage : class "NSImage"
Images:
You can find the "red.tiff", and "green.tiff"
inside the project source folder.
To add the images to your project right click
on the "Resources" folder, choose "Add..." then
"Existing files..."
After choosing the files make sure the
check box at the top of the window saying
"Copy items into destination group's folder (if needed)"
is checked.
*)
if theValue's intValue() = 0 then
set theValue to NSImage's imageNamed_("green")
else
set theValue to NSImage's imageNamed_("red")
end if
end if
(*
Return the "value" of theValue to the table view for display.
*)
return theValue
end tableView_objectValueForTableColumn_row_
on numberOfRowsInTableView_(aTableView)
(*
This is a mess but it works. When we get sample projects
from Sal we will see a better way.
*)
try
if theDataSource's |count|() is equal to null then
return 0
else
(*
Required method. Simply returns the integer value
representing the number of items in our array "theDataSource."
*)
return theDataSource's |count|()
end if
on error
return 0
end try
end numberOfRowsInTableView_
on tableView_sortDescriptorsDidChange_(aTableView, oldDescriptors)
(*
When a user clicks on the table column headers this method
is called. You can find it in the documentation under NSTableViewDelegate.
For this to work you must set the "Sort Key" and "Selector" in the
Table Column Attributes in IB.
Note:
Common Selectors are "compare:" and "caseInsensitiveCompare:"
The colon is part of the name.
*)
set sortDesc to aTableView's sortDescriptors()
theDataSource's sortUsingDescriptors_(sortDesc)
aTableView's reloadData()
end tableView_sortDescriptorsDidChange_
##################################################
# Application
on awakeFromNib()
(*
To use NSMutableArray we must initialize an instance of it.
In Objective-C it looks like this:
NSMutableArray *theDataSource = [[NSMutableArray alloc] init];
Note:
Remember, we can do this because we set a property at the top
providing us access to the NSMutableArray class.
property NSMutableArray : class "NSMutableArray"
*)
set theDataSource to NSMutableArray's alloc()'s init()
(*
Create a normal AppleScript list of records object
*)
set theData to {{bookTitle:"A Christmas Carol", theAuthor:"Charles Dickens", theStatus:1}, {bookTitle:"The Adventures of Huckleberry Finn", theAuthor:"Mark Twain", theStatus:1}, {bookTitle:"The Adventures of Tom Sawyer", theAuthor:"Mark Twain", theStatus:0}, {bookTitle:"War and Peace", theAuthor:"Leo Tolstoy", theStatus:0}}
(*
Call NSMutableArray's method addObjectsFromArray_() and
add our list of records to it.
Note:
Once we add our "list of records" to theDataSource
it is no longer a list of records, it is an
Objective-C mutable array of dictionary objects.
*)
theDataSource's addObjectsFromArray_(theData)
aTableView's reloadData()
end awakeFromNib
on applicationWillFinishLaunching_(aNotification)
-- Insert code here to initialize your application before any files are opened
end applicationWillFinishLaunching_
on applicationShouldTerminate_(sender)
(*
Since we allocated memory for the NSMutableArray
we clean up after ourselves when we are done.
*)
theDataSource's release()
(*
As pointed out by Shane Stanley
my NSTerminateNow causes an error
Easy fix it to return true instead
*)
return true
end applicationShouldTerminate_
end script
Conclusion
Writing AppleScriptObjC is basically writing Objective-C using AppleScript syntax. My first suggestions is get “Cocoa Programming for Mac OS X Third Edition” by Aaron Hillegass and learn how to read the documentation. I really don’t see a way to create meaningful applications using AppleScriptObjC without knowing Objective-C. I reserver the right to be wrong on this point though after we see some sample projects from Sal.
Until next time. Happy coding!