Skip to content

iOS: a partial swipe-down on the photo picker sheet cancels the pick, and the later selection is dropped #2243

Closed

Description

@chrisbobbe

Describe the bug

On iOS, with file_picker 13.1.0, if the user starts to swipe the picker sheet down and then lets it spring back, pickFiles completes right away with an empty list, as if the user had cancelled. The picker stays open. If the user then selects files and confirms, nothing is returned for them: pickFiles has already completed.

I reproduced this with the photo picker (FileType.media). I couldn't reproduce it with the document picker (FileType.any).

Platform

  • Android
  • iOS
  • Web
  • Desktop

Platform OS version

  • iOS 26.6 (physical device)
  • iOS 17.5 (simulator; see video below)

How are you picking?

Here's code for a sample app you can use to reproduce the issue:

sample app
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: ReproPage()));

class ReproPage extends StatefulWidget {
  const ReproPage({super.key});

  @override
  State<ReproPage> createState() => _ReproPageState();
}

class _ReproPageState extends State<ReproPage> {
  final List<String> _log = [];

  void _addLog(String line) {
    final now = DateTime.now();
    final time = [now.hour, now.minute, now.second]
      .map((n) => n.toString().padLeft(2, '0')).join(':');
    setState(() => _log.add('$time  $line'));
  }

  Future<void> _pick(FileType type) async {
    _addLog('pickFiles(type: ${type.name}) called');
    try {
      final files = await FilePicker.pickFiles(type: type);
      _addLog('pickFiles returned ${files.length} file(s)'
        '${files.isEmpty ? '' : ': ${files.map((f) => f.name).join(', ')}'}');
    } catch (e) {
      _addLog('pickFiles threw: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('file_picker swipe repro')),
      body: Column(children: [
        Padding(
          padding: const EdgeInsets.all(16),
          child: Wrap(spacing: 16, children: [
            ElevatedButton(
              onPressed: () => _pick(FileType.media),
              child: const Text('Pick media')),
            ElevatedButton(
              onPressed: () => _pick(FileType.any),
              child: const Text('Pick any file')),
          ])),
        Expanded(child: ListView(
          padding: const EdgeInsets.symmetric(horizontal: 16),
          children: [for (final line in _log) Text(line)])),
      ]));
  }
}

Details to reproduce the issue

  1. flutter create --platforms=ios file_picker_swipe_repro, then cd file_picker_swipe_repro.
  2. flutter pub add file_picker:13.1.0.
  3. Replace lib/main.dart with the code above.
  4. Run it on an iOS device: flutter run.
  5. Tap Pick media. The photo picker opens as a sheet.
  6. Put a finger on the sheet's top bar (not on the photo grid, which scrolls instead). Drag down about a third of the screen, and let go so the sheet springs back. The picker stays open.
  7. Select a photo, and tap Add.

Expected: nothing is logged at step 6. After step 7, the log shows pickFiles returned 1 file(s): … with the photo's name.

Actual: at step 6, the log already shows pickFiles returned 0 file(s). At step 7 the picker closes, but nothing more is logged: the selection is lost.

With Pick any file (the document picker), the equivalent steps work as expected.

Error Log
N/A

Screenshots and/or video

Screen.Recording.2026-10-09.at.3.06.15.PM.mov

Flutter Version details

Details
$ flutter doctor -v
[✓] Flutter (Channel main, 3.49.0-1.0.pre-209, on macOS 26.5.1 25F80 darwin-arm64, locale en-US) [2.9s]
    • Flutter version 3.49.0-1.0.pre-209 on channel main at /Users/chrisbobbe/.local/lib/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 3a8e191c9e (10 days ago), 2026-09-29 18:38:24 +0000
    • Engine revision 9c931498d5
    • Dart version 3.14.0 (build 3.14.0-271.0.dev)
    • DevTools version 2.61.0-dev.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations,
      enable-native-assets, enable-record-use, enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration,
      enable-riscv64, enable-hcpp

[!] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [2.2s]
    • Android SDK at /Users/chrisbobbe/Library/Android/sdk
    • Emulator version 36.1.9.0 (build_id 13823996) (CL:N/A)
    • Platform android-36, build-tools 36.0.0
    • ANDROID_HOME = /Users/chrisbobbe/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
      This is the JDK bundled with the latest Android Studio installation on this machine.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 21.0.10+-117844308-b1163.108)
    ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

