AppleScriptObjC in Xcode Part 1

Hi Bill,

To add methods to any class you create a category.

Add a new Objective-C file to your project.
Name it “NSTextView+methods.”

The .h file:

The .m file:

In the AppleScriptObjC file you use:

You can add as many methods onto Objective-C class as you like. I would document your changes for future reference.

Also, avoid using the words “get” and “set” in your method names. This will bite you later on when you start using bindings. I would only use underscores in method names to conform to AppleScriptObjC standards. Meaning, don’t use them unless you have multiple parameters and the underscores are identifying them.

I have not looked into doing videos for Lynda but I have been a member for over three years now.

hth,

Craig

Craig,

Here are the .h, .m.and AppleScriptObjC files.

This works great.

I am glad you knew the answer to my questions.

Thanks Again…

Bill Hernandez
Plano, Texas

// ±--------±--------±--------±--------±--------±--------+
// [4325] ( BEGIN ) OBJECTIVE-C METHOD INTERFACE FILE
// The .h file:
// ±--------±--------±--------±--------±--------±--------+
// NSTextView+methods.h
// tutorial_part_one
//
// Created by Bill Hernandez on 10/14/09.
// Copyright 2009 mac-specialist.com. All rights reserved.
// ±--------±--------±--------±--------±--------±--------+

#import <Cocoa/Cocoa.h>

// ±--------±--------±--------±--------±--------±--------+
// This was the default text when I created the new
// ‘Objective-C Class’ File, so I commented it out
// ±--------±--------±--------±--------±--------±--------+
// @interface NSTextView_methods : NSObject {
//
// }
//
// @end
// ±--------±--------±--------±--------±--------±--------+

// ±--------±--------±--------±--------±--------±--------+
// METHOD INTERFACE TO EXTEND NSTextView by Craig Williams
//
// To add methods to any class you create a category.
//
// Add a new ‘Objective-C Class’ file to your project.
// Name it “NSTextView+methods.”
//
// Add a new method, Xcode creates two files (an interface file, and an implementation)
// ( 1 ) NSTextView+methods.h
// ( 2 ) NSTextView+methods.m
// ±--------±--------±--------±--------±--------±--------+
@interface NSTextView (methods)
-(NSString *)stringValue;
@end
// ±--------±--------±--------±--------±--------±--------+
// [4325] ( END ) OBJECTIVE-C METHOD INTERFACE FILE
// ±--------±--------±--------±--------±--------±--------+

// ±--------±--------±--------±--------±--------±--------+
// [4326] ( BEGIN ) OBJECTIVE-C METHOD IMPLEMENTATION FILE
// The .m file:
// ±--------±--------±--------±--------±--------±--------+
// NSTextView+methods.m
// tutorial_part_one
//
// Created by Bill Hernandez on 10/14/09.
// Copyright 2009 mac-specialist.com. All rights reserved.
// ±--------±--------±--------±--------±--------±--------+

#import “NSTextView+methods.h”

// ±--------±--------±--------±--------±--------±--------+
// This was the default text when I created the new
// ‘Objective-C Class’ File, so I commented it out
// ±--------±--------±--------±--------±--------±--------+
// @implementation NSTextView_methods
//
// @end
// ±--------±--------±--------±--------±--------±--------+

// ±--------±--------±--------±--------±--------±--------+
// METHOD IMPLEMENTATION TO EXTEND NSTextView by Craig Williams
//
// To add methods to any class you create a category.
//
// Add a new ‘Objective-C Class’ file to your project.
// Name it “NSTextView+methods.”
//
// Add a new method, Xcode creates two files (an interface file, and an implementation)
// ( 1 ) NSTextView+methods.h
// ( 2 ) NSTextView+methods.m
// ±--------±--------±--------±--------±--------±--------+
@implementation NSTextView (methods)
-(NSString *)stringValue
{
return [self string];
}
@end
// ±--------±--------±--------±--------±--------±--------+
// [4326] ( END ) OBJECTIVE-C METHOD IMPLEMENTATION FILE
// ±--------±--------±--------±--------±--------±--------+

