📋 Feature Description
Add an App Bundle Explorer to DebugSwift that allows developers to browse app bundle contents, view embedded resources, extract and share files, search within bundle, and preview assets.
🎯 Motivation
The app bundle contains critical resources that developers often need to inspect during debugging:
- Images and asset catalogs
- Configuration files (plist, json, xml)
- Localizations and strings files
- HTML/CSS resources
- Embedded databases
- Certificates and provisioning profiles
- Fonts and other assets
Currently, developers must:
- Connect device to Mac
- Access via Xcode's Devices window
- Use terminal commands
- Or access files programmatically
An in-app bundle explorer would make resource inspection immediate and intuitive.
✨ Proposed Features
Core Functionality
- Bundle Browser: Navigate app bundle file structure
- File Preview: View images, text, JSON, plist, etc.
- Search: Find files by name or content
- File Details: Size, type, permissions, dates
- Extract/Share: Export files via share sheet
- Asset Catalog Viewer: Browse asset catalog contents
File Viewers
- Image Viewer: Display images with metadata
- Text Viewer: View text files with syntax highlighting
- JSON/Plist Viewer: Formatted, searchable view
- PDF Viewer: View PDF documents
- Audio/Video Player: Play media files
- Archive Viewer: Browse .zip, .tar files
- Certificate Viewer: Inspect certificates and profiles
Search & Analysis
- File Search: Search by name, extension, size
- Content Search: Search within text files
- Size Analysis: Find large files
- Duplicate Detection: Identify duplicate resources
- Unused Resource Detection: Find unused assets
- Compression Analysis: Identify optimization opportunities
Developer Tools
- Path Copy: Copy file paths
- Quick Look: System quick look integration
- File Info: Detailed file metadata
- Hex Viewer: View raw file data
- Compare Files: Diff viewer for text files
🎨 UI/UX Design
Bundle Explorer Screen
┌─────────────────────────────────────────┐
│ App Bundle Explorer [Search 🔍] │
├─────────────────────────────────────────┤
│ Path: / MyApp.app │
│ Size: 45.2 MB │
├─────────────────────────────────────────┤
│ 📁 Assets.car 12.3 MB │
│ 📁 Frameworks 18.5 MB │
│ 📁 Localizations 2.1 MB │
│ 📄 Info.plist 8.2 KB │
│ 📄 embedded.mobileprovision 12.1 KB │
│ 📁 Base.lproj 1.5 MB │
│ 📁 en.lproj 890 KB │
│ 📁 pt-BR.lproj 845 KB │
│ 🖼️ AppIcon60x60@2x.png 15.2 KB │
│ 🖼️ LaunchImage.png 145 KB │
│ 📄 config.json 2.3 KB │
│ 📄 GoogleService-Info.plist 4.1 KB │
├─────────────────────────────────────────┤
│ Quick Stats │
│ Total Files: 1,234 │
│ Images: 456 | Text: 89 | Other: 689│
│ Largest: Frameworks/Lib.dylib (8.2MB) │
└─────────────────────────────────────────┘
File Detail View
┌─────────────────────────────────────────┐
│ < Bundle config.json │
├─────────────────────────────────────────┤
│ Preview │
│ ┌─────────────────────────────────────┐ │
│ │ { │ │
│ │ "apiBaseURL": "https://...", │ │
│ │ "environment": "production", │ │
│ │ "features": { │ │
│ │ "analytics": true, │ │
│ │ "crashReporting": true │ │
│ │ }, │ │
│ │ "version": "1.0.0" │ │
│ │ } │ │
│ └─────────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ File Information │
│ Name: config.json │
│ Path: /MyApp.app/config.json │
│ Size: 2.3 KB (2,345 bytes) │
│ Type: JSON Document │
│ Modified: Mar 06, 2026 10:30 AM │
│ Permissions: -rw-r--r-- │
├─────────────────────────────────────────┤
│ Actions │
│ [Copy Path] │
│ [Share File] │
│ [View as Hex] │
│ [Quick Look] │
└─────────────────────────────────────────┘
Asset Catalog Viewer
┌─────────────────────────────────────────┐
│ < Bundle Assets.car │
├─────────────────────────────────────────┤
│ Asset Catalog: 12.3 MB (456 assets) │
├─────────────────────────────────────────┤
│ [Images] [Colors] [Data] [All] │
├─────────────────────────────────────────┤
│ Images (234) │
│ │
│ ┌────┐ AppIcon │
│ │ 📱 │ 60x60@2x (15.2 KB) │
│ └────┘ 60x60@3x (22.8 KB) │
│ [Preview] │
│ │
│ ┌────┐ LaunchImage │
│ │ 🚀 │ 375x812@2x (145 KB) │
│ └────┘ 414x896@3x (189 KB) │
│ [Preview] │
│ │
│ ┌────┐ icon-home │
│ │ 🏠 │ 24x24@1x (1.2 KB) │
│ └────┘ 24x24@2x (2.4 KB) │
│ [Preview] │
│ │
│ Colors (12) │
│ ● AccentColor #007AFF │
│ ● BackgroundColor #FFFFFF │
│ ● TextColor #000000 │
│ │
│ [Load More...] │
└─────────────────────────────────────────┘
Search Results
┌─────────────────────────────────────────┐
│ < Search "config" │
├─────────────────────────────────────────┤
│ 8 results found │
├─────────────────────────────────────────┤
│ Files (3) │
│ 📄 config.json 2.3 KB │
│ /MyApp.app/ │
│ │
│ 📄 AppConfig.plist 1.8 KB │
│ /MyApp.app/Resources/ │
│ │
│ 📄 firebase-config.json 4.1 KB │
│ /MyApp.app/ │
├─────────────────────────────────────────┤
│ Content Matches (5) │
│ 📄 Info.plist │
│ ...UIConfigurationKey... │
│ │
│ 📄 settings.json │
│ ..."configurationVersion": "2.0"... │
└─────────────────────────────────────────┘
🔧 Technical Implementation
Architecture
public class AppBundleExplorer {
public static let shared = AppBundleExplorer()
public func getBundleContents() -> BundleContents
public func searchFiles(query: String) -> [BundleItem]
public func previewFile(_ item: BundleItem) -> FilePreview?
public func extractAssets() -> [AssetItem]
}
struct BundleContents {
let rootPath: URL
let items: [BundleItem]
let totalSize: Int64
let statistics: BundleStatistics
}
struct BundleItem: Identifiable {
let id: UUID
let name: String
let path: URL
let type: FileType
let size: Int64
let createdAt: Date
let modifiedAt: Date
let isDirectory: Bool
enum FileType {
case image, text, json, plist, binary, archive, media, other
}
}
struct BundleStatistics {
let totalFiles: Int
let totalDirectories: Int
let imageCount: Int
let textFileCount: Int
let largestFile: BundleItem?
let typeBreakdown: [BundleItem.FileType: Int]
}
Bundle Enumeration
func getBundleContents(at path: URL? = nil) -> BundleContents {
let bundlePath = path ?? Bundle.main.bundleURL
let fileManager = FileManager.default
var items: [BundleItem] = []
var totalSize: Int64 = 0
guard let enumerator = fileManager.enumerator(
at: bundlePath,
includingPropertiesForKeys: [
.fileSizeKey,
.isDirectoryKey,
.creationDateKey,
.contentModificationDateKey
]
) else {
return BundleContents(
rootPath: bundlePath,
items: [],
totalSize: 0,
statistics: BundleStatistics(
totalFiles: 0,
totalDirectories: 0,
imageCount: 0,
textFileCount: 0,
largestFile: nil,
typeBreakdown: [:]
)
)
}
for case let fileURL as URL in enumerator {
do {
let resourceValues = try fileURL.resourceValues(
forKeys: [
.fileSizeKey,
.isDirectoryKey,
.creationDateKey,
.contentModificationDateKey
]
)
let isDirectory = resourceValues.isDirectory ?? false
let size = Int64(resourceValues.fileSize ?? 0)
let item = BundleItem(
id: UUID(),
name: fileURL.lastPathComponent,
path: fileURL,
type: determineFileType(fileURL),
size: size,
createdAt: resourceValues.creationDate ?? Date(),
modifiedAt: resourceValues.contentModificationDate ?? Date(),
isDirectory: isDirectory
)
items.append(item)
if !isDirectory {
totalSize += size
}
} catch {
print("Error reading file: \(error)")
}
}
let statistics = calculateStatistics(items)
return BundleContents(
rootPath: bundlePath,
items: items.sorted { $0.name < $1.name },
totalSize: totalSize,
statistics: statistics
)
}
func determineFileType(_ url: URL) -> BundleItem.FileType {
let ext = url.pathExtension.lowercased()
switch ext {
case "png", "jpg", "jpeg", "gif", "heic", "svg":
return .image
case "txt", "md", "swift", "m", "h", "c", "cpp":
return .text
case "json":
return .json
case "plist":
return .plist
case "zip", "tar", "gz":
return .archive
case "mp4", "mov", "mp3", "m4a":
return .media
default:
// Check if it's a text file
if let data = try? Data(contentsOf: url, options: .mappedIfSafe),
String(data: data, encoding: .utf8) != nil {
return .text
}
return .binary
}
}
Asset Catalog Extraction
func extractAssets() -> [AssetItem] {
var assets: [AssetItem] = []
// Find Assets.car file
guard let assetsURL = Bundle.main.url(
forResource: "Assets",
withExtension: "car"
) else {
return []
}
// Use CUICatalog to read asset catalog (private API, for debug builds only)
#if DEBUG
if let catalog = CUICatalog(url: assetsURL) {
for rendition in catalog.allRenditions() {
let asset = AssetItem(
name: rendition.name,
type: determineAssetType(rendition),
size: rendition.data.count,
scale: rendition.scale,
idiom: rendition.idiom
)
assets.append(asset)
}
}
#endif
return assets
}
struct AssetItem: Identifiable {
let id = UUID()
let name: String
let type: AssetType
let size: Int
let scale: CGFloat
let idiom: String
enum AssetType {
case image, color, data
}
}
File Preview
func previewFile(_ item: BundleItem) -> FilePreview? {
switch item.type {
case .image:
return previewImage(item)
case .text, .json:
return previewText(item)
case .plist:
return previewPlist(item)
default:
return nil
}
}
func previewImage(_ item: BundleItem) -> FilePreview {
guard let image = UIImage(contentsOfFile: item.path.path) else {
return FilePreview(type: .error, content: "Failed to load image")
}
let metadata = """
Dimensions: \(Int(image.size.width)) x \(Int(image.size.height))
Scale: \(image.scale)x
Orientation: \(image.imageOrientation.rawValue)
"""
return FilePreview(type: .image, content: image, metadata: metadata)
}
func previewText(_ item: BundleItem) -> FilePreview {
guard let content = try? String(contentsOf: item.path, encoding: .utf8) else {
return FilePreview(type: .error, content: "Failed to read file")
}
return FilePreview(type: .text, content: content)
}
func previewPlist(_ item: BundleItem) -> FilePreview {
guard let data = try? Data(contentsOf: item.path),
let plist = try? PropertyListSerialization.propertyList(
from: data,
options: [],
format: nil
) else {
return FilePreview(type: .error, content: "Failed to parse plist")
}
let jsonData = try? JSONSerialization.data(withJSONObject: plist, options: .prettyPrinted)
let jsonString = jsonData.flatMap { String(data: $0, encoding: .utf8) } ?? "Error"
return FilePreview(type: .text, content: jsonString)
}
struct FilePreview {
enum PreviewType {
case image, text, error
}
let type: PreviewType
let content: Any
let metadata: String?
init(type: PreviewType, content: Any, metadata: String? = nil) {
self.type = type
self.content = content
self.metadata = metadata
}
}
Search Functionality
func searchFiles(query: String, in path: URL? = nil) -> [BundleItem] {
let contents = getBundleContents(at: path)
let lowercaseQuery = query.lowercased()
return contents.items.filter { item in
// Search by filename
if item.name.lowercased().contains(lowercaseQuery) {
return true
}
// Search within text files
if item.type == .text || item.type == .json {
if let content = try? String(contentsOf: item.path, encoding: .utf8),
content.lowercased().contains(lowercaseQuery) {
return true
}
}
return false
}
}
📝 Implementation Checklist
Phase 1: Basic Explorer
Phase 2: Enhanced Preview
Phase 3: Advanced Features
🧪 Testing Requirements
📚 Documentation
### App Bundle Explorer
- Browse app bundle file structure
- View embedded resources
- Extract and share files
- Search within bundle
- Preview assets and configurations
// Access bundle explorer
DebugSwift.Resources.showBundleExplorer()
// Search files
let results = DebugSwift.Bundle.search("config")
// Extract specific file
DebugSwift.Bundle.export(file: "config.json")
🎯 Success Criteria
- Browse complete app bundle
- Preview images, text, JSON, plist files
- Search by filename and content
- Extract and share files
- View asset catalog contents
- Display file metadata
- Minimal performance impact
📊 Priority
Medium - Useful for resource inspection.
🏷️ Labels
enhancement, feature, resources, bundle, assets, medium-priority
📋 Feature Description
Add an App Bundle Explorer to DebugSwift that allows developers to browse app bundle contents, view embedded resources, extract and share files, search within bundle, and preview assets.
🎯 Motivation
The app bundle contains critical resources that developers often need to inspect during debugging:
Currently, developers must:
An in-app bundle explorer would make resource inspection immediate and intuitive.
✨ Proposed Features
Core Functionality
File Viewers
Search & Analysis
Developer Tools
🎨 UI/UX Design
Bundle Explorer Screen
File Detail View
Asset Catalog Viewer
Search Results
🔧 Technical Implementation
Architecture
Bundle Enumeration
Asset Catalog Extraction
File Preview
Search Functionality
📝 Implementation Checklist
Phase 1: Basic Explorer
Phase 2: Enhanced Preview
Phase 3: Advanced Features
🧪 Testing Requirements
📚 Documentation
🎯 Success Criteria
📊 Priority
Medium - Useful for resource inspection.
🏷️ Labels
enhancement,feature,resources,bundle,assets,medium-priority