You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI中FileSaver iOS端无保存UI及路径不存在问题排查

解决.NET MAUI中FileSaver iOS端无保存UI且路径错误的问题

问题原因

iOS的沙盒机制与Android存在差异:默认的SaveAsync方法会将文件写入应用私有沙盒目录,不会触发系统保存路径选择UI,且沙盒路径的访问权限限制会导致后续EnsureSuccess()抛出"路径不存在"错误;而Android端默认调用系统文件选择器,允许用户指定公共保存位置。

解决方案

要实现iOS与Android一致的保存交互流程,需手动调用iOS系统文件选择器并配置必要权限,步骤如下:

1. 添加iOS权限配置

在iOS项目的Info.plist中添加权限描述,确保应用能访问文件系统和相册:

<key>NSDocumentsFolderUsageDescription</key>
<string>需要访问文件目录以保存收据PDF</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要访问相册以保存收据PDF</string>

2. 实现平台特定的文件选择服务

创建跨平台接口,并在iOS项目中实现系统文件选择逻辑:

共享项目接口

public interface IFileSavePicker
{
    Task<NSUrl> PickSaveLocationAsync(string fileName);
}

iOS平台实现

[assembly: Dependency(typeof(IOSFileSavePicker))]
namespace YourAppNamespace.iOS
{
    public class IOSFileSavePicker : IFileSavePicker
    {
        public Task<NSUrl> PickSaveLocationAsync(string fileName)
        {
            var tcs = new TaskCompletionSource<NSUrl>();
            
            // 指定PDF类型的UTI
            var documentPicker = new UIDocumentPickerViewController(
                new string[] { "com.adobe.pdf" }, 
                UIDocumentPickerMode.ExportToService);
            
            documentPicker.DidPickDocumentAtUrls += (sender, args) =>
            {
                var selectedUrl = args.Urls.FirstOrDefault();
                if (selectedUrl != null)
                {
                    // 请求持久访问权限
                    bool hasAccess = selectedUrl.StartAccessingSecurityScopedResource();
                    tcs.SetResult(hasAccess ? selectedUrl : null);
                }
                else
                {
                    tcs.SetResult(null);
                }
            };
            
            documentPicker.WasCancelled += (sender, args) => tcs.SetResult(null);
            
            // 获取当前窗口并弹出选择器
            var keyWindow = UIApplication.SharedApplication.ConnectedScenes
                .OfType<UIWindowScene>()
                .FirstOrDefault()?
                .Windows
                .FirstOrDefault(w => w.IsKeyWindow);
            
            keyWindow?.RootViewController.PresentViewController(documentPicker, true, null);
            
            return tcs.Task;
        }
    }
}

3. 修改共享代码适配平台逻辑

在原代码中添加平台分支,iOS端使用自定义选择器,其他平台保留原FileSaver逻辑:

byte[] bytes = feeHistoryViewModel.networkCalls.PostAsyncWithHeaderPDF("studentfeeaudit/receipt/-1/" + ob.PaymentId + "/" + ob.SessionId).Result;

if (bytes != null)
{
    using var stream = new MemoryStream(bytes);
    string fileName = $"{AccountInfo.userAccount.Name} {ob.InvoiceNumber} {ob.Date}_fees.pdf";
    string savedFilePath = null;

#if IOS
    var savePicker = DependencyService.Get<IFileSavePicker>();
    var selectedUrl = await savePicker.PickSaveLocationAsync(fileName);
    
    if (selectedUrl == null)
    {
        // 用户取消保存
        return;
    }
    
    try
    {
        // 将流写入选择的文件
        stream.Position = 0;
        using var fileStream = new FileStream(selectedUrl.Path, FileMode.Create);
        await stream.CopyToAsync(fileStream, cancellationTokenSource.Token);
        savedFilePath = selectedUrl.Path;
    }
    finally
    {
        // 释放安全访问权限
        selectedUrl.StopAccessingSecurityScopedResource();
    }
#else
    var fileSaverResult = await fileSaver.SaveAsync(fileName, stream, cancellationTokenSource.Token);
    fileSaverResult.EnsureSuccess();
    savedFilePath = fileSaverResult.FilePath;
#endif

    if (!string.IsNullOrEmpty(savedFilePath))
    {
        var request = new NotificationRequest
        {
            NotificationId = 1000,
            Title = fileName,
            Description = savedFilePath,
            ReturningData = savedFilePath
        };
        await LocalNotificationCenter.Current.Show(request);
    }
}

说明

  • iOS端通过UIDocumentPickerViewController触发系统保存选择UI,用户可选择保存到文件APP、相册等公共位置,避免沙盒权限问题。
  • 必须调用StartAccessingSecurityScopedResource和StopAccessingSecurityScopedResource来管理安全范围资源的访问权限,防止内存泄漏和权限错误。

内容的提问来源于stack exchange,提问作者Harshit Agarwal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 15:40:20