Skip to content

[image_picker_ios] PR 3/5 Split SPM packaging and migrate the plugin class to Swift - #13235

Merged
auto-submit[bot] merged 7 commits into
flutter:mainfrom
victogomez-cs:pr3/image-picker-ios-mixed-language-and-swift-plugin
Oct 22, 2026
Merged

auto-submit[bot] merged 7 commits into
flutter:mainfrom
victogomez-cs:pr3/image-picker-ios-mixed-language-and-swift-plugin

Conversation

@victogomez-cs

@victogomez-cs victogomez-cs commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Splits the Swift package into a Swift target and an image_picker_ios_objc target. The Swift target depends on the Objective-C target, which still holds the view provider, image utilities, PHPicker save operation, and Pigeon messages. Migrates FLTImagePickerPlugin to ImagePickerPlugin and moves the camera, photo library, and PHPicker seams into that Swift target. pluginClass is now ImagePickerPlugin.

Plugin behavior is unchanged. Bumps image_picker_ios to 0.8.13+13.

PR 3/5 of the Obj-C → Swift migration. Part of flutter/flutter#119107

Pre-Review Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2

@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Oct 13, 2026
@victogomez-cs victogomez-cs added the triage-ios Should be looked at in iOS triage label Oct 13, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request migrates the iOS implementation of the image_picker plugin from Objective-C to Swift and restructures the codebase into mixed-language Swift Package Manager targets. The review feedback focuses on improving the safety and idiomatic quality of the new Swift code. Key recommendations include addressing a potential data race in processPickerItems using NSLock and native Swift arrays, removing redundant Objective-C synchronization blocks, refactoring sendCallResult(withSavedPathList:) to use native Swift arrays instead of NSArray, and removing unnecessary type casts in both the plugin implementation and its unit tests.

Comment on lines +503 to +531
let pathList = NSMutableArray(capacity: results.count)
var saveError: FlutterError?

let sendListOperation = BlockOperation {
if let saveError {
self.sendCallResult(with: saveError)
} else {
self.sendCallResult(withSavedPathList: pathList)
}
saveQueue = nil
}