– ±--------±--------±--------±--------±--------±--------+
– [4327] ( END ) AppleScriptObjC FILE
– ±--------±--------±--------±--------±--------±--------+
– tutorial_part_oneAppDelegate.applescript
– tutorial_part_one

– Created by Bill Hernandez on 10/13/09.
– Copyright 2009 mac-specialist.com. All rights reserved.

– ±--------±--------±--------±--------±--------±--------+
– NOTES :
– ±--------±--------±--------±--------±--------±--------+
– set time_stamp to my get_time_stamp(2)

– ( n ) —> format choices { 1, 2, 3 }
– ( 1 ) —> 2009.10.13
– ( 2 ) —> [ 2009.10.13 ] ( 07.14.43.PM )
– ( 3 ) —> 2009.10.13_07.15.15.PM
– ±--------±--------±--------±--------±--------±--------+

script tutorial_part_oneAppDelegate
– ±--------±--------±--------±--------±--------±--------+
– Inheritance
– ±--------±--------±--------±--------±--------±--------+
property parent : class “NSObject”
–Option DoubleClick on —> NSTextView then click on the book icon
–Option DoubleClick on —> NSText then click on the book icon

-- +---------+---------+---------+---------+---------+---------+
-- IBOutlets	
-- Interface Builder considers an outlet as any
-- property with "missing value" as its value
-- +---------+---------+---------+---------+---------+---------+
property aWindow : missing value
property textView : missing value
property textField : missing value

property show_message : false

-- +---------+---------+---------+---------+---------+---------+
-- IBActions (button clicks)
-- Interface Builder considers an action as any
-- single parameter method ending with an underscore 
-- +---------+---------+---------+---------+---------+---------+
on setTextViewFromTextField_(sender)
	-- USER MODIFIABLE
	set which_update_method to 1 -- choices { 1, 2, 3 }
	
	-- date_object and current_date (NOT USED FOR NOW)
	set date_object to current date
	set current_date to (date_object as string)
	
	-- ( n ) ---> time_stamp format choices { 1, 2, 3 }
	-- ( 1 ) ---> [2009.10.13](07.15.41.PM)
	-- ( 2 ) ---> [ 2009.10.13 ] ( 07.14.43.PM )
	-- ( 3 ) ---> 2009.10.13_07.15.15.PM
	set time_stamp to my get_time_stamp(2) -- format choices { 1, 2, 3 }
	
	if (which_update_method is 1) then
		tell textField
			set textFieldValue to get stringValue()
		end tell
		
		tell textView
			set textViewValue to get stringValue() -- USE THE NEWLY DEFINED METHOD
			set newText to (textViewValue as string) & return & time_stamp & "  |  " & (textFieldValue as string)
			setString_(newText)
		end tell
		
		
	else if (which_update_method is 2) then
		tell textField
			set textFieldValue to get stringValue()
		end tell
		
		tell textView
			set textViewValue to get |string|()
			set newText to (textViewValue as string) & return & time_stamp & "  |  " & (textFieldValue as string)
			setString_(newText)
		end tell
		
		-- THIS ALSO WORKS OUTSIDE OF THE TELL BLOCK
		-- textView's setString_((textFieldValue as string) & return & (textViewValue as string))
		
	else		---> (which_update_method is 3)
		set previousText to textView's |string|()
		set currentText to textField's stringValue()
		
		tell class "NSString" of current application
			set newText to its stringWithFormat_("%@%@%@%@", previousText, return, time_stamp & "  |  ", currentText)
		end tell
		
		textView's setString_(newText)
	end if
	
	if (show_message) then
		set x to textView's stringValue()		-- USE THE NEWLY DEFINED METHOD
		display dialog (x as string)
		
		display dialog (newText as string)
	end if
	
end setTextViewFromTextField_
-- +---------+---------+---------+---------+---------+---------+
on get_time_stamp(which)
	if (which is 1) then
		set returnValue to do shell script "date '+[%Y.%m.%d](%I.%M.%S.%p)'"
		---> [2009.10.13](07.15.41.PM)
	else if (which is 2) then
		set returnValue to do shell script "date '+[ %Y.%m.%d ] ( %I.%M.%S.%p )'"
		---> [ 2009.10.13 ] ( 07.14.43.PM )
	else if (which is 3) then
		set returnValue to do shell script "date '+%Y.%m.%d_%I.%M.%S.%p'"
		---> 2009.10.13_07.15.15.PM
	else
		-- which is 1 or anything else
		set returnValue to do shell script "date '+[%Y.%m.%d](%I.%M.%S.%p)'"
		---> [2009.10.13](07.15.41.PM)
	end if
	return returnValue
end get_time_stamp
-- +---------+---------+---------+---------+---------+---------+
-- Application
-- +---------+---------+---------+---------+---------+---------+

on awakeFromNib()
	set time_stamp to my get_time_stamp(2) -- format choices { 1, 2, 3 }
	
	tell textView
		setTextContainerInset_({5.0, 5.0})
		setString_(time_stamp & "  |  " & "Bill Hernandez")
	end tell
	tell textField
		setStringValue_("Default text")
	end tell
	
	-- add this as the last item in the awakeFromNib handler
	aWindow's makeFirstResponder_(textField)
	
end awakeFromNib
-- +---------+---------+---------+---------+---------+---------+	
on applicationWillFinishLaunching_(aNotification)
	-- Insert code here to initialize your application before any files are opened 
end applicationWillFinishLaunching_
-- +---------+---------+---------+---------+---------+---------+
on applicationShouldTerminate_(sender)
	-- Insert code here to do any housekeeping before your application quits 
	return current application's NSTerminateNow
end applicationShouldTerminate_
-- +---------+---------+---------+---------+---------+---------+	

end script
– ±--------±--------±--------±--------±--------±--------+
– [4327] ( END ) AppleScriptObjC FILE
– ±--------±--------±--------±--------±--------±--------+

on setTextViewFromTextField_(sender)
		set textFieldValue to textfield's stringValue() as string
		set textfieldhistory to textFieldValue & return & textfieldhistory
		textView's setString_(textfieldhistory)
	end setTextViewFromTextField_

:smiley:

Hello Craig -

First, thanks very much for the effort here. After a year of reading Cocoa tutorials, IB tutorials and Xcode 3 Unleashed I was very happy to find a simple guide that was current. Most of the tutorials I’ve found have been based on older versions of Xcode/IB and have been nearly useless.

I just finished going through part one with good results but one thing doesn’t work right. When I run the app, a window named “Window” pops up in front of the “PartOne” window. It’s empty, you cant type anything in it, but you can dismiss it or put it in the background.

Where is this coming from and how can I get rid of it?

I’m running 10.6.2 with Xcode 3.2.1 and IB 3.2.1(740).

Thanks,

Scott.

Model: MacBook Pro Intel Core Duo, Mac Mini Intel Core Duo
AppleScript: 2.3
Browser: Safari 531.21.10
Operating System: Mac OS X (10.6)

Sounds like for some reason you have an extra window in IB.
Just delete it
image

Thanks, you’re right I did. Can’t figure how it got there but there it was…

Thanks,
Scott.

The problem I had with the tutorial:

I believe that the tutorial assumes you know what cocoa is, what obj-c is, and what all these classes and parents mean. For a little old mac scripter (no pun intended) who just wants to run simple automations and create a GUI to connect his scripts into a nice interface.

All I wanted to do was learn how to take one script I have already made, and put it into this program and connect it to a button in the GUI. I finally figured it out after much puzzeling over what was necessary for this to happen.

AppleScript Studio assumes you are just a simple scripter and want to build a UI for your scripts. You can use it without any help. This new stuff AppleScript Objc assumes you are an XCode developer and you want to help your friends hook up their scripts into an GUI.

Anyway. thanks for the tutorial. But I think it might be appropriate to have smaller more specific tutorials on how to use the simplest most basic program (1 button connected to 1 script and nothing else) and then maybe build your way up as you add more and more controls to the program. Since the first tutorial includes how to use the strings and text areas and spends more time on them then the button (which was an afterthought) it made it difficult to determine exactly what is required to just simply have 1 button trigger to running a script.

A tip to all of you not knowing anything about Objective-C, C, inheritance and Xcode in particular, this is the guide which made me the programmer I’m now:

BecomeAnXcoder

Hope it helps,
ief2

@fdenger - I have felt the same way on so many occasions when going through a programming book or a tutorial. When things are over your head there can be some anger toward the author because they are not explaining it in a way that you understand.

Things that you might consider about these particular tutorials.

  • They were not meant to be a comprehensive covering of AppleScriptObjC.
  • I put them together just weeks after Snow Leopard was released to give others an overview of the new framework because there was relatively no other documentation at that time.
  • The audience I was targeting was experienced AppleScript Studio users who just needed an overview to get started.

That being said, I want to reiterate that I understand your position. Programming is hard and it doesn’t make it any easier to learn when tutorials expect you to already have a good grasp on things before reading them. You may have better success reading chapter 30 of “Learn AppleScript, The Comprehensive Guide to scripting and Automation on Mac OS X, Third Edition”. I wrote that with a focus on more of a “ground up” approach. There are several example applications built during the chapter and the first one is a dialog attached to a button.

If you have specific questions then please post them to the forum. There are many wonderful people here who really go out of their way to help. And if you are serious about learning AppleScript, the best way is to start answering peoples questions on this forum. Myself and many others learned a lot about programming by doing this very thing.

Best,

Craig

Craig,

Thanks for the reply. I was just wanting to post my feedback. I think your tutorial is good I just think if you can’t/don’t have time to wrap your head around objC its difficult to follow along.

I’m working on a little modification to my application now. I want the window to stay on top because its a utility app for users of our database application… Here’s my code (hopefully someone can tell me why it doesn’t work):

    on activated_(sender)
        tell class "NSWindow" of current application
            sender's setLevel(3)
        end tell
    end activated_

I’m sure its an obvious problem but I read all about how to setLevel in objC and I think i have it in the right place in my main applescript file in my project. I am not getting any errors in the debugger so I am scratching my head…

Thanks!

Hi All,

Ok I did some research and figured out the answer to my question, after much playing around and searching. Here’s a good tutorial on the subject to access the window methods from Applescript ObjC. The basic idea is you need to make a property with a missing value, then connect the window to that property. Once you do that you have an object you can call all the window’s methods from. So for example:

property myWindow : missing value

on setTopLevel_(sender)
myWindow's setLevel(3)
end setTopLevel_

on setNormalLevel_(sender)
myWindow's setLevel(0)
end setNormalLevel_

So now go into IB and control-drag the window’s titlebar to App Delegate and select myWindow to connect to. Then take a button in your window and control-drag it to to the App Delegate and select setTopLevel: to connect to. Then connect another button to setNormalLevel: Then compile and run and you will see that by clicking the buttons you can set the window to “floating” (top) level and the other button sets it back to normal. Note: make sure you put this code inside your AppDelegate script in XCode after the parent property. Otherwise cocoa won’t know what your talking about.

Note that now that you have the window connected to the property “myWindow” you can access any of cocoa’s NSWindow’s methods by calling them like so:


myWindow's (any-NSWindow-method)

I got most of my information from:

http://www.applescriptwiki.com/applescriptwiki/show/setLevelOfAWindow

Also as far as the most basic tutorial to just create 1 button in IB and connect it to just 1 function in applescript for the non-cocoa non-C programmer who doesn’t want nor care about C or ObjC or Cocoa, I wrote it down here:

http://fatcpu.blogspot.com/2010/09/applescriptobjc-tutorial-most-basic.html

Thanks again Craig for your initial tutorial and getting me started. I’m glad this info was here.

Hi Craig

I’ve tried to modify your example such that the “Set Text” button loads an rtf document from a file and displays it in the Text View. Here’s the crux of what I thought was a simple edit to the setTextViewFromTextField subroutine :


on setTextViewFromFile_(sender)
   textView's readRTFDFromFile_(choose file with prompt "Select your subtitles file:" of type {"rtf"} without invisibles)
end setTextViewFromFile_

But this is throwing up an error as follows:
-[NSButton readRTFDFromFile:]: unrecognized selector sent to instance

Which seems to suggest to my badly out of my depth cocoa-newbieness that textView is thinking it belongs to the NSButton class rather than the NSTextView class … clearly I’ve messed something up. Can you point me in the right direction so I can get where I’m trying to go.

Ultimately I’d like my little test app to read in and display an rtf file, then I would want to parse that file line by line and extract info for font, size and style, and to spit that out as an XML file. So far I have all that working via AppleScripting the TextEdit app (and then programmatically saving that out the parsed file as an XML) but as a next step I’d like to be able to skip the TextEdit step and do it in AppleScriptObjC, including importantly the displaying it part!

Greatly appreciate any help you can offer, especially what the heck is screwy in my logic in the above seemingly oversimplified code

Cheers
Andy

EDIT: So I think I realized the first of my errors above was in not correctly connecting the App Delegate to the NSTextView item in Interface Builder … having connected that up properly (I think) I’m now no longer getting the NSButton readRTFDFromFile:]: unrecognized selector error, but instead am now getting:

-[NSAppleEventDescriptor length]: unrecognized selector sent to instance

Bugger. Out of the frying pan and into the fire :rolleyes:

EDIT 2: Aha! Should have used POSIX path.

How is the performance relative to Objective C? I don’t want to learn Objective C and Applescript notation is good enough for what I need to do.

What are everyone’s thoughts?

It depends very much on what you’re doing. For some things, AS will be too slow – but then for some things, Objective-C is too slow and people resort to straight C.

Can you give an example of when to use this Language as opposed to Objective C? I come from the windows world and this seems like what Microsoft did with .NET and CLR.

I prefer the syntax of Applescript because the Objective C syntax is foreign to me (Java/C#).

When should you use Applescript? Is this interpreted? Don’t you get compiled runtimes in the end?

The main use case is when you are using AppleScript already – so generally when you are scripting another app or apps. That’s not to say it can’t be used for stand-alone stuff, but that’s no really what it’s designed for, nor is it ideal for that.

Not really. It’s more about giving scripts that drive other apps access to Cocoa – mainly for UI, but as is the nature of such things, anything else that will help them.

If you know C, Objective-C syntax probably takes half-a-day at most to get the hang of. And you use a similar approach in AppleScriptObjC – the same goes for using the other bridges, for Python, Ruby, etc. Cocoa and Objective-C are closely entwined.

You get a mixture. The AS component is loaded at run-time – it depends very much on the dynamic nature of the Cocoa runtime.

I wonder how can I check if textField is empty?

I’ve add if is not “” but don’t work


on setTextViewFromTextField_(sender)
	set textFieldValue to textField's stringValue()
	if textFieldValue is not "" then
		textView's setString_(textFieldValue)
		beep
	end if
end setTextViewFromTextField_

It worked with:


on setTextViewFromTextField_(sender)
	set textFieldValue to textField's stringValue()
	if textFieldValue's isEqualToString_("") then
        	else
            	textView's setString_(textFieldValue)
        	beep
	end if
end setTextViewFromTextField_

but should be a more elegant/correct way to avoid else statement?

P.S. using Xcode 4.0.1

You have to coerce the result to text. Something like:

if textFieldValue as text = “” then

Thanks, I need not equal so this work fine:

if textFieldValue as text isn’t “” then

My favorite way to determine an empty string is the length method.
There’s no overhead and you can compare directly to an AppleScript integer value


	on setTextViewFromTextField_(sender)
		set textFieldValue to textField's stringValue()
		if textFieldValue's |length|() is not 0 then
			textView's setString_(textFieldValue)
			beep
		end
	end setTextViewFromTextField_