4

我正在尝试PHPickerViewController使用 SwiftUI 和可组合架构来实现。(并不是说我认为这特别相关,但它可以解释为什么我的某些代码是这样的)。

示例项目

我一直在玩这个来尝试解决它。我在 GitHub 上创建了一个小示例项目,它删除了可组合架构并保持 UI 超级简单。

https://github.com/oliverfoggin/BrokenImagePickers/tree/main

看起来 iOS 15 在 UIImagePickerViewController 和 PHPickerViewController 上都出现了问题。(这是有道理的,因为它们都在引擎盖下使用相同的 UI)。

我猜嵌套步骤是确定在 UIKit 应用程序中使用它们时是否发生相同的错误。

我的代码

我的代码相当简单。它几乎只是对使用的相同功能的重新实现,UIImagePickerViewController但我想尝试使用更新的 API。

我的代码看起来像这样......

public struct ImagePicker: UIViewControllerRepresentable {

// Vars and setup stuff...
  @Environment(\.presentationMode) var presentationMode

  let viewStore: ViewStore<ImagePickerState, ImagePickerAction>
  
  public init(store: Store<ImagePickerState, ImagePickerAction>) {
    self.viewStore = ViewStore(store)
  }
  
// UIViewControllerRepresentable required functions
  public func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> some UIViewController {

    // Configuring the PHPickerViewController
    var config = PHPickerConfiguration()
    config.filter = PHPickerFilter.images
    
    let picker = PHPickerViewController(configuration: config)
    picker.delegate = context.coordinator
    return picker
  }
  
  public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
  
  public func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
  
// This is the coordinator that acts as the delegate
  public class Coordinator: PHPickerViewControllerDelegate {
    let parent: ImagePicker
    
    init(_ parent: ImagePicker) {
      self.parent = parent
    }
    
    public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
      picker.dismiss(animated: true)
      
      guard let itemProvider = results.first?.itemProvider,
        itemProvider.canLoadObject(ofClass: UIImage.self) else {
        return
      }
      
      itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
        if let image = image as? UIImage {
          DispatchQueue.main.async {
            self?.parent.viewStore.send(.imagePicked(image: image))
          }
        }
      }
    }
  }
}

所有这些都适用于简单的情况

我可以展示ImagePicker视图并选择一张照片,一切都很好。我可以取消它。我什至可以向下滚动我拥有的巨大图像集合视图。我什至可以看到新图像出现在我的状态对象中并将其显示在我的应用程序中。(注意......这仍然是 WIP,所以代码有点笨拙,但这只是为了让它最初工作)。

问题案例

问题是当我点击搜索栏时PHPickerView(这是Apple在控件中提供的搜索栏,我没有创建它或编码它)。它似乎开始向上滑动键盘,然后视图变为空白,中间有一条消息......

无法加载照片

[再试一次]

我还收到了一个看起来很奇怪的错误日志。(我删除了时间戳以缩短行)。

// These happen on immediately presenting the ImagePicker
AppName[587:30596] [Picker] Showing picker unavailable UI (reason: still loading) with error: (null)
AppName[587:30596] Writing analzed variants.


// These happen when tapping the search bar
AppName[587:30867] [lifecycle] [u A95D90FC-C77B-43CC-8FC6-C8E7C81DD22A:m (null)] [com.apple.mobileslideshow.photospicker(1.0)] Connection to plugin interrupted while in use.
AppName[587:31002] [lifecycle] [u A95D90FC-C77B-43CC-8FC6-C8E7C81DD22A:m (null)] [com.apple.mobileslideshow.photospicker(1.0)] Connection to plugin invalidated while in use.
AppName[587:30596] [Picker] Showing picker unavailable UI (reason: crashed) with error: (null)
AppName[587:30596] viewServiceDidTerminateWithError:: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted}

点击“再试一次”按钮重新加载初始滚动屏幕,我可以继续使用它。但是再次点击搜索栏只会显示相同的错误。

我通常是第一个指出错误几乎肯定与 Apple API 无关的人,但我被这个问题难住了。我不确定我在做什么导致这种情况发生?

是它在 SwiftUI 视图中的事实吗?

在 UIKit 中重新创建项目

我使用 UIKit 重新制作了同一个项目... https://github.com/oliverfoggin/UIKit-Image-Pickers

而且我根本无法复制崩溃。

另外...如果您对设备进行任何类型的屏幕录制,则不会发生崩溃。我尝试在设备本身上录制,但无法复制。我还尝试使用 iPhone 屏幕从我的 Mac 录制电影,但无法复制崩溃。但是……当我在 QuickTime 上停止录制时,崩溃又可以复制了。

4

4 回答 4

4

好吧..这似乎是一个iOS错误。

我在这里创建了一个显示错误的示例项目...... https://github.com/oliverfoggin/BrokenImagePickers

还有一个用 UIKit 编写的副本项目,它不... https://github.com/oliverfoggin/UIKit-Image-Pickers

我试图对发生的这种情况进行屏幕录制,但似乎如果发生任何屏幕录制(无论是在设备上还是通过 Mac 上的 QuickTime),这都会抑制错误的发生。

我已经向 Apple 提交了一份雷达文件,并将两个项目都发送给他们,以了解正在发生的事情并提供大量细节。我会随时更新这方面的任何进展。

黑客解决方法

经过进一步调查,我发现您可以从 SwiftUI 开始,然后呈现一个 PHPickerViewController 而不会发生这种崩溃。

如果您提供 UIViewControllerRepresentable... 从 SwiftUI 开始,如果您提供 PHPickerViewController 则从那里开始,它不会崩溃。

所以我想出了一个(非常俗气的)解决方法来避免这种崩溃。

我首先创建了一个UIViewController像包装器一样使用的子类。

