@@ -16,19 +16,11 @@ class BrowserModel: ObservableObject {
1616 @Published var canNavigateUp : Bool = false
1717 @Published var showingFileImporter : Bool = false
1818
19- private let fileManager = FileManager . default
20- private let imageTypes : Set < String > = {
21- var types = Set < String > ( )
22-
23- // Common image extensions
24- let extensions = [ " jpg " , " jpeg " , " png " , " gif " , " bmp " , " tiff " , " tif " , " heic " , " heif " , " webp " , " svg " , " ico " ]
19+ // Caches to speed up metadata/icon recomputation in large directories
20+ private var fileItemCache : [ URL : FileItem ] = [ : ]
21+ private var iconNameCache : [ String : String ] = [ : ] // key: UTI identifier
2522
26- for ext in extensions {
27- types. insert ( ext. lowercased ( ) )
28- }
29-
30- return types
31- } ( )
23+ private let fileManager = FileManager . default
3224
3325 init ( ) {
3426 let documentsURL = fileManager. urls ( for: . documentDirectory, in: . userDomainMask) . first
@@ -89,35 +81,40 @@ class BrowserModel: ObservableObject {
8981 let iconName : String
9082 if isDir {
9183 iconName = " folder.fill "
92- } else if uti? . conforms ( to: UTType . image) ?? false {
93- iconName = " photo "
94- } else if uti? . conforms ( to: UTType . movie) ?? false {
95- iconName = " film "
96- } else if uti? . conforms ( to: UTType . text) ?? false {
97- iconName = " doc.text "
98- } else if uti? . conforms ( to: UTType . sourceCode) ?? false {
99- iconName = " doc.text.fill "
10084 } else {
101- iconName = " doc "
85+ iconName = self . iconName ( for: uti)
86+ }
87+
88+ // Reuse cached FileItem when unchanged to avoid recomputing
89+ if let cached = fileItemCache [ url] ,
90+ cached. isDirectory == isDir,
91+ cached. fileSize == fileSize,
92+ cached. modificationDate == modDate,
93+ cached. uti == uti {
94+ fileItems. append ( cached)
95+ } else {
96+ let fileItem = FileItem (
97+ url: url,
98+ name: url. lastPathComponent,
99+ iconName: iconName,
100+ isDirectory: isDir,
101+ fileSize: fileSize,
102+ modificationDate: modDate,
103+ uti: uti,
104+ isAnimatedGif: isAnimatedGif,
105+ isVideo: isVideo
106+ )
107+ fileItems. append ( fileItem)
108+ fileItemCache [ url] = fileItem
102109 }
103-
104- let fileItem = FileItem (
105- url: url,
106- name: url. lastPathComponent,
107- iconName: iconName,
108- isDirectory: isDir,
109- fileSize: fileSize,
110- modificationDate: modDate,
111- uti: uti,
112- isAnimatedGif: isAnimatedGif,
113- isVideo: isVideo
114- )
115- fileItems. append ( fileItem)
116110 }
117111
118112 fileItems. sort { $0. name. localizedCaseInsensitiveCompare ( $1. name) == . orderedAscending }
119113
120114 self . items = fileItems
115+ // Prune cache to current directory entries to bound memory usage
116+ let currentURLs = Set ( fileItems. map { $0. url } )
117+ fileItemCache = fileItemCache. filter { currentURLs. contains ( $0. key) }
121118 print ( " Loaded \( items. count) items. " )
122119
123120 updateNavigationState ( )
@@ -165,15 +162,46 @@ class BrowserModel: ObservableObject {
165162
166163 func isImageFile( _ item: FileItem ) -> Bool {
167164 guard !item. isDirectory else { return false }
168- let ext = item. url. pathExtension. lowercased ( )
169- return imageTypes. contains ( ext)
165+
166+ // Prefer the UTI from metadata; if missing, derive from the filename extension
167+ let type = item. uti ?? UTType ( filenameExtension: item. url. pathExtension. lowercased ( ) )
168+
169+ if let type {
170+ return type. conforms ( to: . rawImage) || type. conforms ( to: . image)
171+ }
172+
173+ return false
170174 }
171175
172176 func openDirectory( _ item: FileItem ) {
173177 self . currentDirectory = item. url
174178 loadCurrentDirectory ( )
175179 }
176180
181+ // Returns an SF Symbol name for a given UTI, with simple caching
182+ private func iconName( for uti: UTType ? ) -> String {
183+ guard let uti = uti else { return " doc " }
184+ let key = uti. identifier
185+ if let cached = iconNameCache [ key] {
186+ return cached
187+ }
188+
189+ let name : String
190+ if uti. conforms ( to: UTType . rawImage) || uti. conforms ( to: UTType . image) {
191+ name = " photo "
192+ } else if uti. conforms ( to: UTType . movie) {
193+ name = " film "
194+ } else if uti. conforms ( to: UTType . text) {
195+ name = " doc.text "
196+ } else if uti. conforms ( to: UTType . sourceCode) {
197+ name = " doc.text.fill "
198+ } else {
199+ name = " doc "
200+ }
201+ iconNameCache [ key] = name
202+ return name
203+ }
204+
177205 private func updateNavigationState( ) {
178206 let homeDirectory = fileManager. homeDirectoryForCurrentUser
179207 canNavigateUp = currentDirectory. path != homeDirectory. path &&
0 commit comments