Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

whirl

macrumors member
Original poster
Jul 24, 2011
49
1
I have a small AppleScript which I would like to to put into a user interface so I have installed Xcode 13.4.1 with a new AppleScrip Project

After about 5 days trying to work out why I cannot control drag say a button to the Deligates Cube because this is a bug I nearly gave up but I manged to find a work around for that.

The main headache I can forsee is going to be adding my AppleScript progress bar into Xcode so i was wondering if anyone can help me at least understand how to wire this up.

I have added a progress bar to the UI from the Libray. I had 2 choices here so I chose Determinate Progress Bar. My goaal is to try and get the progress bar on the UI to update.

This is my current AppleScript for the progress Bar which I am trying to get into Xcode.

AppleScript:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set progress total steps to 100
set progress completed steps to 0
set progress description to "Getting Things Ready"

delay 1

set progress description to "Please Wait..."

repeat with i from 1 to 100
   
    --Do stuff here...   
   
    set progress completed steps to i
    set progress additional description to "Updating Please Wait"
    delay 1
   
end repeat

set progress completed steps to numberOfImages

Appreciate any help on this.
 
Last edited:

Mark FX

macrumors regular
Nov 18, 2011
159
17
West Sussex, UK
I don't believe you can use AppleScript's built in progress mechanism from an AppleScriptObjC Xcode project, as you Xcode project application is a standalone application bundle.
So you will have to use an 'NSProgressIndicator' on your window, and then wire it up from the Interface Builder to your AppDelegate or other Script class in the usual way.
I usually right click the blue class cube or interface element to access the connection panel, as opposed to control dragging.
I do believe there is a bug in Xcode 13 as regards interface element connection to script classes, but I'm using Xcode 12 on 'Catalina', where there is no problem or bug connecting interface controls.

below is a very basic example of using the 'NSProgressIndicator' class in an AppleScriptObjC project, where I have put the NSProgressIndicator on the window and wired it up to the 'progessIndicator' property.
Then set the NSProgressIndicator's minimum value to 0, the maximum value to 10, and the current value to 0, set in the in Interface Builder inspection pane.

AppleScript:
script AppDelegate
  
    use AppleScript version "2.4"
    use scripting additions
  
    property parent : class "NSObject"
    property app : reference to current application
  
    -- IBOutlets
    property mainWindow : missing value
    property progessIndicator : missing value
  
    on applicationWillFinishLaunching:notification
      
    end applicationWillFinishLaunching:
  
    on applicationDidFinishLaunching:notification

    end applicationDidFinishLaunching:
  
    on applicationWillBecomeActive:notification
      
    end applicationWillBecomeActive:
  
    on applicationDidBecomeActive:notification
        set progessIndicator's usesThreadedAnimation to true
        repeat with i from 1 to 10
            progessIndicator's incrementBy:1.0
            delay 1.0
        end repeat
    end applicationDidBecomeActive:
  
    on applicationWillResignActive:notification
      
    end applicationWillResignActive:
  
    on applicationDidResignActive:notification
      
    end applicationDidResignActive:
  
    on applicationShouldTerminateAfterLastWindowClosed:sender
        return true as boolean
        --return false as boolean
    end applicationShouldTerminateAfterLastWindowClosed:
  
    on applicationShouldTerminate:sender
        return app's NSTerminateNow
    end applicationShouldTerminate:

    on applicationWillTerminate:notification
      
    end applicationWillTerminate:
  
end script

I have just put the code into the AppDelegate script for this simple example, as when you create a new AppleScriptObjC project in Xcode, you will have an AppDelegate script created for you.
So you can simply paste my code over the top of the existing created code.

Do be aware that I'm using Xcode 12 on Mac OSX Catalina, and not Xcode 13 on a newer OS, so there may be changes on the later systems that I won't be aware of.

Regards Mark
 
Last edited:

Red Menace

macrumors 6502a
May 29, 2011
578
226
Colorado, USA
There is indeed a bug in Xcode 13 that has yet to be fixed. If you can’t use an earlier version to work around it, AppleScriptObjC can also be used in the Script Editor.
 

whirl

macrumors member
Original poster
Jul 24, 2011
49
1
I don't believe you can use AppleScript's built in progress mechanism from an AppleScriptObjC Xcode project, as you Xcode project application is a standalone application bundle.
So you will have to use an 'NSProgressIndicator' on your window, and then wire it up from the Interface Builder to your AppDelegate or other Script class in the usual way.
I usually right click the blue class cube or interface element to access the connection panel, as opposed to control dragging.
I do believe there is a bug in Xcode 13 as regards interface element connection to script classes, but I'm using Xcode 12 on 'Catalina', where there is no problem or bug connecting interface controls.