for result in results {
pathList.add(NSNull())
let index = pathList.count - 1
guard
let saveOperation = FLTPHPickerSaveImageToPathOperation(
result: result,
maxHeight: maxHeight,
maxWidth: maxWidth,
desiredImageQuality: desiredImageQuality,
fullMetadata: requestFullMetadata,
savedPathBlock: { savedPath, error in
if let savedPath {
pathList[index] = savedPath
} else {
saveError = error
}
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The current implementation of processPickerItems uses NSMutableArray and NSNull to collect paths from concurrent background operations. Since NSMutableArray is not thread-safe, concurrent writes to it from different background threads in saveQueue can cause data races, crashes, or undefined behavior. Additionally, saveError is written to concurrently without synchronization.

We can resolve this data race and make the code much more idiomatic Swift by:

  1. Using a native Swift array [String?] instead of NSMutableArray and NSNull.
  2. Protecting concurrent writes to pathList and saveError using an NSLock.
    var pathList = [String?](repeating: nil, count: results.count)
    var saveError: FlutterError?
    let lock = NSLock()

    let sendListOperation = BlockOperation {
      if let saveError {
        self.sendCallResult(with: saveError)
      } else {
        self.sendCallResult(withSavedPathList: pathList)
      }
      saveQueue = nil
    }

    for (index, result) in results.enumerated() {
      guard
        let saveOperation = FLTPHPickerSaveImageToPathOperation(
          result: result,
          maxHeight: maxHeight,
          maxWidth: maxWidth,
          desiredImageQuality: desiredImageQuality,
          fullMetadata: requestFullMetadata,
          savedPathBlock: { savedPath, error in
            lock.lock()
            defer { lock.unlock() }
            if let savedPath {
              pathList[index] = savedPath
            } else {
              saveError = error
            }
          })

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

savedPathBlock runs on PHPicker's private queue, and the save queue runs those operations concurrently, so the path-list writes could overlap. pathList is now [String?], and an NSLock covers writes to it and to saveError. The main-queue operation copies both under that lock after the saves finish

Comment on lines +350 to +355
objc_sync_enter(self)
let beingPresented = imagePickerController.isBeingPresented
objc_sync_exit(self)
if beingPresented {
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The use of objc_sync_enter and objc_sync_exit is unidiomatic in Swift. Since showCamera is always called on the main thread, this synchronization is redundant and can be safely removed.

Suggested change
objc_sync_enter(self)
let beingPresented = imagePickerController.isBeingPresented
objc_sync_exit(self)
if beingPresented {
return
}
if imagePickerController.isBeingPresented {
return
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the port of @synchronized(self) around beingPresented. The authorized path calls showCamera on the caller. Only the not-determined path hops to the main queue first. I kept that critical section

details: nil))
return
}
sendCallResult(withSavedPathList: [destination.path] as NSArray)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Remove the unnecessary as NSArray cast since sendCallResult(withSavedPathList:) now accepts a native Swift array.

Suggested change
sendCallResult(withSavedPathList: [destination.path] as NSArray)
sendCallResult(withSavedPathList: [destination.path])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. That call uses the [String?]? parameter

let savedPath = FLTImagePickerPhotoAssetUtil.saveImage(
withOriginalImageData: originalImageData, image: image ?? UIImage(),
maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality)
sendCallResult(withSavedPathList: [savedPath] as NSArray)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Remove the unnecessary as NSArray cast since sendCallResult(withSavedPathList:) now accepts a native Swift array.

Suggested change
sendCallResult(withSavedPathList: [savedPath] as NSArray)
sendCallResult(withSavedPathList: [savedPath])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. That call uses the [String?]? parameter

withPickerInfo info: [String: Any]?, image: UIImage, imageQuality: NSNumber?
) {
let savedPath = FLTImagePickerPhotoAssetUtil.saveImage(
withPickerInfo: info, image: image, imageQuality: imageQuality)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Remove the unnecessary as NSArray cast since sendCallResult(withSavedPathList:) now accepts a native Swift array.

    sendCallResult(withSavedPathList: [savedPath])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. That call uses the [String?]? parameter

Comment on lines +629 to +646
func sendCallResult(withSavedPathList pathList: NSArray?) {
guard let callContext else {
return
}

if let pathList, pathList.contains(NSNull()) {
callContext.result(
nil,
FlutterError(
code: "create_error",
message: "pathList's items should not be null",
details: nil))
} else {
let paths = pathList?.compactMap { $0 as? String } ?? []
callContext.result(paths, nil)
}
self.callContext = nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To make the API more idiomatic Swift and avoid unnecessary bridging to Objective-C types, we should change sendCallResult(withSavedPathList:) to accept a native Swift array [String?]? instead of NSArray?.

Suggested change
func sendCallResult(withSavedPathList pathList: NSArray?) {
guard let callContext else {
return
}
if let pathList, pathList.contains(NSNull()) {
callContext.result(
nil,
FlutterError(
code: "create_error",
message: "pathList's items should not be null",
details: nil))
} else {
let paths = pathList?.compactMap { $0 as? String } ?? []
callContext.result(paths, nil)
}
self.callContext = nil
}
func sendCallResult(withSavedPathList pathList: [String?]?) {
guard let callContext else {
return
}
if let pathList, pathList.contains(where: { $0 == nil }) {
callContext.result(
nil,
FlutterError(
code: "create_error",
message: "pathList's items should not be null",
details: nil))
} else {
let paths = pathList?.compactMap { $0 } ?? []
callContext.result(paths, nil)
}
self.callContext = nil
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sendCallResult(withSavedPathList:) now takes [String?]?. A nil item still returns create_error. The call sites pass Swift arrays directly

Comment on lines 238 to 255
private func captureSendResult(
_ plugin: FLTImagePickerPlugin, pathList: [Any]?
_ plugin: ImagePickerPlugin, pathList: [Any]?
) -> (result: [String]?, error: FlutterError?) {
var received: [String]?
var receivedError: FlutterError?
plugin.callContext = FLTImagePickerMethodCallContext { result, error in
plugin.callContext = ImagePickerMethodCallContext { result, error in
received = result
receivedError = error
}
plugin.sendCallResult(withSavedPathList: pathList)
plugin.sendCallResult(withSavedPathList: pathList as NSArray?)
return (received, receivedError)
}

@Test func pluginMultiImagePathHasNullItem() {
let plugin = FLTImagePickerPlugin(viewProvider: StubViewProvider())
let plugin = ImagePickerPlugin(viewProvider: StubViewProvider())
let (_, error) = captureSendResult(plugin, pathList: [NSNull()])
#expect(error?.code == "create_error")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Since we updated sendCallResult(withSavedPathList:) to accept a native Swift array [String?]?, we can update captureSendResult and the tests to use native Swift optionals instead of NSArray and NSNull.

Suggested change
private func captureSendResult(
_ plugin: FLTImagePickerPlugin, pathList: [Any]?
_ plugin: ImagePickerPlugin, pathList: [Any]?
) -> (result: [String]?, error: FlutterError?) {
var received: [String]?
var receivedError: FlutterError?
plugin.callContext = FLTImagePickerMethodCallContext { result, error in
plugin.callContext = ImagePickerMethodCallContext { result, error in
received = result
receivedError = error
}
plugin.sendCallResult(withSavedPathList: pathList)
plugin.sendCallResult(withSavedPathList: pathList as NSArray?)
return (received, receivedError)
}
@Test func pluginMultiImagePathHasNullItem() {
let plugin = FLTImagePickerPlugin(viewProvider: StubViewProvider())
let plugin = ImagePickerPlugin(viewProvider: StubViewProvider())
let (_, error) = captureSendResult(plugin, pathList: [NSNull()])
#expect(error?.code == "create_error")
}
private func captureSendResult(
_ plugin: ImagePickerPlugin, pathList: [String?]?
) -> (result: [String]?, error: FlutterError?) {
var received: [String]?
var receivedError: FlutterError?
plugin.callContext = ImagePickerMethodCallContext { result, error in
received = result
receivedError = error
}
plugin.sendCallResult(withSavedPathList: pathList)
return (received, receivedError)
}
@Test func pluginMultiImagePathHasNullItem() {
let plugin = ImagePickerPlugin(viewProvider: StubViewProvider())
let (_, error) = captureSendResult(plugin, pathList: [nil])
#expect(error?.code == "create_error")
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. captureSendResult takes [String?]?, and the null-item test passes [nil].

confirmed()
}
plugin.sendCallResult(withSavedPathList: ["a", "b"])
plugin.sendCallResult(withSavedPathList: ["a", "b"] as NSArray)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Remove the unnecessary as NSArray cast since sendCallResult(withSavedPathList:) now accepts a native Swift array.

Suggested change
plugin.sendCallResult(withSavedPathList: ["a", "b"] as NSArray)
plugin.sendCallResult(withSavedPathList: ["a", "b"])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. That call uses the [String?]? parameter

confirmed()
}
plugin.sendCallResult(withSavedPathList: ["a", "b"])
plugin.sendCallResult(withSavedPathList: ["a", "b"] as NSArray)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Remove the unnecessary as NSArray cast since sendCallResult(withSavedPathList:) now accepts a native Swift array.

Suggested change
plugin.sendCallResult(withSavedPathList: ["a", "b"] as NSArray)
plugin.sendCallResult(withSavedPathList: ["a", "b"])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. That call uses the [String?]? parameter

auto-submit Bot pushed a commit that referenced this pull request Oct 14, 2026
…kUTType code paths (#13224)

With Xcode 27 the `xcode analyze` step of `Mac_arm64 ios_platform_tests` fails on image_picker_ios:

```
FLTImagePickerImageUtil.m:132:37: error: 'kUTTypeGIF' is deprecated: first deprecated in iOS 15.0 - Use UTTypeGIF or UTType.gif (swift) instead. [-Werror,-Wdeprecated-declarations]
  132 |     gifTypeIdentifier = (NSString *)kUTTypeGIF;
```

Staging run on Xcode 27 (27A266a): https://ci.chromium.org/b/8668491298933206513

`kUTTypeGIF` is only referenced in the `else` branch of an `if (@available(iOS 14.0, *))` check (added in #10848), so it never runs on iOS 14+. The compiler still has to compile that branch though, and Xcode 27 only supports deployment targets of iOS 15 and up, so the plugin is now always built with `-target …-ios15.0` and the reference trips `-Wdeprecated-declarations`, which `xcode analyze` treats as an error. The build stops at the first failing file; `FLTImagePickerPhotoAssetUtil.m` has the same pattern, and `FLTImagePickerPlugin.m` uses `kUTTypeImage` / `kUTTypeMovie` unconditionally.

Flutter 3.47 dropped iOS 13 and 14, so rather than keep those code paths alive this raises the package minimum to Flutter 3.47 / Dart 3.13 and iOS 15 (podspec and `Package.swift`), and deletes the iOS 13 branches: `UTTypeGIF`, `UTTypeImage` and `UTTypeMovie` are used directly. Clients on older Flutter stay on the previous version through `pub`, so this is a patch bump, same as #10231 did for iOS 13. The remaining `@available(iOS 13/14, *)` checks in `FLTImagePickerPlugin.m` are now dead too; I left them alone since that file is being rewritten in Swift in #13235 and they can go there.

Also updates the app-facing `image_picker` README's support table to iOS 15 (with a `## NEXT` entry, no version bump), the same way #10470 did for iOS 13.

Verified on Xcode 27 (with the earlier, fallback-preserving version of this change): `xcode analyze`, `xcode analyze deprecation` and the native tests pass for image_picker_ios — https://ci.chromium.org/b/8668086845504323249 (shard 3 of a staging replay; the remaining `native test` failure in that build is in_app_purchase_storekit, flutter/flutter#194285).

Part of flutter/flutter#194019

## Pre-Review Checklist

Test exemption: native-only change with no behaviour change; the existing GIF tests in `RunnerTests/ImageUtilTests.swift` and `RunnerTests/PhotoAssetUtilTests.swift` already exercise both call sites.

[^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
imagePickerController.delegate = self
var mediaTypes: [String] = []
if context.includeImages {
mediaTypes.append(kUTTypeImage as String)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

launchUIImagePicker now uses UTType.image.identifier and UTType.movie.identifier, matching what landed in #13224. Rebased onto that. 0.8.13+12 is the published iOS 15 bump, so this PR is 0.8.13+13

@victogomez-cs
victogomez-cs force-pushed the pr3/image-picker-ios-mixed-language-and-swift-plugin branch from 7b356b6 to 5a4cee4 Compare October 14, 2026 15:57
}

/// Production implementation that forwards to UIImagePickerController.
struct DefaultCameraAvailability: CameraAvailabilityChecking {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: rename struct to DefaultCameraAvailabilityChecking or rename protocol to CameraAvailability

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Renamed the struct to DefaultCameraAvailabilityChecker. That matches DefaultCameraPermissionChecker and DefaultPhotoLibraryPermissionChecker. The protocol stays CameraAvailabilityChecking

var imageQuality: NSNumber?

/// Maximum number of items to select. 0 indicates no maximum.
var maxItemCount: Int = 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: can remove Int

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. var maxItemCount = 0

}
}

@available(iOS 14, *)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i think our minimum is already 15 so you can remove this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed @available(iOS 14, *) from launchPHPicker. The deployment target is iOS 15, so that method can still call the PHPicker helpers


public func pickImage(
withSource source: FLTSourceSpecification, maxSize: FLTMaxSize, quality imageQuality: NSNumber?,
fullMetadata: Bool, completion: @escaping (String?, FlutterError?) -> Void

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: separate lines for these params

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. pickImage, pickMultiImage, and pickVideo each take one parameter per line, and swift-format lint --strict keeps that wrapping

if (paths?.count ?? 0) > 1 {
completion(
nil,
FlutterError(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the indentation has been insane. does completion(nil, FlutterError( pass the linter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. swift-format lint --strict accepts completion(nil, FlutterError(. That indent is the formatter’s wrap, so I left it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you reduce the indentation given that it passes the check?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. The FlutterError is built first, and the call is completion(nil, invalidResult). swift-format lint --strict still passes


func checkCameraAuthorization(
with imagePickerController: UIImagePickerController,
camera device: UIImagePickerController.CameraDevice

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: cameraDevice

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. The parameter is cameraDevice

cameraPermissionChecker.requestAccess(for: .video) { granted in
DispatchQueue.main.async {
if granted {
self.showCamera(device, with: imagePickerController)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

weak

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you will need to put it in outer closure (requestAccess) rather than the inner one (async)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. [weak self] is on the requestAccess closure, and the main-queue hop uses self?

photoLibraryPermissionChecker.requestAuthorization { status in
DispatchQueue.main.async {
if status == .authorized {
self.showPhotoLibrary(with: imagePickerController)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. [weak self] is on requestAuthorization as well

switch status {
case .restricted:
sendCallResult(
with: FlutterError(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does sendCallResult(with: FlutterError( pass linter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. swift-format lint --strict accepts sendCallResult(with: FlutterError(. Same formatter wrap as the completion call

let lock = NSLock()

let sendListOperation = BlockOperation {
lock.lock()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why is lock required? Is saveError and pathList accessed in multiple queues?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. savedPathBlock runs on PHPicker’s private queue, and those save operations run concurrently, so pathList and saveError are written from more than one thread. The main queue operation copies both under the same lock before it calls sendCallResult

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you leave a comment there?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i assume it's also in objc code?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Left a comment at the lock. The Objective-C implementation wrote pathList and saveError from savedPathBlock with no lock. The completions still run concurrently on PHPicker's private queue, and the main-queue operation reads both values, so the Swift port locks those writes

…instead of NSArray. Refactor tests to handle optional strings and ensure compatibility with new type definitions.
…tional headers for Swift compatibility. This change ensures that the necessary Objective-C headers are accessible for the Swift plugin, facilitating the use of the Pigeon API and related helpers.
…d Swift compatibility. This change replaces the excluded header file in Package.swift and updates the umbrella header in ImagePickerPlugin.modulemap to ensure proper integration with Swift.
…ethod signatures for clarity. Renamed `DefaultCameraAvailability` to `DefaultCameraAvailabilityChecker` and adjusted related references. Enhanced `FlutterResultAdapter` typealias for better readability and updated method parameters in `ImagePickerPlugin` for consistency.
@victogomez-cs
victogomez-cs force-pushed the pr3/image-picker-ios-mixed-language-and-swift-plugin branch from 5a4cee4 to d9c7b29 Compare October 22, 2026 17:03
…ed paths correctly. Calls single-item completions once when multiple paths are returned, ensuring proper error handling. Refactor tests to reflect this change.
@victogomez-cs victogomez-cs added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 22, 2026
@auto-submit
auto-submit Bot merged commit 1e35bca into flutter:main Oct 22, 2026
13 checks passed
4rthurmonteiro added a commit to 4rthurmonteiro/packages that referenced this pull request Oct 22, 2026
main migrated the image_picker_ios plugin class to Swift (flutter#13235). Moves the
recent media implementation into ImagePickerPlugin.swift, adds the
RecentAssetsFetching and ThumbnailRequesting seams as Swift protocols, and
adds the access-level methods to PhotoLibraryPermissionChecking.
@victogomez-cs
victogomez-cs deleted the pr3/image-picker-ios-mixed-language-and-swift-plugin branch October 22, 2026 22:16
bkonyi pushed a commit to bkonyi/flutter that referenced this pull request Oct 23, 2026
flutter/packages@93baed9...108dad0

2026-10-22 [email protected] [material_ui]
Migrate M3 IconButton template to use new gen_defaults
(flutter/packages#12922)
2026-10-22 [email protected] [material_ui]
Migrate M3 NavigationBar template to use new gen_defaults
(flutter/packages#13067)
2026-10-22 [email protected] [material_ui]
Remove obsolete `SurfaceTint` defaults template (flutter/packages#13276)
2026-10-22 [email protected] [material_ui]
Migrate M3 Radio template to use new gen_defaults
(flutter/packages#13210)
2026-10-22 [email protected] [material_ui]
Migrate M3 NavigationDrawer template to use new gen_defaults
(flutter/packages#13068)
2026-10-22 [email protected] [image_picker_ios] PR 3/5 Split
SPM packaging and migrate the plugin class to Swift
(flutter/packages#13235)
2026-10-22 [email protected] [ci] Increase check-discovery timeout
in release workflows (flutter/packages#13392)
2026-10-22 [email protected] [material_ui]
Migrate M3 `Switch` template to use new gen_defaults
(flutter/packages#13274)
2026-10-22 [email protected] [material_ui]
Migrate M3 PopupMenu template to use new gen_defaults
(flutter/packages#13209)
2026-10-22 [email protected] [material_ui] Add day and weekday
builders to CalendarDatePicker and showDatePicker
(flutter/packages#12587)
2026-10-22 [email protected] [material_ui]
Migrate M3 `SearchView` template to use new gen_defaults
(flutter/packages#13272)
2026-10-22 [email protected] [material_ui]
Migrate M3 `TextField` template to use new gen_defaults
(flutter/packages#13275)
2026-10-22 [email protected] [material_ui]
Migrate M3 RangeSlider template to use new gen_defaults
(flutter/packages#13215)
2026-10-22 [email protected] [go_router] Keep web
autocomplete focused in shell routes (flutter/packages#13348)
2026-10-22 [email protected] [shared_preferences_android] Fix web
compilation of apps that import the package (flutter/packages#13380)
2026-10-22 [email protected] [material_ui]
Migrate M3 NavigationRail template to use new gen_defaults
(flutter/packages#13069)
2026-10-22 [email protected] [material_ui]
Migrate M3 SearchBar template to use new gen_defaults
(flutter/packages#13216)
2026-10-22 [email protected] [material_ui] Fix for time picker error
in locales that do not use Latin digits (flutter/packages#13035)
2026-10-22 [email protected] [material_ui]
Fix MenuAnchor scrollbar safe-area padding (flutter/packages#12470)
2026-10-22 [email protected] [material_ui] Document Material 3
fallback for Divider.color and VerticalDivider.color
(flutter/packages#12967)
2026-10-22 [email protected]
[material_ui] Migrate M3 tabs template to use new gen_defaults
(flutter/packages#13139)
2026-10-22 [email protected] [material_ui]
Migrate M3 progress indicator defaults to new gen_defaults
(flutter/packages#13026)
2026-10-22 [email protected] [google_sign_in] Convert the iOS
example app and UI tests to Swift (flutter/packages#13271)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC [email protected] on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
technobibi added a commit to technobibi/packages that referenced this pull request Oct 27, 2026
Resolves conflicts with flutter#13235, which moved the Objective-C sources to
the image_picker_ios_objc target and migrated the plugin class to
Swift:

- Moves this change to 0.8.13+14, above main's 0.8.13+13 entry.
- Ports the two UIImagePickerController save guards from the deleted
  FLTImagePickerPlugin.m to ImagePickerPlugin.swift, sending the same
  `invalid_image` error instead of a path list when saving fails.
- Updates this change's two plugin tests to the Swift plugin class names.
suryaKommana2662 pushed a commit to suryaKommana2662/flutter that referenced this pull request Oct 29, 2026
flutter/packages@93baed9...108dad0

2026-10-22 [email protected] [material_ui]
Migrate M3 IconButton template to use new gen_defaults
(flutter/packages#12922)
2026-10-22 [email protected] [material_ui]
Migrate M3 NavigationBar template to use new gen_defaults
(flutter/packages#13067)
2026-10-22 [email protected] [material_ui]
Remove obsolete `SurfaceTint` defaults template (flutter/packages#13276)
2026-10-22 [email protected] [material_ui]
Migrate M3 Radio template to use new gen_defaults
(flutter/packages#13210)
2026-10-22 [email protected] [material_ui]
Migrate M3 NavigationDrawer template to use new gen_defaults
(flutter/packages#13068)
2026-10-22 [email protected] [image_picker_ios] PR 3/5 Split
SPM packaging and migrate the plugin class to Swift
(flutter/packages#13235)
2026-10-22 [email protected] [ci] Increase check-discovery timeout
in release workflows (flutter/packages#13392)
2026-10-22 [email protected] [material_ui]
Migrate M3 `Switch` template to use new gen_defaults
(flutter/packages#13274)
2026-10-22 [email protected] [material_ui]
Migrate M3 PopupMenu template to use new gen_defaults
(flutter/packages#13209)
2026-10-22 [email protected] [material_ui] Add day and weekday
builders to CalendarDatePicker and showDatePicker
(flutter/packages#12587)
2026-10-22 [email protected] [material_ui]
Migrate M3 `SearchView` template to use new gen_defaults
(flutter/packages#13272)
2026-10-22 [email protected] [material_ui]
Migrate M3 `TextField` template to use new gen_defaults
(flutter/packages#13275)
2026-10-22 [email protected] [material_ui]
Migrate M3 RangeSlider template to use new gen_defaults
(flutter/packages#13215)
2026-10-22 [email protected] [go_router] Keep web
autocomplete focused in shell routes (flutter/packages#13348)
2026-10-22 [email protected] [shared_preferences_android] Fix web
compilation of apps that import the package (flutter/packages#13380)
2026-10-22 [email protected] [material_ui]
Migrate M3 NavigationRail template to use new gen_defaults
(flutter/packages#13069)
2026-10-22 [email protected] [material_ui]
Migrate M3 SearchBar template to use new gen_defaults
(flutter/packages#13216)
2026-10-22 [email protected] [material_ui] Fix for time picker error
in locales that do not use Latin digits (flutter/packages#13035)
2026-10-22 [email protected] [material_ui]
Fix MenuAnchor scrollbar safe-area padding (flutter/packages#12470)
2026-10-22 [email protected] [material_ui] Document Material 3
fallback for Divider.color and VerticalDivider.color
(flutter/packages#12967)
2026-10-22 [email protected]
[material_ui] Migrate M3 tabs template to use new gen_defaults
(flutter/packages#13139)
2026-10-22 [email protected] [material_ui]
Migrate M3 progress indicator defaults to new gen_defaults
(flutter/packages#13026)
2026-10-22 [email protected] [google_sign_in] Convert the iOS
example app and UI tests to Swift (flutter/packages#13271)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC [email protected] on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
auto-submit Bot pushed a commit that referenced this pull request Oct 29, 2026
…eration, and image utilities to Swift (#13394)

Migrates the Objective-C helpers that `ImagePickerPlugin` calls into the Swift target:

* `FIPViewProvider` becomes `ViewProvider` / `DefaultViewProvider`.
* `FLTPHPickerSaveImageToPathOperation` becomes `PHPickerSaveImageToPathOperation`.
* `FLTImagePickerImageUtil`, `FLTImagePickerMetaDataUtil`, and `FLTImagePickerPhotoAssetUtil` become `ImagePickerImageUtil`, `ImagePickerMetaDataUtil`, and `ImagePickerPhotoAssetUtil`.
* The `PHImageManager` seam becomes `ImageDataRequesting`.
* `PHPickerResult` conforms to `PickerItem`, replacing the `BridgedPickerItem` wrapper.

Deletes the custom module map and `image_picker_ios.h` umbrella header. The `image_picker_ios_objc` target remains for the Pigeon messages until the next PR. CocoaPods no longer sets `s.module_map`.

Bumps image_picker_ios to **0.8.13+14**.

PR 4/5 of the Obj-C → Swift migration, stacked on #13235. Part of flutter/flutter#119107

## Pre-Review Checklist

**Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

[^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosubmit Merge PR when tree becomes green via auto submit App CICD Run CI/CD p: image_picker platform-ios triage-ios Should be looked at in iOS triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants