Seeking a Reliable Way to Refresh Finder for Custom Folder Icon Changes in a Sandboxed App

Hello everyone,

I'm developing a macOS application that programmatically sets custom icons for folders, and I've hit a wall trying to get Finder to display the icon changes consistently.

The Goal: To change a folder's icon using NSWorkspace.shared.setIcon and have Finder immediately show the new icon.

What I've Tried (The Refresh Mechanism):

After setting the icon, I attempt to force a Finder refresh using several sandbox-friendly techniques:

  • Updating the Modification Date (the "touch" method):

    try FileManager.default.setAttributes([.modificationDate: Date()], ofItemAtPath: pathToUse)
    
  • Notifying NSWorkspace:

    NSWorkspace.shared.noteFileSystemChanged(pathToUse)
    
  • Posting Distributed Notifications:

    DistributedNotificationCenter.default().post(name: Notification.Name("com.apple.Finder.FolderChanged"), object: pathToUse)
    

The Problem:

This combination of methods works perfectly, but only under specific conditions:

  1. When setting a custom icon on a folder for the first time.
  2. When changing the icon of an alias.

For any subsequent icon change on a regular folder, Finder stubbornly displays the old, cached icon. I've confirmed that the user can see the new icon by manually closing and reopening the folder window, but this is obviously not a user-friendly solution.

Investigating a Reset with AppleScript:

I've noticed that the AppleScript update command seems to force the kind of complete refresh I need:

tell application "Finder"
    update POSIX file "/path/to/your/folder"
end tell

Running this seems to reset the folder's state in Finder, effectively recreating the "first-time set" scenario where my other methods work.

However, the major roadblock is that I can't figure out how to reliably execute this from a sandboxed environment. I understand it likely requires specific scripting entitlements, but it's unclear which ones would be needed for this update command on a user-chosen folder, or if it's even permissible for the App Store.

My Questions:

  1. Is there a reliable, sandbox-safe way to make the standard Cocoa methods (noteFileSystemChanged, updating the modification date, etc.) work for subsequent icon updates on regular folders? Am I missing a step?

  2. If not, what is the correct way to configure a sandboxed app's entitlements to safely run the tell application "Finder" to update command for a folder the user has granted access to?

Any insight or alternative approaches would be greatly appreciated. Thank you

Answered by DTS Engineer in 844055022

I've noticed that the AppleScript update command seems to force the kind of complete refresh I need:

So, what this actually does is technically documented, though the source is more than a little obscure. From the Files.h header doc for the long deprecated "FNNotify" API:


 *  Discussion:
 *    FNNotify is used to notify system clients (such as the Finder) of
 *    modifications to the contents of a directory, specifically
 *    addition or removal of files or folders from the directory. The
 *    Finder and other system clients will refresh their views of the
 *    specified directory when they receive the change notification.
 *    FNNotify is not meant to notify the Finder of changes to a
 *    specific file (for example, changes to a file's type or creator);
 *    for that purpose, use a kAESync AppleEvent sent to the Finder.

kAESync is defined in the equally obscure "FinderRegistry.h", inside the Carbon framework:

/*
   The old Finder Event suite was 'FNDR'
   The new suite is 'fndr'
*/
enum {
  kAEFinderSuite                = 'fndr'
};

/*
  //////////////////////////////////////
   Finder Events
  //////////////////////////////////////
*/
enum {
  kAECleanUp                    = 'fclu',
  kAEEject                      = 'ejct',
  kAEEmpty                      = 'empt',
  kAEErase                      = 'fera',
  kAEGestalt                    = 'gstl',
  kAEPutAway                    = 'ptwy',
  kAERebuildDesktopDB           = 'rddb',
  kAESync                       = 'fupd',
  kAEInterceptOpen              = 'fopn'
};

SO, that's the event you need to send. That leads to here:

However, the major roadblock is that I can't figure out how to reliably execute this from a sandboxed environment. I understand it likely requires specific scripting entitlements, but it's unclear which ones would be needed for this update command on a user-chosen folder, or if it's even permissible for the App Store.

What have you actually tried here? I haven't tested this in any detail but, as far as I can tell, kAEFinderSuite/kAESync is one of the "safe" AppleEvent that your app should be allowed to send without any additional configuration.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I've noticed that the AppleScript update command seems to force the kind of complete refresh I need:

So, what this actually does is technically documented, though the source is more than a little obscure. From the Files.h header doc for the long deprecated "FNNotify" API:


 *  Discussion:
 *    FNNotify is used to notify system clients (such as the Finder) of
 *    modifications to the contents of a directory, specifically
 *    addition or removal of files or folders from the directory. The
 *    Finder and other system clients will refresh their views of the
 *    specified directory when they receive the change notification.
 *    FNNotify is not meant to notify the Finder of changes to a
 *    specific file (for example, changes to a file's type or creator);
 *    for that purpose, use a kAESync AppleEvent sent to the Finder.

kAESync is defined in the equally obscure "FinderRegistry.h", inside the Carbon framework:

/*
   The old Finder Event suite was 'FNDR'
   The new suite is 'fndr'
*/
enum {
  kAEFinderSuite                = 'fndr'
};

/*
  //////////////////////////////////////
   Finder Events
  //////////////////////////////////////
*/
enum {
  kAECleanUp                    = 'fclu',
  kAEEject                      = 'ejct',
  kAEEmpty                      = 'empt',
  kAEErase                      = 'fera',
  kAEGestalt                    = 'gstl',
  kAEPutAway                    = 'ptwy',
  kAERebuildDesktopDB           = 'rddb',
  kAESync                       = 'fupd',
  kAEInterceptOpen              = 'fopn'
};

SO, that's the event you need to send. That leads to here:

However, the major roadblock is that I can't figure out how to reliably execute this from a sandboxed environment. I understand it likely requires specific scripting entitlements, but it's unclear which ones would be needed for this update command on a user-chosen folder, or if it's even permissible for the App Store.

What have you actually tried here? I haven't tested this in any detail but, as far as I can tell, kAEFinderSuite/kAESync is one of the "safe" AppleEvent that your app should be allowed to send without any additional configuration.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi there, Thank you very much for your response. Unfortunately, I am still unable to refresh the icon. I don't have much experience, so it may just be a small oversight. I tried to prompt the method because I couldn't find any documentation. This is my final solution.

I don't get any errors, but the icon still does not refresh. (kAESync AppleEvent sent successfully)

Test code


//  ContentView.swift
//  test refresh folder icon

import SwiftUI
import AppKit
import Carbon

struct ContentView: View {
    @State private var selectedFolder: URL?
    @State private var statusMessage: String = "No folder chosen"

    var body: some View {
        VStack(spacing: 16) {
            Text(statusMessage)
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.horizontal)

            Button("Select Folder…", action: selectFolder)

            Button("Apply Test Icon & Refresh Finder", action: applyIcon)
                .disabled(selectedFolder == nil)
        }
        .padding()
        .frame(minWidth: 380, minHeight: 160)
    }

    // MARK: - Actions

    private func selectFolder() {
        let panel = NSOpenPanel()
        panel.canChooseFiles = false
        panel.canChooseDirectories = true
        panel.allowsMultipleSelection = false
        panel.prompt = "Choose"

        if panel.runModal() == .OK, let url = panel.url {
            selectedFolder = url
            statusMessage = "Selected: \(url.path)"
        }
    }

    private func applyIcon() {
        guard let folderURL = selectedFolder else { return }

        // For sandboxed apps, we must start accessing the security-scoped resource before use.
        guard folderURL.startAccessingSecurityScopedResource() else {
            statusMessage = "❌ Failed to access folder. Check sandbox entitlements."
            return
        }
        defer { folderURL.stopAccessingSecurityScopedResource() }

        // Simple colored square icon
        let iconSize = CGFloat(512)
        let icon = NSImage(size: NSSize(width: iconSize, height: iconSize), flipped: false) { rect in
            NSColor.systemOrange.setFill()
            rect.fill()
            return true
        }

        do {
            try NSWorkspace.shared.setIcon(icon, forFile: folderURL.path, options: [])
            sendKAESyncEvent(for: folderURL)
            statusMessage = "Icon updated successfully"
        } catch {
            statusMessage = "❌ Failed: \(error.localizedDescription)"
        }
    }

    // MARK: - DTS Engineer suggested kAESync AppleEvent

    private func sendKAESyncEvent(for url: URL) {
        let finderDescriptor = NSAppleEventDescriptor(bundleIdentifier: "com.apple.finder")
        
        // Create the AppleEvent for kAEFinderSuite/kAESync ('fndr'/'fupd')
        let appleEvent = NSAppleEventDescriptor(eventClass: AEEventClass(0x666e6472), // 'fndr'
                                                eventID: AEEventID(0x66757064),   // 'fupd'
                                                targetDescriptor: finderDescriptor,
                                                returnID: AEReturnID(kAutoGenerateReturnID),
                                                transactionID: AETransactionID(kAnyTransactionID))
        
        // Set the direct object parameter to the folder URL
        let fileDesc = NSAppleEventDescriptor(fileURL: url)
        appleEvent.setDescriptor(fileDesc, forKeyword: keyDirectObject)

        // Send the AppleEvent
        do {
            _ = try appleEvent.sendEvent(options: [.noReply], timeout: 1.0)
            NSLog("kAESync AppleEvent sent successfully")
        } catch {
            NSLog("kAESync AppleEvent failed: \(error.localizedDescription)")
        }
    }
}

#Preview {
    ContentView()
}

Question

  1. Do you have any idea what might be causing the issue?
  2. Could you please provide more information?

Many thanks for any insight!

Seeking a Reliable Way to Refresh Finder for Custom Folder Icon Changes in a Sandboxed App
 
 
Q