Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/file_selector/file_selector_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.5.3+4

* Improves compatibility with `UIScene`.
* Updates minimum supported SDK version to Flutter 3.32/Dart 3.8.

## 0.5.3+3

* Updates minimum supported version to iOS 13.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import Flutter
import XCTest

@testable import file_selector_ios
Expand All @@ -16,13 +17,21 @@ final class TestViewPresenter: ViewPresenter {
}
}

final class StubViewPresenterProvider: ViewPresenterProvider {
var viewPresenter: ViewPresenter?

init(viewPresenter: ViewPresenter?) {
self.viewPresenter = viewPresenter
}
}

class FileSelectorTests: XCTestCase {
func testPickerPresents() throws {
let plugin = FileSelectorPlugin()
let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import)
let presenter = TestViewPresenter()
let plugin = FileSelectorPlugin(
viewPresenterProvider: StubViewPresenterProvider(viewPresenter: presenter))
let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import)
plugin.documentPickerViewControllerOverride = picker
plugin.viewPresenterOverride = presenter

plugin.openFile(
config: FileSelectorConfig(utis: [], allowMultiSelection: false)
Expand All @@ -34,10 +43,10 @@ class FileSelectorTests: XCTestCase {
}

func testReturnsPickedFiles() throws {
let plugin = FileSelectorPlugin()
let plugin = FileSelectorPlugin(
viewPresenterProvider: StubViewPresenterProvider(viewPresenter: TestViewPresenter()))
let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import)
plugin.documentPickerViewControllerOverride = picker
plugin.viewPresenterOverride = TestViewPresenter()
let completionWasCalled = expectation(description: "completion")

plugin.openFile(
Expand All @@ -60,10 +69,10 @@ class FileSelectorTests: XCTestCase {
}

func testCancellingPickerReturnsEmptyList() throws {
let plugin = FileSelectorPlugin()
let plugin = FileSelectorPlugin(
viewPresenterProvider: StubViewPresenterProvider(viewPresenter: TestViewPresenter()))
let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import)
plugin.documentPickerViewControllerOverride = picker
plugin.viewPresenterOverride = TestViewPresenter()
let completionWasCalled = expectation(description: "completion")

plugin.openFile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ publish_to: 'none'
version: 1.0.0

environment:
sdk: ^3.9.0
flutter: ">=3.35.0"
sdk: ^3.10.0
flutter: ">=3.38.0"

dependencies:
# The following adds the Cupertino Icons font to your application.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,19 @@ public class FileSelectorPlugin: NSObject, FlutterPlugin, FileSelectorApi {
var pendingCompletions: Set<PickerCompletionBridge> = []
/// Overridden document picker, for testing.
var documentPickerViewControllerOverride: UIDocumentPickerViewController?
/// Overridden view presenter, for testing.
var viewPresenterOverride: ViewPresenter?
/// The view controller provider, for showing the document picker.
let viewPresenterProvider: ViewPresenterProvider

public static func register(with registrar: FlutterPluginRegistrar) {
let instance = FileSelectorPlugin()
let instance = FileSelectorPlugin(
viewPresenterProvider: DefaultViewPresenterProvider(registrar: registrar))
FileSelectorApiSetup.setUp(binaryMessenger: registrar.messenger(), api: instance)
}

init(viewPresenterProvider: ViewPresenterProvider) {
self.viewPresenterProvider = viewPresenterProvider
}

func openFile(config: FileSelectorConfig, completion: @escaping (Result<[String], Error>) -> Void)
{
let completionBridge = PickerCompletionBridge(completion: completion, owner: self)
Expand All @@ -64,14 +69,12 @@ public class FileSelectorPlugin: NSObject, FlutterPlugin, FileSelectorApi {
documentPicker.allowsMultipleSelection = config.allowMultiSelection
documentPicker.delegate = completionBridge

let presenter =
self.viewPresenterOverride ?? UIApplication.shared.delegate?.window??.rootViewController
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could keep this override structure instead of adding ViewPresenterProvider, but I thought it was cleaner to reduce test branching in production code.

if let presenter = presenter {
if let presenter = viewPresenterProvider.viewPresenter {
pendingCompletions.insert(completionBridge)
presenter.present(documentPicker, animated: true, completion: nil)
} else {
completion(
.failure(PigeonError(code: "error", message: "Missing root view controller.", details: nil))
.failure(PigeonError(code: "error", message: "No view controller available.", details: nil))
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import Flutter
import UIKit

/// Protocol for UIViewController methods relating to presenting a controller.
Expand All @@ -18,3 +19,26 @@ protocol ViewPresenter {

/// ViewPresenter is intentionally a direct passthroguh to UIViewController.
extension UIViewController: ViewPresenter {}

/// Protocol for FlutterPluginRegistrar method for accessing the view controller.
///
/// This is necessary because Swift doesn't allow for only partially implementing a protocol, so
/// a stub implementation of FlutterPluginRegistrar for tests would break any time something was
/// added to that protocol.
protocol ViewPresenterProvider {
/// Returns the view controller associated with the Flutter content.
var viewPresenter: ViewPresenter? { get }
}

/// Non-test implementation of ViewControllerProvider that forwards to the plugin registrar.
class DefaultViewPresenterProvider: ViewPresenterProvider {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary because FlutterPluginRegistrar is a protocol rather than a class, and the Swift "One Weird Trick" to add protocol conformance only works on classes, not protocols, so we can't do a no-code passthrough to the real registrar using something like line 21 above.

private let registrar: FlutterPluginRegistrar

init(registrar: FlutterPluginRegistrar) {
self.registrar = registrar
}

var viewPresenter: ViewPresenter? {
registrar.viewController
}
}
6 changes: 3 additions & 3 deletions packages/file_selector/file_selector_ios/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ name: file_selector_ios
description: iOS implementation of the file_selector plugin.
repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_ios
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22
version: 0.5.3+3
version: 0.5.3+4

environment:
sdk: ^3.9.0
flutter: ">=3.35.0"
sdk: ^3.10.0
flutter: ">=3.38.0"

flutter:
plugin:
Expand Down