imagePickerController didFinishPickingMediaWithInfo not being called - swift

I am trying for the first time to use an external library, WDImagePicker. Below is the WDImagePicker code relevant to my problem.
#objc public protocol WDImagePickerDelegate {
#objc optional func imagePicker(imagePicker: WDImagePicker, pickedImage: UIImage)
#objc optional func imagePickerDidCancel(imagePicker: WDImagePicker)
}
#objc public class WDImagePicker: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate, WDImageCropControllerDelegate {
public var delegate: WDImagePickerDelegate?
private var _imagePickerController: UIImagePickerController!
public var imagePickerController: UIImagePickerController {
return _imagePickerController
}
override public init() {
super.init()
self.cropSize = CGSize(width: 320, height: 320)
_imagePickerController = UIImagePickerController()
_imagePickerController.delegate = self
_imagePickerController.sourceType = .photoLibrary
}
public func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
print("this line never runs\n\n")
}
}
In the controller I wrote myself, I have this method, which presents cropImagePicker's imagePickerContoller; however even though the delegate of this controller is cropImagePicker, cropImagePicker's ImagePickerControllerDelegate methods never get called (I tested all of them).
#IBAction func cameraButtonPressed() {
let cropImagePicker = WDImagePicker()
cropImagePicker.cropSize = CGSize(width: 50, height: 50)
cropImagePicker.delegate = self
cropImagePicker.resizableCropArea = false
cropImagePicker.imagePickerController.delegate = cropImagePicker
self.present(cropImagePicker.imagePickerController, animated: true, completion: nil)
}
Why is this happening? Could it be because of the swift 3 changes I had to make to the library? And most importantly, how can I get around this? All input is appreciated.

Related

Pick and play a video in SwiftUI — how convert from UIKit code?

I'm working on a camera app and I've got a problem.
I've never used UIKit to build an app, but a lot of the reference code does.
So I tried to convert it using swiftUI but I failed.
There is UIKit code which I want to convert to SwiftUI.
static func startMediaBrowser(
delegate: UIViewController & UINavigationControllerDelegate & UIImagePickerControllerDelegate,
sourceType: UIImagePickerController.SourceType
) {
guard UIImagePickerController.isSourceTypeAvailable(sourceType)
else { return }
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [kUTTypeMovie as String]
mediaUI.allowsEditing = true
mediaUI.delegate = delegate
delegate.present(mediaUI, animated: true, completion: nil)
}
import AVKit
import MobileCoreServices
import UIKit
class PlayVideoViewController: UIViewController {
#IBAction func playVideo(_ sender: AnyObject) {
VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
}
}
// MARK: - UIImagePickerControllerDelegate
extension PlayVideoViewController: UIImagePickerControllerDelegate {
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
else { return }
dismiss(animated: true) {
let player = AVPlayer(url: url)
let vcPlayer = AVPlayerViewController()
vcPlayer.player = player
self.present(vcPlayer, animated: true, completion: nil)
}
}
}
// MARK: - UINavigationControllerDelegate
extension PlayVideoViewController: UINavigationControllerDelegate {
}
Here's what I've tried, and the compilation passes, but it only does UIImagePickerController() , and the delegate function I wrote doesn't work.
import SwiftUI
import AVKit
import MobileCoreServices
import UIKit
struct ContentView: View {
#State private var isShowVideoLibrary = false
#State private var image = UIImage()
#State private var isShowCamara = false
var body: some View {
VStack {
HStack{
Button {
isShowVideoLibrary.toggle()
} label: {
Text("Play video")
}
}
}
.sheet(isPresented: $isShowVideoLibrary) {
VideoPicker(sourceType: .photoLibrary)
}
}
struct VideoPicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Environment(\.presentationMode) private var presentationMode
func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPicker>) -> UIViewController {
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [kUTTypeMovie as String]
mediaUI.allowsEditing = true
mediaUI.delegate = context.coordinator
return mediaUI
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
final class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
var parent: VideoPicker
init(_ parent: VideoPicker) {
self.parent = parent
}
private func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) -> UIViewController {
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
else { return AVPlayerViewController()}
// 2
parent.presentationMode.wrappedValue.dismiss()
//3
let player = AVPlayer(url: url)
let vcPlayer = AVPlayerViewController()
vcPlayer.player = player
return vcPlayer
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
The problem you have is that you haven't implemented the correct UIImagePickerControllerDelegate function signature.
Your Coordinator has:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) -> UIViewController
while the correct method is:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
The method won't get called unless the signature matches exactly.
A solution
The UIImagePickerController is just used to select the image or video, you'll need additional cod to play the selected video. Luckily SwiftUI has a VideoPlayer that makes it easy:
import UniformTypeIdentifiers
struct ContentView: View {
#State private var isShowVideoLibrary = false
#State private var url: URL?
var body: some View {
Group {
if let url {
VideoPlayer(player: AVPlayer(url: url))
} else {
VStack {
HStack{
Button {
isShowVideoLibrary.toggle()
} label: {
Text("Play video")
}
}
}
}
}
.sheet(isPresented: $isShowVideoLibrary) {
VideoPicker(sourceType: .photoLibrary) { url in
self.url = url
isShowVideoLibrary = false
}
}
}
}
struct VideoPicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
let didFinish: (URL?) -> Void
func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPicker>) -> UIViewController {
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [UTType.movie.identifier]
mediaUI.allowsEditing = true
mediaUI.delegate = context.coordinator
return mediaUI
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
final class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
let didFinish: (URL?) -> Void
init(didFinish: #escaping (URL?) -> Void) {
self.didFinish = didFinish
}
// This func passes the URL back to the calling View
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == UTType.movie.identifier,
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
else {
didFinish(nil)
return
}
didFinish(url)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(didFinish: didFinish)
}
}

SWIFTUI warning imagePickerController nearly matches - happens only in existing swift project file

I'm upgrading an existing app with some swift views. Now I'm getting the warning:
Instance method 'imagePickerController(:didFinishPickingMediaWithInfo:)' nearly matches optional requirement 'imagePickerController(:didFinishPickingMediaWithInfo:)' of protocol 'UIImagePickerControllerDelegate'
at the function:
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[.originalImage] as? UIImage {
self.onImagePicked(image)
}
self.onDismiss()
}
Additionally I get the error message:
Cannot infer contextual base in reference to member 'originalImage'
The funny thing is, if I copy the code in a new project everything is fine (no warning, no error). In order to check whether I may have some effects from other views/methods within the existing project, I copied my existing project and deleted all other files, except the one with the imagepicker - still the warning and the error.
Is there any setup or other issue which might be the reason? Any help is more than appreciated - working on that the third day now ....
The whole code is:
import SwiftUI
public struct ImagePickerView: UIViewControllerRepresentable {
private let sourceType: UIImagePickerController.SourceType
private let onImagePicked: (UIImage) -> Void
#Environment(\.presentationMode) private var presentationMode
public init(sourceType: UIImagePickerController.SourceType, onImagePicked: #escaping (UIImage) -> Void) {
self.sourceType = sourceType
self.onImagePicked = onImagePicked
}
public func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = self.sourceType
picker.delegate = context.coordinator
return picker
}
public func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
public func makeCoordinator() -> Coordinator {
Coordinator(
onDismiss: { self.presentationMode.wrappedValue.dismiss() },
onImagePicked: self.onImagePicked
)
}
public class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
private let onDismiss: () -> Void
private let onImagePicked: (UIImage) -> Void
init(onDismiss: #escaping () -> Void, onImagePicked: #escaping (UIImage) -> Void) {
self.onDismiss = onDismiss
self.onImagePicked = onImagePicked
}
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[.originalImage] as? UIImage {
self.onImagePicked(image)
}
print("vor dismiss")
self.onDismiss()
}
public func imagePickerControllerDidCancel(_: UIImagePickerController) {
self.onDismiss()
}
}
}
The project Format is Xcode 12.0-compatible, iOS Deployment Target is 14.0,
The project includes originally also watch
Got the answer, can't explain but instead of
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
it works fine with
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
The problem is solved, however, if anyone could explain - more than welcome

Swift 5 - Issues With Passing Data From Class To Class

As an exercise to learn Swift, I'm creating a simple app where you use ImagePickerController to select a photo and get data about the photo. For now, I'm just pulling pixelWidth and pixelHeight data from photo's PHAsset.
My Setup: I have a ViewController class which I created using Storyboard that includes the UI, ImagePickerController and it's delegate which after selecting photo, will update data in another class called TestGlobalData.
The problem I'm running into is that while I'm able to update variables from ViewController to TestGlobalData, I can't get it to update back on ViewController
Here is my code. Any help would be appreciated, I'm totally stumped (As mentioned I'm also new to Swift, so pointing out any fundamental things I'm not getting would be appreciated too! )
// TestViewController.swift
class TestViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet weak var testPhotoView: UIImageView!
#IBOutlet weak var testWidthLabel: UILabel!
#IBOutlet weak var testHeightLabel: UILabel!
var testWidthText: String?
var testHeightText: String?
var selectionFromPicker: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
}
// Get imagePickerController ///////////////////////////////////////////////////////////////////
#IBAction func getPhotoButton(_ sender: Any) {
getImagePicker()
}
func getImagePicker() {
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
imagePickerController.sourceType = .photoLibrary
imagePickerController.allowsEditing = false
present (imagePickerController, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
defer { dismiss (animated: true, completion: nil)}
guard let selectedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
guard let asset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset else { return }
selectionFromPicker = selectedImage
let data = TestGlobalData()
data.testData = asset // Updates PHAsset
data.updateData() // Data shows as updated here
data.pushData() // Data shows as updated here too
self.updateTestPhoto() // Photo updates successfully (photo does not get passed)
self.textToLabel() // Assigns text to UILobel
self.checkData() // Data is lost and shows as nil here
}
// Functions //////////////////////////////////////////////////////////////////////////////
// Assign Text To Label
func textToLabel() {
testWidthLabel.text = testWidthText
testHeightLabel.text = testHeightText
}
// Update Photo To Selected
func updateTestPhoto() {
testPhotoView.image = selectionFromPicker
}
// Final Check
// TestGlobalData.swift
class TestGlobalData {
var testData: PHAsset?
var testWidth = Int()
var testHeight = Int()
var widthInString = String()
var heightInString = String()
func updateData() {
testWidth = testData!.pixelWidth
testHeight = testData!.pixelHeight
widthInString = String(testWidth)
heightInString = String(testHeight)
//widthInString and testWidth updated successfully at this point
}
func pushData() {
let vc = TestViewController()
vc.testWidthText = widthInString
vc.testHeightText = heightInString
//vc.testWidthText show as updated successfully here
}
}
The problem is you are creating a new instance of the TestViewController in the TestGlobalData class, specifically in the pushData() function.
Instead change the pushData to:
func pushData(vc: UIViewController) {
vc.testWidthText = widthInString
vc.testHeightText = heightInString
}
and change when you call the method as well to:
data.pushData(self)
Here is some additional resources that should help you understand everything better :)
https://code.tutsplus.com/tutorials/swift-from-scratch-an-introduction-to-classes-and-structures--cms-23197
https://www.python-course.eu/python3_class_and_instance_attributes.php

"Extensions must not contain stored properties" preventing me from refactoring code

I have a 13 lines func that is repeated in my app in every ViewController, which sums to a total of 690 lines of code across the entire project!
/// Adds Menu Button
func addMenuButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
let menuImage = UIImage(named: "MenuWhite")
menuButton.setImage(menuImage, for: .normal)
menuButton.addTarget(self, action: #selector(menuTappedAction), for: .touchDown)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuButton)
}
/// Launches the MenuViewController
#objc func menuTappedAction() {
coordinator?.openMenu()
}
for menuTappedAction function to work, I have to declare a weak var like this:
extension UIViewController {
weak var coordinator: MainCoordinator?
But by doing this I get error Extensions must not contain stored properties
What I tried so far:
1) Removing the weak keyword will cause conflicts in all my app.
2) Declaring this way:
weak var coordinator: MainCoordinator?
extension UIViewController {
Will silence the error but the coordinator will not perform any action. Any suggestion how to solve this problem?
You can move your addMenuButton() function to a protocol with a protocol extension. For example:
#objc protocol Coordinated: class {
var coordinator: MainCoordinator? { get set }
#objc func menuTappedAction()
}
extension Coordinated where Self: UIViewController {
func addMenuButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
let menuImage = UIImage(named: "MenuWhite")
menuButton.setImage(menuImage, for: .normal)
menuButton.addTarget(self, action: #selector(menuTappedAction), for: .touchDown)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuButton)
}
}
Unfortunately, you can't add #objc methods to class extensions (see: this stackoverflow question), so you'll still have to setup your view controllers like this:
class SomeViewController: UIViewController, Coordinated {
weak var coordinator: MainCoordinator?
/// Launches the MenuViewController
#objc func menuTappedAction() {
coordinator?.openMenu()
}
}
It'll save you some code, and it will allow you to refactor the bigger function addMenuButton(). Hope this helps!
For it to work in an extension you have to make it computed property like so : -
extension ViewController {
// Make it computed property
weak var coordinator: MainCoordinator? {
return MainCoordinator()
}
}
You could use objc associated objects.
extension UIViewController {
private struct Keys {
static var coordinator = "coordinator_key"
}
private class Weak<V: AnyObject> {
weak var value: V?
init?(_ value: V?) {
guard value != nil else { return nil }
self.value = value
}
}
var coordinator: Coordinator? {
get { (objc_getAssociatedObject(self, &Keys.coordinator) as? Weak<Coordinator>)?.value }
set { objc_setAssociatedObject(self, &Keys.coordinator, Weak(newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
}
This happens because an extension is not a class, so it can't contain stored properties. Even if they are weak properties.
With that in mind, you have two main options:
The swift way: Protocol + Protocol Extension
The nasty objc way: associated objects
Option 1: use protocol and a protocol extension:
1.1. Declare your protocol
protocol CoordinatorProtocol: class {
var coordinator: MainCoordinator? { get set }
func menuTappedAction()
}
1.2. Create a protocol extension so you can pre-implement the addMenuButton() method
extension CoordinatorProtocol where Self: UIViewController {
func menuTappedAction() {
// Do your stuff here
}
}
1.3. Declare the weak var coordinator: MainCoordinator? in the classes that will be adopting this protocol. Unfortunately, you can't skip this
class SomeViewController: UIViewController, CoordinatorProtocol {
weak var coordinator: MainCoordinator?
}
Option 2: use objc associated objects (NOT RECOMMENDED)
extension UIViewController {
private struct Keys {
static var coordinator = "coordinator_key"
}
public var coordinator: Coordinator? {
get { objc_getAssociatedObject(self, &Keys.coordinator) as? Coordinator }
set { objc_setAssociatedObject(self, &Keys.coordinator, newValue, .OBJC_ASSOCIATION_ASSIGN) }
}
}
You can do it through subclassing
class CustomVC:UIViewController {
weak var coordinator: MainCoordinator?
func addMenuButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
let menuImage = UIImage(named: "MenuWhite")
menuButton.setImage(menuImage, for: .normal)
menuButton.addTarget(self, action: #selector(menuTappedAction), for: .touchDown)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuButton)
}
/// Launches the MenuViewController
#objc func menuTappedAction() {
coordinator?.openMenu()
}
}
class MainCoordinator {
func openMenu() {
}
}
class ViewController: CustomVC {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Use a NSMapTable to create a state container for your extension, but make sure that you specify to use weak references for keys.
Create a class in which you want to store the state. Let's call it ExtensionState and then create a map as a private field in extension file.
private var extensionStateMap: NSMapTable<TypeBeingExtended, ExtensionState> = NSMapTable.weakToStrongObjects()
Then your extension can be something like this.
extension TypeBeingExtended {
private func getExtensionState() -> ExtensionState {
var state = extensionStateMap.object(forKey: self)
if state == nil {
state = ExtensionState()
extensionStateMap.setObject(state, forKey: self)
}
return state
}
func toggleFlag() {
var state = getExtensionState()
state.flag = !state.flag
}
}
This works in iOS and macOS development, but not on server side Swift as there is no NSMapTable there.

How do I expose functions I add to a file that I created with cocoa pods?

I'm sure this question has been answered but I have no idea how it would be worded so please bear with me, clearly I have a lot to learn about using libraries as this is my first time. I downloaded a library with pod install and am now trying to edit a class in it, but the function I add isn't exposed to my code outside of the pod file. Mind you, the pod came with a bunch of other files but I can't find any references to the other public variables of this class in them so I'm not sure how they are exposed to my outside code but the function I am creating (its the really obviously named one is not). Please help me
import UIKit
#objc public protocol WDImagePickerDelegate {
#objc optional func imagePicker(imagePicker: WDImagePicker, pickedImage: UIImage)
#objc optional func imagePickerDidCancel(imagePicker: WDImagePicker)
}
#objc public class WDImagePicker: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate, WDImageCropControllerDelegate {
public var delegate: WDImagePickerDelegate?
public var cropSize: CGSize!
public var resizableCropArea = false
public func myCustomFunctionAddedAfterPodInstall(){
print("hello world")
}
override public init() {
super.init()
print("WD was installed")
self.cropSize = CGSize(width: 320, height: 320)
_imagePickerController = UIImagePickerController()
_imagePickerController.delegate = self
_imagePickerController.sourceType = .photoLibrary
}
}
What I found is that XCode will not autocomplete that custom function, and it even gave a "no such function" error before I built and ran it. Once I did that, however, everything worked fine. Here's my code:
WDImagePicker.swift:
#objc open class WDImagePicker: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate, WDImageCropControllerDelegate {
open var delegate: WDImagePickerDelegate?
open var cropSize: CGSize!
open var resizableCropArea = false
public func myCustomFunctionAddedAfterPodInstall(){
print("hello world")
}
...
}
In my ViewController:
override func viewDidLoad() {
super.viewDidLoad()
imagePicker = WDImagePicker()
imagePicker?.cropSize = CGSize(width: 280, height: 90)
imagePicker?.delegate = self
imagePicker?.myCustomFunctionAddedAfterPodInstall()
self.present((imagePicker?.imagePickerController)!, animated: true, completion: nil)
}