below is a very basic example of using the 'NSProgressIndicator' class in an AppleScriptObjC project, where I have put the NSProgressIndicator on the window and wired it up to the 'progessIndicator' property.
Then set the NSProgressIndicator's minimum value to 0, the maximum value to 10, and the current value to 0, set in the in Interface Builder inspection pane.

AppleScript:
script AppDelegate
 
    use AppleScript version "2.4"
    use scripting additions
 
    property parent : class "NSObject"
    property app : reference to current application
 
    -- IBOutlets
    property mainWindow : missing value
    property progessIndicator : missing value
 
    on applicationWillFinishLaunching:notification
    
    end applicationWillFinishLaunching:
 
    on applicationDidFinishLaunching:notification

    end applicationDidFinishLaunching:
 
    on applicationWillBecomeActive:notification
    
    end applicationWillBecomeActive:
 
    on applicationDidBecomeActive:notification
        set progessIndicator's usesThreadedAnimation to true
        repeat with i from 1 to 10
            progessIndicator's incrementBy:1.0
            delay 1.0
        end repeat
    end applicationDidBecomeActive:
 
    on applicationWillResignActive:notification
    
    end applicationWillResignActive:
 
    on applicationDidResignActive:notification
    
    end applicationDidResignActive:
 
    on applicationShouldTerminateAfterLastWindowClosed:sender
        return true as boolean
        --return false as boolean
    end applicationShouldTerminateAfterLastWindowClosed:
 
    on applicationShouldTerminate:sender
        return app's NSTerminateNow
    end applicationShouldTerminate:

    on applicationWillTerminate:notification
    
    end applicationWillTerminate:
 
end script

I have just put the code into the AppDelegate script for this simple example, as when you create a new AppleScriptObjC project in Xcode, you will have an AppDelegate script created for you.
So you can simply paste my code over the top of the existing created code.

Do be aware that I'm using Xcode 12 on Mac OSX Catalina, and not Xcode 13 on a newer OS, so there may be changes on the later systems that I won't be aware of.

Regards Mark

Hi Mark,
I copied and pasted that into a new project.

I have added a progress bar on to the UI and then in the Bindings I have bound the value to App Deligate and set the Model Key Path to progessIndicator. Is this correct ?

When I run the app, I am getting an error popup in the console saying

2022-07-09 21:09:06.335233+0100 TEST-13.0[21855:402591] ***
-[AppDelegate applicationDidBecomeActive:]:
Can’t set usesThreadedAnimation of missing value to true. (error -10006)

Screenshot 2022-07-09 at 21.08.47.jpg


Ian
 

whirl

macrumors member
Original poster
Jul 24, 2011
49
1
There is indeed a bug in Xcode 13 that has yet to be fixed. If you can’t use an earlier version to work around it, AppleScriptObjC can also be used in the Script Editor.

Are there any tutorials to this please
 

Mark FX

macrumors regular
Nov 18, 2011
159
17
West Sussex, UK
No do not use the 'Value : Model Key Path', but use 'Referencing Outlets : New Referencing Outlet', and drag from the little round circle to the Blue AppDelegate Cube Class Object, then select 'progessIndicator' from the list of two properties.
You can also do the same thing by clicking the Progress Indicator object on the window, and right clicking it to reveal the connection panel, or right clicking the Blue AppDelegate Cube Class object instead.
But my understanding is that there are bugs in Xcode 13 as regards connecting UI elements to Script properties.

Screenshot.png


There are very few resources for AppleScriptObjC Xcode projects on the interweb, but you'll find more help on the subject over at the Macscripter forums, where I occasionally hang out myself, and help out with AppleScriptObjC code solutions.

But in all honesty you are better off learning Swift instead these days, as Apple stopped support and updates for AppleScript a few years ago, and have even removed the New AppleScript project template form Xcode 14.

Regards Mark
 
Last edited:

Red Menace

macrumors 6502a
May 29, 2011
578
226
Colorado, USA
Instead of a binding, you need to use an outlet (via the Connections Inspector) so that you can refer to the object. A binding is a two-way connection between a property and an element (such as the value of a progress indicator), while an outlet is a reference to an object (the indicator itself). An outlet is used when specifying the receiver for methods (the progress indicator’s usesThreadedAnimation method, for example).

When using the Script Editor, you lose a lot of the help that Xcode provides, so it becomes a bit more noisy since you have to define everything yourself. For example, a simple handler to just create the progress bar is about 20 lines, but something more involved such as a complete working sample progress indicator controller with descriptive text fields and a cancel button is around 220 lines.

As for tutorials, there isn’t much for AppleScriptObjC (even from Apple), the expectation is to just translate an Objective-C project. An example project that does not use Xcode would be something like:

AppleScript:
use framework "Foundation"
use scripting additions


script ProgressBarController -- a script object/class for a combination progress bar panel
 
   # UI item frame constants (for window layout)
   property WINDOW_FRAME : {{200, 600}, {526, 89}}
   property TOP_TEXT_FRAME : {{22, 57}, {482, 17}}
   property FULL_INDICATOR_FRAME : {{24, 35}, {478, 12}} -- without button
   property REDUCED_INDICATOR_FRAME : {{24, 35}, {450, 12}} -- with button
   property BUTTON_FRAME : {{483, 33}, {18, 18}}
   property BOTTOM_TEXT_FRAME : {{22, 15}, {482, 12}}
 
   # the UI items
   property progressWindow : missing value
   property indicator : missing value
   property topTextField : missing value
   property bottomTextField : missing value
   property cancelButton : missing value -- for setting up the action
 
   # other controller properties
   property setup : false
   property showCancel : false
 
   on initWithCancel() -- initialize with a cancel button
      if setup then return me -- already created
      set my showCancel to true
      init()
   end initWithCancel
 
   on init() -- default initialization
      if setup then return me -- already created
      try
         # create the window - title can be set via the property
         tell (current application's NSPanel's alloc's initWithContentRect:WINDOW_FRAME styleMask:(current application's NSWindowStyleMaskTitled) backing:(current application's NSBackingStoreBuffered) defer:true)
            set my progressWindow to it
            its setPreventsApplicationTerminationWhenModal:false
            its setAllowsConcurrentViewDrawing:true
            its (contentView's setCanDrawConcurrently:true)
            its setReleasedWhenClosed:false -- so window can be shown again
            its setLevel:(current application's NSFloatingWindowLevel) -- float above other windows
            its setHasShadow:true
            its |center|()
            
            # create a text field above the progress indicator
            tell (current application's NSTextField's alloc's initWithFrame:TOP_TEXT_FRAME)
               set my topTextField to it
               its setBordered:false
               its setDrawsBackground:false
               its setFont:(current application's NSFont's systemFontOfSize:13) -- also boldSystemFont
               its setEditable:false
               its setSelectable:false
               its (cell's setLineBreakMode:(current application's NSLineBreakByClipping))
            end tell
            its (contentView's addSubview:topTextField) -- add to window
            
            # create a progress indicator
            if showCancel then
               set theFrame to REDUCED_INDICATOR_FRAME
            else -- reclaim button space
               set theFrame to FULL_INDICATOR_FRAME
            end if
            tell (current application's NSProgressIndicator's alloc's initWithFrame:theFrame)
               set my indicator to it
               its setUsesThreadedAnimation:true
               its setDisplayedWhenStopped:true
               # these can be changed using the indicator property
               its setStyle:(current application's NSProgressIndicatorBarStyle)
               its setIndeterminate:false
            end tell
            its (contentView's addSubview:indicator) -- add to window
            
            # create a cancel button
            if showCancel then
               tell (current application's NSButton's alloc's initWithFrame:BUTTON_FRAME)
                  set my cancelButton to it
                  its setButtonType:(current application's NSMomentaryPushInButton)
                  its setBezelStyle:(current application's NSCircularBezelStyle)
                  its setBordered:false
                  tell its cell()
                     its setBackgroundStyle:(current application's NSBackgroundStyleLight)
                     its setHighlightsBy:(current application's NSChangeGrayCellMask)
                     its setImageScaling:(current application's NSImageScaleProportionallyUpOrDown)
                  end tell
                  its setImage:(current application's NSImage's imageNamed:(current application's NSImageNameStopProgressFreestandingTemplate))
                  # button action is set up in the main script
                  its setKeyEquivalent:(character id 27) -- escape key
                  its setRefusesFirstResponder:true -- no focus ring
               end tell
               its (contentView's addSubview:cancelButton) -- add to window
            end if
            
            # create a text field below the progress indicator
            tell (current application's NSTextField's alloc's initWithFrame:BOTTOM_TEXT_FRAME)
               set my bottomTextField to it
               its setBordered:false
               its setDrawsBackground:false
               its setFont:(current application's NSFont's systemFontOfSize:10)
               its setEditable:false
               its setSelectable:false
               its (cell's setLineBreakMode:(current application's NSLineBreakByTruncatingMiddle))
            end tell
            its (contentView's addSubview:bottomTextField) -- add to window
            
         end tell
        
         set my setup to true
         return me -- success
      on error errmess
         set logText to "Error in ProgressBarController's 'init' handler: "
         log logText & errmess
         # display alert logText message errmess
         return missing value -- failure
      end try
   end init
 
   to fetchEvents() -- pass on user events to avoid blocking the UI (Shane Stanley)
      repeat -- forever (or at least until events run out)
         tell current application's NSApp
            set theEvent to its nextEventMatchingMask:(current application's NSEventMaskAny) untilDate:(missing value) inMode:(current application's NSDefaultRunLoopMode) dequeue:true
            if theEvent is missing value then exit repeat -- none left
            its sendEvent:theEvent -- pass it on
         end tell
      end repeat
   end fetchEvents
 
   to updateProgress by amount
      fetchEvents()
      if amount is not 0 then try -- skip coercion errors
         indicator's incrementBy:(amount as integer)
      end try
      progressWindow's display()
   end updateProgress
 
   to setTextFields given top:top : (missing value), bottom:bottom : (missing value)
      if top is not missing value then topTextField's setStringValue:(top as text)
      if bottom is not missing value then bottomTextField's setStringValue:(bottom as text)
      progressWindow's display()
   end setTextFields
 
   to setProgressValues given minimum:minimum : (missing value), maximum:maximum : (missing value)
      if minimum is not missing value then try -- skip coercion errors
         indicator's setMinValue:(minimum as integer)
      end try
      if maximum is not missing value then try -- skip coercion errors
         indicator's setMaxValue:(maximum as integer)
      end try
   end setProgressValues
 
   to showWindow()
      tell progressWindow to makeKeyAndOrderFront:me
   end showWindow
 
   to closeWindow()
      tell progressWindow to orderOut:me
   end closeWindow
 
end script


(*
   ----- Example -----
 
   Typical operation:
      • set up cancel button action as needed
      • create progress controller
      • adjust settings
      • show the progress window
      • place progress updates in the item processing loop
         check the 'stopProgress' property for cancel as needed
      • close the progress window
*)


property |error| : missing value -- since performSelectorOnMainThread doesn't return anything
property controller : missing value -- this will be the progress controller instance
property stopProgress : missing value -- a flag to stop (cancel button pressed)

on run -- example can be run as app and from Script Editor
   open {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} -- or whatever
end run

on open theItems -- items dragged onto droplet
   try
      if current application's NSThread's isMainThread() as boolean then
         doStuff_(theItems)
      else
         my performSelectorOnMainThread:"doStuff:" withObject:theItems waitUntilDone:true
      end if
      if |error| is not missing value then error |error|'s errmess number |error|'s errnum
      log "done"
      if name of current application is not in {"Script Editor", "Script Debugger"} then tell me to quit -- don't quit script editors
   on error errmess number errnum
      display alert "Error " & errnum message errmess
   end try
end open

to doStuff:stuff -- UI stuff needs to be done on the main thread
   try
      set stopProgress to false
      set my controller to ProgressBarController's initWithCancel() -- init progress
      controller's cancelButton's setTarget:me -- set up the button action
      controller's cancelButton's setAction:"cancelProgress:"
      controller's (setProgressValues given maximum:(count stuff)) -- set up details
      controller's indicator's setDoubleValue:0 -- (re)set
      controller's (setTextFields given top:"", bottom:"")
      controller's progressWindow's setTitle:"Progress Example"
      controller's showWindow()
      process(stuff)
   on error errmess number errnum
      set my |error| to {errmess:errmess, errnum:errnum}
   end try
end doStuff:

to process(theItems) -- process with progress
   repeat with anItem in theItems
      delay 0.5 -- slow down a bit
      controller's (updateProgress by 1)
      controller's (setTextFields given top:"Processing...", bottom:(anItem as text))
      if stopProgress then exit repeat -- check for the cancel button
      log anItem as text -- do whatever with the item
   end repeat
   delay 1
   controller's closeWindow()
end process

on cancelProgress:sender -- action when the cancel button is pressed
   set my stopProgress to true
   # whatever
   controller's closeWindow()
   set theNumber to controller's indicator's doubleValue -- current progress
   display alert "Progress Canceled" message "The progress was stopped after " & theNumber & " items." giving up after 3
end cancelProgress:
 
  • Like
Reactions: Nick Passmore

whirl

macrumors member
Original poster
Jul 24, 2011
49
1
There are very few resources for AppleScriptObjC Xcode projects on the interweb, but you'll find more help on the subject over at the Macscripter forums, where I occasionally hang out myself, and help out with AppleScriptObjC code solutions.

I joined the Macscripter forums a few days ago but I don't have the option to post. I believe I have to be verified but not sure how long that takes.

But in all honesty you are better off learning Swift instead these days, as Apple stopped support and updates for AppleScript a few years ago, and have even removed the New AppleScript project template form Xcode 14.
I had a quick look at swift and it looks a bit over my head at the moment. I just liked the idea of trying to put a few of my simple AppleScript files into a GUI for users.

Interesting you mentioned AppleScript been dropped from Xcode 14 because I did download that thinking the bug fix for connecting things up may have been fixed but there is no longer an option to choose AppleScript project.

Thanks for the above explanation, I will try that tonight and just see if I can get it going for the sake of trying :)
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.