class WrappedPhotoPicker: UIViewController {
  var picker: PHPickerViewController?
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    if let picker = picker {
      present(picker, animated: false)
    }
  }
}

然后在 SwiftUI 视图中创建这个包装器并在其中设置选择器。

struct WrappedPickerView: UIViewControllerRepresentable {
  @Environment(\.presentationMode) var presentationMode
  @Binding var photoPickerResult: PHPickerResult?
  
  let wrappedPicker = WrappedPhotoPicker()
  
  func makeUIViewController(context: Context) -> WrappedPhotoPicker {
    var config = PHPickerConfiguration()
    config.filter = .images
    config.selectionLimit = 1
    
    let picker = PHPickerViewController(configuration: config)
    picker.delegate = context.coordinator
    
    wrappedPicker.picker = picker
    return wrappedPicker
  }
  
  func updateUIViewController(_ uiViewController: WrappedPhotoPicker, context: Context) {}
  
  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
  
  class Coordinator: PHPickerViewControllerDelegate {
    let parent: WrappedPickerView
    
    init(_ parent: WrappedPickerView) {
      self.parent = parent
    }
    
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
      parent.presentationMode.wrappedValue.dismiss()
      parent.wrappedPicker.dismiss(animated: false)
      
      parent.photoPickerResult = results.first
    }
  }
}

这远非理想,因为我在错误的时间和内容上呈现。但它一直有效,直到 Apple 对此提供永久修复。

于 2021-09-25T10:00:51.813 回答
2

.... 仍然是 15.0 中的 iOS 错误。除了 PHPickerResult 之外,我还修改了 Fogmeisterclass Coordinator以返回图像。

struct WrappedPickerView: UIViewControllerRepresentable {
  @Environment(\.presentationMode) var presentationMode
  @Binding var photoPickerResult: PHPickerResult?
  @Binding var image: UIImage?

  let wrappedPicker = WrappedPhotoPicker()

  func makeUIViewController(context: Context) -> WrappedPhotoPicker {
       var config = PHPickerConfiguration()
       config.filter = .images
       config.selectionLimit = 1

       let picker = PHPickerViewController(configuration: config)
       picker.delegate = context.coordinator

       wrappedPicker.picker = picker
       return wrappedPicker
   }

   func updateUIViewController(_ uiViewController: WrappedPhotoPicker, context: Context) {}

   func makeCoordinator() -> Coordinator {
    Coordinator(self)
   }

  class Coordinator: PHPickerViewControllerDelegate {
      let parent: WrappedPickerView

      init(_ parent: WrappedPickerView) {
          self.parent = parent
      }

     func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        self.parent.presentationMode.wrappedValue.dismiss()
        self.parent.wrappedPicker.dismiss(animated: false)
  
        self.parent.photoPickerResult = results.first
        print(results)
    
        guard let result = results.first else {
        return
       }
    
    
       self.parent.image = nil
    
       DispatchQueue.global().async {
            result.itemProvider.loadObject(ofClass: UIImage.self) { (object, error) in

         guard let imageLoaded = object as? UIImage else {
                return
            }
            DispatchQueue.main.async {
                self.parent.image = imageLoaded
            }    
         }
     }
    
    
   }
  }
 }
于 2021-11-28T10:29:20.253 回答
2

就像@Frustrated_Student 提到的那样,这为我解决了问题。.ignoreSafeArea(.keyboard)

为了详细说明@Frustrated_Student,这个问题与UIViewControllerRepresentable将视图视为许多 SwiftUI 视图以自动避开键盘有关。如果您像我一样使用工作表来展示选择器,那么您可以简单地将其添加.ignoreSafeArea(.keyboard)UIViewControllerRepresentable我的情况下的视图中,我称之为ImagePicker这里是一个更好的例子。

在哪里添加它.ignoreSafeArea(.keyboard)

.sheet(isPresented: $imagePicker) {
    ImagePicker(store: store)
        .ignoresSafeArea(.keyboard)
}

这是@Fogmeister 代码:

public struct ImagePicker: UIViewControllerRepresentable {

// Vars and setup stuff...
  @Environment(\.presentationMode) var presentationMode

  let viewStore: ViewStore<ImagePickerState, ImagePickerAction>
  
  public init(store: Store<ImagePickerState, ImagePickerAction>) {
    self.viewStore = ViewStore(store)
  }
  
// UIViewControllerRepresentable required functions
  public func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> some UIViewController {

    // Configuring the PHPickerViewController
    var config = PHPickerConfiguration()
    config.filter = PHPickerFilter.images
    
    let picker = PHPickerViewController(configuration: config)
    picker.delegate = context.coordinator
    return picker
  }
  
  public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
  
  public func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
  
// This is the coordinator that acts as the delegate
  public class Coordinator: PHPickerViewControllerDelegate {
    let parent: ImagePicker
    
    init(_ parent: ImagePicker) {
      self.parent = parent
    }
    
    public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
      picker.dismiss(animated: true)
      
      guard let itemProvider = results.first?.itemProvider,
        itemProvider.canLoadObject(ofClass: UIImage.self) else {
        return
      }
      
      itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
        if let image = image as? UIImage {
          DispatchQueue.main.async {
            self?.parent.viewStore.send(.imagePicked(image: image))
          }
        }
      }
    }
  }
}
于 2022-02-10T17:27:38.613 回答
1

PHPickerViewController 在键盘不可见但我的视图仍然被挤压的地方崩溃后,我开始遇到一个奇怪的 UI 错误。所以我怀疑是键盘/回避问题。我在父视图中禁用了键盘回避并设法阻止它崩溃。

.ignoresSafeArea(.keyboard)
于 2022-01-02T12:55:55.480 回答