You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何使用Swift/Objective-C直接访问外部USB存储并写入文件

我之前刚好解决过完全一样的需求——直接在iOS应用里读写外部USB存储,不用绕临时文件的弯路。下面给你详细讲Swift的实现方案,Objective-C的思路完全一致,我也会附上关键代码:

核心实现思路:利用UIDocumentPickerViewController获取外部存储的可写URL

iOS的沙箱机制要求,应用不能直接访问外部存储,必须通过系统提供的文档选择器获取授权。通过UIDocumentPickerViewController,用户可以主动选择USB存储里的目标文件夹,我们就能拿到带有读写权限的URL,之后直接在该位置操作文件即可。

步骤1:配置Info.plist权限

首先需要在Info.plist里添加两个关键配置,确保应用支持原地操作外部文件:

  • 添加LSSupportsOpeningDocumentsInPlace并设置为YES:允许应用直接在外部存储位置读写文件,无需复制到沙箱。
  • 添加UIFileSharingEnabled并设置为YES(可选,但如果需要让用户通过iTunes共享文件的话建议加上)。

步骤2:弹出文档选择器,让用户选择USB存储的目标位置

下面是Swift代码示例,触发选择器让用户选择USB里的文件夹:

import UIKit

class FileWriterViewController: UIViewController, UIDocumentPickerDelegate {

    // 调用这个方法弹出选择器(比如绑定到按钮点击事件)
    @IBAction func selectUSBStorageFolder(_ sender: UIButton) {
        // 创建选择器,允许选择文件夹并获得原地读写权限(asCopy: false是关键)
        let documentPicker = UIDocumentPickerViewController(
            forOpeningContentTypes: [.folder],
            asCopy: false
        )
        documentPicker.delegate = self
        documentPicker.allowsMultipleSelection = false
        present(documentPicker, animated: true)
    }

    // 用户选择完成后的回调
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        guard let targetFolderURL = urls.first else {
            dismiss(animated: true)
            return
        }
        // 拿到目标文件夹URL后,直接执行写入操作
        writeFileToTargetFolder(folderURL: targetFolderURL)
        dismiss(animated: true)
    }

    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
        dismiss(animated: true)
    }
}

步骤3:直接写入文件到USB存储

因为是外部存储,需要用NSFileCoordinator来协调文件操作,避免和其他应用的读写冲突。下面是写入文件的示例代码:

extension FileWriterViewController {
    func writeFileToTargetFolder(folderURL: URL) {
        // 示例:准备要写入的文件内容
        let sampleContent = "这是写入USB存储的测试内容".data(using: .utf8)!
        // 拼接目标文件的完整URL(比如在选中的文件夹里创建test.txt)
        let targetFileURL = folderURL.appendingPathComponent("test.txt")

        let fileCoordinator = NSFileCoordinator()
        var coordinatorError: NSError?

        // 协调写入操作
        fileCoordinator.coordinate(
            writingItemAt: targetFileURL,
            options: .forReplacing, // 如果文件已存在则替换
            error: &coordinatorError
        ) { (safeFileURL) in
            do {
                // 直接写入到安全的URL(系统会处理权限和冲突)
                try sampleContent.write(to: safeFileURL, options: .atomic)
                DispatchQueue.main.async {
                    print("文件成功写入USB存储!")
                    // 这里可以给用户弹出成功提示
                }
            } catch {
                DispatchQueue.main.async {
                    print("写入失败:\(error.localizedDescription)")
                }
            }
        }

        if let error = coordinatorError {
            print("文件协调失败:\(error.localizedDescription)")
        }
    }
}

Objective-C版本关键代码参考

如果你的项目用Objective-C,核心逻辑完全一致,下面是关键部分的代码:

#import "FileWriterViewController.h"

@interface FileWriterViewController () <UIDocumentPickerDelegate>
@end

@implementation FileWriterViewController

- (IBAction)selectUSBStorageFolder:(UIButton *)sender {
    UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] 
        initWithOpeningContentTypes:@[UTTypeFolder] 
        asCopy:NO];
    picker.delegate = self;
    picker.allowsMultipleSelection = NO;
    [self presentViewController:picker animated:YES completion:nil];
}

- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
    NSURL *targetFolderURL = urls.firstObject;
    [self writeFileToTargetFolder:targetFolderURL];
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (void)writeFileToTargetFolder:(NSURL *)folderURL {
    NSData *sampleContent = [@"这是写入USB存储的测试内容" dataUsingEncoding:NSUTF8StringEncoding];
    NSURL *targetFileURL = [folderURL URLByAppendingPathComponent:@"test.txt"];

    NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
    NSError *coordinatorError = nil;

    [coordinator coordinateWritingItemAtURL:targetFileURL 
        options:NSFileCoordinatorWritingOptionsForReplacing 
        error:&coordinatorError 
        byAccessor:^(NSURL *safeFileURL) {
            NSError *writeError = nil;
            [sampleContent writeToURL:safeFileURL options:NSDataWritingAtomic error:&writeError];
            if (writeError) {
                NSLog(@"写入失败:%@", writeError.localizedDescription);
            } else {
                NSLog(@"文件成功写入USB存储!");
            }
        }];

    if (coordinatorError) {
        NSLog(@"文件协调失败:%@", coordinatorError.localizedDescription);
    }
}

@end

关键优化:持久化存储权限

用户每次选择文件夹太麻烦?可以把拿到的URL保存为书签(Bookmark),下次启动APP时直接恢复权限,不用再让用户选择:

// 保存书签到UserDefaults
func saveBookmark(for url: URL) {
    do {
        let bookmarkData = try url.bookmarkData(
            options: .withSecurityScope,
            includingResourceValuesForKeys: nil,
            relativeTo: nil
        )
        UserDefaults.standard.set(bookmarkData, forKey: "USBStorageBookmark")
    } catch {
        print("保存书签失败:\(error.localizedDescription)")
    }
}

// 从UserDefaults恢复书签并获取URL
func restoreUSBStorageURL() -> URL? {
    guard let bookmarkData = UserDefaults.standard.data(forKey: "USBStorageBookmark") else {
        return nil
    }
    var isStale = false
    do {
        let url = try URL(
            resolvingBookmarkData: bookmarkData,
            options: .withSecurityScope,
            relativeTo: nil,
            bookmarkDataIsStale: &isStale
        )
        // 如果书签过期,需要重新让用户选择
        if isStale {
            UserDefaults.standard.removeObject(forKey: "USBStorageBookmark")
            return nil
        }
        // 启动安全范围访问
        if url.startAccessingSecurityScopedResource() {
            defer { url.stopAccessingSecurityScopedResource() }
            return url
        }
    } catch {
        print("恢复书签失败:\(error.localizedDescription)")
    }
    return nil
}

注意:使用安全范围URL时,必须调用startAccessingSecurityScopedResource()stopAccessingSecurityScopedResource(),确保权限正确释放。

这个方案是苹果官方推荐的,完全符合iOS的安全规范,用户只需要选择一次目标文件夹(之后可以用书签记住),就能直接在APP内完成写入操作,体验比临时文件+Document Interaction Controller的方案流畅太多。

内容的提问来源于stack exchange,提问作者Federico Rotoli

火山引擎 最新活动