MacOS: How to update an AppDelegate property via editing a table view cell? - xcode

In my macOS app I’m trying to update an AppDelegate property by editing a Table View Cell.
I created a sample project (Table View example project with array controller) using tips from the answer to this post.
I’m using macOS Big Sur (11.6.8) and Xcode 12.5.1. I'll be glad to supply a source archive download link or email it if requested. When I tried to add a link in this post it was rejected.
Here's my AppDelegate script:
script AppDelegate
property parent : class "NSObject"
property msg : ""
-- IBOutlets
property theWindow : missing value
property tableViewData : missing value
property arrayController : missing value
property showTableViewData : missing value
on `applicationWillFinishLaunching_(aNotification)`
appInit()
end applicationWillFinishLaunching_
on applicationShouldTerminate_(sender)
return current application's NSTerminateNow
end applicationShouldTerminate_
on appInit()
set my msg to (current date & return & return & "Initializing…") as string
delay .5
initTableView()
set my msg to (current date & return & return & "Ready.") as string
delay .5
end appInit
on initTableView()
set my tableViewData to {}
set my tableViewData to {{adName:("C21 ad" as string), pageNumber:("001" as string)}, {adName:("ERA ad" as string), pageNumber:("002" as string)}}
end initTableView
on `showTableViewData_(sender)`
set my msg to (current date & return & return & "showing tableViewData....") as string
delay .5
set displayText to ""
repeat with thisRecord in tableViewData
display alert "adName: " & (adName of thisRecord as string) & return & "pageNumber: " & (pageNumber of thisRecord as string)
end repeat
set my msg to (current date & return & return & "Ready.") as string
delay .5
end `showTableViewData_`
end script
How it works
As you can see I have hardcoded two records which I use to populate the tableViewData property on initialization which in turn is displayed in the table view. For some reason the adName data is not being displayed, but that bug is not what this post is about.
App initialized
Clicking the button reads the tableViewData property, iterates the records displaying them via an alert (so we can see it's contents).
Record display
Aside from the adName not displaying, so far so good.
Next, I edit the pageNumber table view cell for the first record (changing it from "001" to "777" and hitting return). When I click the button the first record is displayed which still shows a pageNumber value of "001" (instead of the "777" value I entered).
Record display after editing
Here are some shots of the table view cell attributes, connections, and bindings:
Attributes
Connections
Bindings
I tried selecting the bindings setting "Continuously Updates Value" but that doesn't seem to help; my tableViewData property is not updated with the newly entered data in the table view cell.
Most of the current developer docs use Obj-C or Swift instead of AppleScript and iOS or tvOS related. I'm using Xcode 12.5.1 because of problems binding UI elements to my code.
Thanks for taking the time to look this over.

The contents of tableViewData doesn't change but content of arrayController does. Try
repeat with thisRecord in content of arrayController
display alert "adName: " & (adName of thisRecord as string) & return & "pageNumber: " & (pageNumber of thisRecord as string)
end repeat
To make it work, use a Script Object instead of a record.
on makeAd(nameOfAd, pageNr)
script ad
property adName: nameOfAd
property pageNumber: pageNr
end script
return ad
end makeAd
on initTableView()
set my tableViewData to {makeAd("C21 ad", "001"), makeAd("ERA ad", "002")}
end initTableView

Related

How can I avoid hard coding the handlers for each menu item?

I have a stay open AppleScript app that creates a menu with many menu items. The handlers that is mapped are repetitive and only differs in its index. How can I refactor this code to dry it up?
on makeMenus(selectedIndex as integer)
newMenu's removeAllItems() -- remove existing menu items
set menuItems to {"POC1", "POC2"}
repeat with i from 1 to number of items in menuItems
set this_item to item i of menuItems
if i is equal to selectedIndex then set this_item to this_item & " " & (emoji's CHECK)
set thisMenuItem to (current application's NSMenuItem's alloc()'s initWithTitle:this_item action:("someAction" & (i as text) & ":") keyEquivalent:"")
(newMenu's addItem:thisMenuItem)
(thisMenuItem's setTarget:me)
end repeat
-- Create the Quit menu item separately
set thisMenuItem to (current application's NSMenuItem's alloc()'s initWithTitle:"Quit" action:("quitAction:") keyEquivalent:"")
(newMenu's addItem:thisMenuItem)
(thisMenuItem's setTarget:me)
end makeMenus
The repetitive handler is:
on someAction1:sender
updateStatus(1)
end someAction1:
on someAction2:sender
updateStatus(2)
end someAction2:
If I can derive the menu item name or index from a parameter in the someAction handler, that would let me have a single handler to take care of all the menu items.
The sender argument of an action handler will be the object that called the action. In this case the sender will be the particular menuItem, so a common action handler can use a property such as the sender's title.
Note that since it will be a Cocoa object, you will need to coerce the title to an AppleScript string, for example:
on doAction:sender
set menuTitle to (sender's title) as text
if menuTitle is "this" then
doThis()
else if menuTitle is "that" then
doThat()
else
doOther()
end if
end doAction:

How Can I Show Progress/Countdown to Next Action In The Menubar Systemwide Applescript

I created an Applescript that every 600 seconds retrieve the last line of a log file and add it to a Google Sheet spreadsheet.
This is extremely useful, but it is starting to annoy me not knowing when the next action will be performed.
So now I'm trying to display the progress (or countdown in seconds) to the next action in the menubar, so I can, kind of, be aware that the focus application will change.
Can please someone point me in the right direction?
--- Added Information ---
Now, instead of showing a countdown, I want to just simply show how many steps left, and it's still not working. This is a test code I'm working on:
set progress total steps to 12
set progress completed steps to 0
set progress description to "Running..."
set progress additional description to "ETA: 120 Seconds"
repeat with a from 1 to 12
delay 10
set progress completed steps to a
set ETA to a * 10
set progress additional description to "ETA: " & ETA & " seconds"
end repeat
What I'm doing wrong?
Based on the link red_menace gave in comments and the screenshot you provided, here's a script that (I think) will give the results you want. It's not an automator workflow. This is an AppleScript stay-open application. Copy it into Script Editor and save is as an application: choose "Application" from the File Format pulldown menu on the save screen, and make sure you check the Stay open after run handler checkbox. then run the application like normal and see the demo.
use framework "AppKit"
use framework "Foundation"
use scripting additions
property ca : current application
property NSStatusBar : class "NSStatusBar"
property NSMenu : class "NSMenu"
property NSMenuItem : class "NSMenuItem"
property NSImage : class "NSImage"
property NSProgressIndicator : class "NSProgressIndicator"
property NSView : class "NSView"
property NSTextField : class "NSTextField"
property idle_time : 1
global status_bar_item, progress_views, idx
on run
set progress_views to {}
set status_bar_item to NSStatusBar's systemStatusBar's statusItemWithLength:(ca's NSSquareStatusItemLength)
set status_bar_item's |menu| to NSMenu's alloc's initWithTitle:""
set status_bar_item's |menu|'s minimumWidth to 220
my setImage(ca's NSImageNameSmartBadgeTemplate)
my addToStatusMenu(my progress_view_obj("Number 1"))
my addToStatusMenu(my progress_view_obj("Number 2"))
set idx to 0
end run
on idle
set idx to idx + 1
repeat with this_obj in progress_views
set rand_numb to (random number from 1 to 9)
(this_obj's progress_indicator's incrementBy:rand_numb)
this_obj's changeLabel("Adding: " & rand_numb)
end repeat
if idx > 20 then
quit
end if
return idle_time
end idle
on quit
-- remove status item and quit
NSStatusBar's systemStatusBar's removeStatusItem:status_bar_item
continue quit
end quit
on addToStatusMenu(obj)
set statusMenu to status_bar_item's |menu|
statusMenu's addItem:(obj's menu_item)
set end of progress_views to obj
end addToStatusMenu
on progress_view_obj(label_value)
script prog_view_obj
-- text field
property progress_indicator : missing value
property menu_item : missing value
property label_field : missing value
global content_view
-- make label field
set label_field to NSTextField's labelWithString:label_value
set label_field's translatesAutoresizingMaskIntoConstraints to false
-- progress indicator
set progress_indicator to NSProgressIndicator's alloc's initWithFrame:(ca's NSMakeRect(0, 0, 200, 20))
set progress_indicator's indeterminate to false
progress_indicator's setStyle:(ca's NSProgressIndicatorStyleBar)
progress_indicator's startAnimation:me
set progress_indicator's translatesAutoresizingMaskIntoConstraints to false
-- content view for menu item
set content_view to NSView's alloc's initWithFrame:(ca's NSMakeRect(0, 0, 200, 100))
set content_view's translatesAutoresizingMaskIntoConstraints to false
content_view's addSubview:label_field
content_view's addSubview:progress_indicator
-- constraints to arrange elements
--set view_height_constraint to content_view's heightAnchor's constraintEqualToConstant:100
--view_height_constraint's setActive:true
set lf_left_margin to content_view's leadingAnchor's constraintEqualToAnchor:(label_field's leadingAnchor) |constant|:-10
lf_left_margin's setActive:true
set pi_left_margin to content_view's leadingAnchor's constraintEqualToAnchor:(progress_indicator's leadingAnchor) |constant|:-10
pi_left_margin's setActive:true
set pi_right_margin to content_view's trailingAnchor's constraintEqualToAnchor:(progress_indicator's trailingAnchor) |constant|:10
pi_left_margin's setActive:true
set top_spacing_const to content_view's topAnchor's constraintEqualToAnchor:(label_field's topAnchor) |constant|:-10
set mid_spacing_const to label_field's bottomAnchor's constraintEqualToAnchor:(progress_indicator's topAnchor) |constant|:-10
set bott_spacing_const to content_view's bottomAnchor's constraintGreaterThanOrEqualToAnchor:(progress_indicator's bottomAnchor) |constant|:10
top_spacing_const's setActive:true
mid_spacing_const's setActive:true
bott_spacing_const's setActive:true
content_view's updateConstraints()
-- create and flesh out menu item
set menu_item to NSMenuItem's alloc's init()
set menu_item's view to content_view
content_view's updateConstraints()
on changeLabel(label_value)
set label_field's stringValue to label_value
end changeLabel
on changeMinValue(val)
set progress_indicator's minValue to val
end changeMinValue
on changeMaxValue(val)
set progress_indicator's maxValue to val
end changeMaxValue
on changeValue(val)
set progress_indicator's doubleValue to val
end changeValue
on currentValue()
return progress_indicator's doubleValue() as integer
end currentValue
end script
run prog_view_obj
return prog_view_obj
end progress_view_obj
on setImage(imageName)
status_bar_item's button's setImageScaling:(ca's NSImageScaleProportionallyDown)
status_bar_item's button's setImage:(NSImage's imageNamed:imageName)
status_bar_item's button's setImagePosition:(ca's NSImageLeft)
end setImage
You shouldn't need to change anything except in the run and idle handlers — the first to set up the menus and the second to increment the progress bars or update the labels as needed. You can change how frequently the indicators update by increasing or decreasing the idle-time property (it's currently set to run the idle loop once a second). You'll only need to get into the big, messy handler if you need to change the layout or size of the menu items.
Technical point: I've put each progress view inside a script object (script prog_view_obj) to make it easier to work with. That may take some time for you to wrap your head around — it's tricky AppleScript — but at any rate, there's a bunch of convenience handlers at the end of the script object definition that you may find useful.

Populate text field with variable and show text

Here's my code:
set todayDate to do shell script "date '+%Y/%m/%d'"
set salesRef to "COMPANY-AB" & "-" & theCustomerInitials & "-" & todayDate & "-" & theCustomerID
set the clipboard to salesRef as text
display dialog salesRef as text buttons {"OK"} default button 1
Here's the UI: http://i.imgur.com/sfRnpr6.png
I'd like to make the text 'Copied!' appear when the successfully completes the clipboard line, but remain hidden until then, and also populate the text field at the bottom with the 'salesRef' variable, so the user get's an output.
Essentially then, I'll be able to remove the display dialog. But I can't work out how to do this :(
Successfully managed this by adding these lines:
property theTextField : missing value
…
theTextField's setStringValue:salesRef
Then created a connection to the text field in IB by control dragging from the App Delegate to the text field, and selecting the property.
Are you ok with NSAppleScript?
if so here is an example:
NSAppleScript *myScriptThatShouldGimmeStuff = [[NSAppleScript alloc] initWithSource:
#"return (text returned of (display dialog \"gimme your input:\" default answer \"\" buttons {\"Ok\"} default button 1))"];
//this previous line is the applescript
NSAppleEventDescriptor *theStuff = [myScriptThatShouldGimmeStuff executeAndReturnError:nil];
self.myTextBoxToPutInputIn.stringValue = [theStuff stringValue]; //[theStuff stringValue] is the result returned
/|\
/ | \
/ | \
|
|
//don't forget to change this
//(I didn't comment out the arrow because i want xcode
// to throw you an error so you don't forget to change that).
So it is not clear from you code what you are doing, but IF you are using ASOC, then you have to define OUTLETS to the controls in your UI, and then you just set the .stringValue of the outlet to whatever string you want to appear in them.
If you don't know how to set up outlets, watch this video and download the example code (look for INTRO VIDEO on the page):
http://www.macosxautomation.com/applescript/develop/index.html
Or for another step-by-step example, here:
http://asobjcresources.weebly.com/getting-a-text-field-input.html

Setting Property Value of Object in Applescript Objective-C

I am working in XCode (5) on OSX 10.9.5, creating an ApplescriptOjbC project.
I have created a new applescript in the project with a class (script) named "MSDropBox" and am using it to accept the drop of files. The files dropped have their file paths read correctly. I now just want to pass this array off to the Array Controller, which is used as the source for a table. I want the table to reflect the dragged files.
I have a method in the AppDelegate class that sets the value and I am calling it from the MSDropBox class. However, it is affecting the values in the table. I believe it is calling on the method of the class, but not object. How do I affect the object?
Below is the MSDropBox class:
script MSDropBox
property parent : class "NSBox"
property window : missing value
property thisPageList : missing value
on draggingEntered_(sender)
log "entered"
set pb to sender's draggingPasteboard()
set theOptions to {NSPasteboardURLReadingFileURLsOnlyKey:1}
return pb's canReadObjectForClasses_options_({current application's |NSURL|}, theOptions)
end draggingEntered_
on performDragOperation_(sender)
log "perform"
-- Get the file paths
set pb to sender's draggingPasteboard()
set theOptions to {NSPasteboardURLReadingFileURLsOnlyKey:1}
set theURLs to pb's readObjectsForClasses_options_({current application's |NSURL|}, theOptions)
log theURLs
repeat with thisURL in theURLs
set thisURLStr to (characters 1 thru -1 of ((thisURL's |path|()) as string) as string)
set thisPageList to thisPageList & thisURLStr as list
end repeat
return true
end performDragOperation_
on concludeDragOperation_(sender)
log "concludeDragOperation_"
tell class "AppDelegate" of current application
setPageList_(thisPageList)
end tell
end concludeDragOperation_
end script
In Objective-C it's
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
[appDelegate setPageList:thisPageList];
The AppleScriptObjC equivalent is
set appDelegate to current application's NSApplication's sharedApplication()'s delegate()
appDelegate's setPageList_(thisPageList)
I'm not sure whether AppleScriptObjC recognizes the application delegate method without type casting, maybe you have to add as AppDelegate

Support With Sharing AppleScriptObjC Variable To Label Object

script AppDelegate
property theWindow : missing value
property displayLabel : missing value
on applicationWillFinishLaunching:aNotification
display dialog "Are you ready?" buttons {"Yes", "No"} default button "Yes"
end applicationWillFinishLaunching:
if result = {button returned:"No"} then
display alert "That's a shame."
tell application "This Application"
quit
end tell
else if result = {button returned:"Yes"} then
set testVariable to "this is a test"
end if
end script
So, this is an example of an app I'm working on in AppleScriptObjC. I've taken a lot out and replaced things with different strings, but this is the basic layout.
How can I get the value of 'testVariable' into the 'displayLabel' property, which is linked to a label in the interface builder? The label is empty initially, but I want the value of 'testVariable' to populate it once the script has been run.
Thanks!!!
The syntax is pretty similar to the ObjC syntax
ObjC
displayLabel.stringValue = testVariable;
AppleScriptObjC
set displayLabel's stringValue to testVariable

Resources