Kodeco Forums

Making A Mac App Scriptable Tutorial

As an app developer, itā€™s near impossible to think of all the ways people will want to use your app. Wouldnā€™t it be cool to let your users create scripts to customize your app to their own personal needs? With Applescript and Javascript for Automation (JXA), you can! In this making a Mac app scriptable [ā€¦]


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/1033-making-a-mac-app-scriptable-tutorial

Hi Sarah,
The link for the starter version of the project is actually pointing to the final version of the project. Anyway, very interesting tutorial.
Thanks
John

Hi John,

Thanks for noticing that, and for letting me know. Not sure how that happened, but it should be fixed now.

Regards,
Sarah

Great content, Iā€™ve started playing with JavaScript for Automation sometime ago, this is the most in-depth tutorial I found.

Great tutorial.

Compiler error: Type 'Any" has no subscript membersā€¦

I had to modify two functions to get the code to compile under XCode 8 and Swift 3.
Iā€™m new to Swift, so there are probably many ways to improve my code, but it does compile and work now. :slight_smile:

func receivedTasksChangeNotification(_ notification: Notification) {
	
	if let notificationObject = notification.object as? [String:Any] {
		if let newData = notificationObject["newTasksData"] as? [Task] {
			completeTaskData = newData
			refreshDisplay()
		}
	}
}

func receivedSingleTaskChangeNotification(_ notification: Notification) {
	
	if let notificationObject = notification.object as? [String:Any] {
		if let newData = notificationObject["newTaskData"] as? Task {
			let newID = newData.id
			
			// update an existing task with the new data and return
			
			for (index, task) in completeTaskData.enumerated() {
				if task.id == newID {
					completeTaskData[index] = newData
					refreshDisplay()
					return
				}
			}
			
			// otherwise, create a new Task with the specified data
			
			completeTaskData.append(newData)
			refreshDisplay()
		}
	}

}

I came up with the same solution but I chained the ā€˜letā€™ calls:

    func receivedTasksChangeNotification(_ notification: Notification) {
        if let notificationObject = notification.object as? [String: Any],
            let newData = notificationObject["newTasksData"] as? [Task] {
            completeTaskData = newData
            refreshDisplay()
        }
    }

    func receivedSingleTaskChangeNotification(_ notification: Notification) {
        if let notificationObject = notification.object as? [String: Any],
            let newData = notificationObject["newTaskData"] as? Task {
            let newID = newData.id
            for (index, task) in completeTaskData.enumerated() {
                if task.id == newID {
                    completeTaskData[index] = newData
                    refreshDisplay()
                    return
                }
            }
            
            completeTaskData.append(newData)
            refreshDisplay()
        }
    }

I am just working on updating the tutorial to Swift 3 now.

Hi Sarah,
Great post and very useful. Iā€™ve managed to follow most of it, but I got stuck at this bit:

  func insertObject(_ object: Task, inTasksAtIndex index: Int) {
    tasks = dataProvider.insertNew(task: object, at: index)
  }

How did you get the inTasksAtIndex portion of this function call? Is it the Swift class or the AppleScript class name?

Iā€™m thinking that Iā€™ve made it harder than I need to by having an AppleScript class name with two words i.e. ā€œmail messagesā€.

Thanks
Andrew

Hi Andrew,

This & removeObjectā€¦ are functions that are auto-generated when using Key-Value-Coding.

The AppDelegate has an array property called ā€œtasksā€, so I capitalise that property name and insert it as a parameter name in both:

  func insertObject(object: Task, inTasksAtIndex index: Int)

and

  func removeObjectFromTasksAtIndex(index: Int)

If your AppleScript class is called ā€œmail messagesā€ then it is probably backed by an AppDelegate property called something like ā€œmailMessagesā€ - that is what you need to capitalise and put into the function call in place of ā€œTasksā€.

Hope this helps,
Sarah

Sarah,

Thanks for your help. Thatā€™s got me a lot further.
Thanks
Andrew

Hello Sarah,

nice tutorial, but I am struggling with custom commands. When I execute your custom command script with the final project I get an error.

error "Scriptable Tasks got an error: task id \"67D3FA85-C8B9-4987-8E13-9B615A067338\" doesnā€™t understand the    ā€œĀ«event TaSktextĀ»ā€ message." number -1708 from task id "67D3FA85-C8B9-4987-8E13-9B615A067338"

What is the correct syntax to call a method from Apple Script?

Thanks

When you open the Dictionary for the app in the Script Editorā€™s Library, can you see the mark command? If not, close the Dictionary window, delete it from the Library and re-import it from the app.

Do you have more than one copy of the app running? This can happen as the Script Editor tries to keep hold of a running copy, even while you are editing and compiling a new copy. If so, close them both and do a fresh build & run from Xcode - this is only a problem dug in development.

The syntax for calling the custom command is shown in two sample script files: ā€œ7. Custom Command.scptā€ in either the Apple Script or theJavaScript folder.

Hopefully these pointers will solve the issue, but please reply to this thread if you are still having problems.

Sarah

This tutorial is more than six months old so questions are no longer supported at the moment for it. We will update it as soon as possible. Thank you! :]