[✓] Xcode - develop for iOS and macOS (Xcode 26.6) [953ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 17F113
    • CocoaPods version 1.17.0

[✓] Chrome - develop for the web [4ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Connected device (4 available) [6.4s]
    • Chris’s iPhone (mobile) • <redacted>            • ios            • iOS 26.6.2 23G90
    • iPhone 15 Pro (mobile)  • D2E5855E-A059-4FBB-A9AF-117C12378F4F • ios            • com.apple.CoreSimulator.SimRuntime.iOS-17-5 (simulator)
    • macOS (desktop)         • macos                                • darwin-arm64   • macOS 26.5.1 25F80 darwin-arm64
    • Chrome (web)            • chrome                               • web-javascript • Google Chrome 155.0.8059.39

[✓] Network resources [288ms]
    • All expected network resources are available.

! Doctor found issues in 1 category.

Additional context
I may post a diagnosis from Claude Code, below, which I haven't confirmed, but that might still be helpful to someone who's already familiar with the code it touches on.

Activity

  1. added
    new issueAn issue that hasn't yet been seen from the maintainer
    on Oct 9, 2026
  2. chrisbobbe commented on Oct 9, 2026

    @chrisbobbe
    Author

    As foreshadowed in the issue description—

    I may post a diagnosis from Claude Code, below, which I haven't confirmed, but that might still be helpful to someone who's already familiar with the code it touches on.

    —here's a copy-pasted diagnosis from a Claude Code session, in case it's helpful:

    Details

    IOSFilePickerHandler finishes the pending request in presentationControllerWillDismiss(_:):
    https://github.com/vicajilau/flutter_file_picker/blob/1c5cfe162/packages/file_picker_darwin/darwin/file_picker_darwin/Sources/file_picker_darwin/IOSFilePickerHandler.swift#L192-L194

    UIKit calls that method when the user starts to pull the sheet down, even if the sheet doesn't end up dismissed. From WWDC19 session 224, "Modernizing Your UI for iOS 13" (https://developer.apple.com/videos/play/wwdc2019/224/):

    Now, keep in mind that if a user were to repeatedly tug on a Sheet without pulling it down, the delegate will receive, Should and WillDismiss multiple times before calling DidDismiss, if it receives DidDismiss at all.

    finishCurrentRequest(nil) completes the request with no files, and sets result to nil:
    https://github.com/vicajilau/flutter_file_picker/blob/1c5cfe162/packages/file_picker_darwin/darwin/file_picker_darwin/Sources/file_picker_darwin/IOSFilePickerHandler.swift#L467-L481

    So when the user later finishes picking, picker(_:didFinishPicking:) returns early at its guard let currentResult = result check:
    https://github.com/vicajilau/flutter_file_picker/blob/1c5cfe162/packages/file_picker_darwin/darwin/file_picker_darwin/Sources/file_picker_darwin/IOSFilePickerHandler.swift#L142-L144

    presentationControllerDidDismiss(_:) already handles a completed swipe-to-dismiss:
    https://github.com/vicajilau/flutter_file_picker/blob/1c5cfe162/packages/file_picker_darwin/darwin/file_picker_darwin/Sources/file_picker_darwin/IOSFilePickerHandler.swift#L196-L200

    presentationControllerWillDismiss(_:) was added in #2040, to fix #2021: after a very fast swipe-down, the picker could get stuck, failing every later pick with multiple_request. (#2021 was reported against 11.0.2, which implemented only presentationControllerDidDismiss.) So simply removing it would likely bring #2021 back.

    Instead, presentationControllerWillDismiss(_:) could wait for the dismissal to end, and finish the request only if the dismissal wasn't cancelled:

    func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
        guard let coordinator = presentationController.presentedViewController.transitionCoordinator else {
            finishCurrentRequest(nil)
            return
        }
        coordinator.animate(alongsideTransition: nil) { [weak self] context in
            if !context.isCancelled {
                self?.finishCurrentRequest(nil)
            }
        }
    }

    finishCurrentRequest already does nothing when there's no pending request, so it's fine if presentationControllerDidDismiss(_:) also calls it. (I haven't tested this.)

  3. added
    bugSomething isn't working
    iOSIssue applies to iOS platform
    on Oct 10, 2026
  4. vicajilau commented on Oct 10, 2026

    @vicajilau
    Owner

    Thanks a lot for the excellent report, the repro app and the diagnosis! 🙌 Everything checked out against the code.

    As you suggested, presentationControllerWillDismiss now waits for the dismissal transition to end through the transitionCoordinator, and only finishes the request if it wasn't cancelled. So a partial swipe that springs back keeps the pick open and the later selection is returned, while a completed dismissal, including the fast swipe from #2021, still cancels it.

    The fix is in #2244. We reproduced the bug with your sample app and confirmed the fix with it on the simulator. It will ship in file_picker_darwin 2.1.4, and with file_picker 13.1.0 a regular flutter pub upgrade will pick it up.

  5. vicajilau commented on Oct 11, 2026

    @vicajilau
    Owner

    The fix is now published in file_picker_darwin 2.1.4. With file_picker 13.1.0 a regular flutter pub upgrade picks it up. Thanks again @chrisbobbe! 🙌

  6. added a commit that references this issue on Oct 15, 2026
    fec06c0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingiOSIssue applies to iOS platformnew issueAn issue that hasn't yet been seen from the maintainer

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions