documentPicker delegate not called by UIDocumentPickerViewController - ios

I have the following ViewController that implements functionality for importing files into the app using the UIDocumentPickerViewController:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func importFromFiles(origin: UIViewController?) {
let documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypeContent as String], in: .import)
documentPicker.delegate = self
documentPicker.allowsMultipleSelection = true
origin?.present(documentPicker, animated: true, completion: nil)
}
}
As you can see, the importFromFiles method receives a ViewController, which is simply the active VC. This method is called from the AppDelegate.
For now, the documentPicker method looks just as follows:
extension MyViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print("Files picked")
}
}
The picker is displayed and dismissed correctly, but the delegate is never called and therefore the print never executed.
Edit 1
The call to the importFromFiles method is happening inside the AppDelegate. More specifically, it happens while defining a closure for a SwiftTweaks tweak:
MyTweaks.importFromFiles.addClosure {
let topViewController = visibleViewController(root: self.window?.rootViewController)
let myVC: MyViewController = MyViewController()
myVC.importFromFiles(origin: topViewController)
}

It appears you need to retain the main object as you have
let myVC: MyViewController = MyViewController()
myVC.importFromFiles(origin: topViewController)
inside where you present that picker here MyViewController() isn't retained so make it an instance var like
var main = MyViewController()
main.importFromFiles(origin:self)

Related

How to load a UIViewController from a class that does not inherit UIviewController (from a swift file with class )?

I am working on building framework for a widget. I have two files as follows:
widgetBuilder.swift and
webWidget.swift
widgetBuilder.swift is file with getters and setters that takes certain values from the app that is going to use this widget framework.
Code from widgetBuilder.swift
import Foundation
class WidgetBuilder {
private var userId: String!
private var emailId: String!
public func setuserId(userId: String) -> WidgetBuilder{
self.userId = userId
return self
}
public func setEmailId(emailId: String) -> WidgetBuilder{
self.emailId = emailId
return self
}
public func build() -> WidgetBuilder{
// Wanted to load the webview from here
}
}
Once the initialization is done I would call the build function, I wanted to load the ViewController of webWidget.swift
Code from webWidget.swift
class webWidget: UIViewController, WKUIDelegate, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.uiDelegate = self
webView.navigationDelegate = self
view = webView
self.loadWebView()
}
public func loadWebView(){
let url = URL(string: "https://www.google.com")!
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
}
public func loadWidgetScreen() {
//Something is not correct here
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "WidgetController")
self.present(controller, animated: true, completion: nil)
}
}
How do I load the webWidget view from the widgetBuilder.swift and pass some data along ?
So this is how I solved , Posting it, might be helpful for somebody looking to load a ViewController from a swift file.
Once I call the the build() from my swift file , it has to load the view,
Added a var that gets the ViewController instance from the parent view that wants to load this widget.
Inside the widgetBuilder.swift
private var clientView: UIViewController?
public init(_ viewController:UIViewController){
self.clientView = viewController;
}
Then,
public func build(){
// Widget() creates ViewController instance , this viewcontroller is mapped to a widget.xib file
let controller: UIViewController = Widget() // Creating the instance of the view class
self.clientView?.addChild(controller) // self.clientView is the ViewController instance that wants to load widget view
self.clientView?.view.addSubview(controller.view)
controller.view.frame = (clientView?.view.bounds)!
controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
controller.didMove(toParent: clientView)
}
In webWidget.swift
replace your loadWidgetScreen() method with below
public func loadWidgetScreen() {
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
// topController should now be your topmost view controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "WidgetController")
self.present(controller, animated: true, completion: nil)
}
else {
print("topViewController not found")
}
}

How to pass a value from second view controller to first view controller through coordinator pattern

I want to use Soroush Khanlou coordinator pattern (http://khanlou.com) in my iOS app. My problem is how can I pass data back to my first view controller through coordinators?
Before using the coordinator pattern, I used a delegate(protocol) to pass data back from second view controller to the first one, because my first view controller was responsible for creating and presenting the second view controller, that was not a good solution. So I decided to use coordinator
to remove the job of app navigation from my view controllers.
This is the scenario:
class FirstViewController: UIViewController {
weak var coordinator: MainCoordinator?
func showSecondViewController(data: Data) {
coordinator?.presentSecondViewController(data: data) {[weak self] in
guard let self = self else { return }
//Do something
}
}
class MainCoordinator: NSObject, Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start(completion: (() -> Void)?) {
navigationController.delegate = self
let firstViewController = FirstViewController.instantiate()
firstViewController.coordinator = self
navigationController.pushViewController(firstViewController, animated: false)
}
func presentSecondViewController(data: Data, completion: #escaping () -> Void) {
let child = SecondCoordinator(data: data, navigationController: navigationController)
childCoordinators.append(child)
child.parentCoordinator = self
child.start() {
completion()
}
}
func refresh(data: SomeDataType) {
//data from second view controller is here but I don't know how to pass it to my first view controller
//there is no reference to my first view controller
//what is the best way to pass data back?
}
}
class SecondCoordinator: Coordinator {
weak var parentCoordinator: MainCoordinator?
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
var data: Data!
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
convenience init(data: Data, navigationController: UINavigationController) {
self.init(navigationController: navigationController)
self.data = data
}
func start(completion: (() -> Void)?) {
let vc = SecondViewController(data: data)
vc.coordinator = self
vc.delegate = self. //here I used delegate to get data from second view controller to pass it to first view controller
navigationController.present(vc, animated: true) {
completion!()
}
}
}
extension SecondCoordinator: SecondViewControllerDelegate {
func refresh(data: SomeDataType) {
parentCoordinator?.refresh(data: data) // I need to pass this data to my first view controller
}
}
Using delegate pattern or closure callback pattern, you can pass a value from second view controller to first view controller.
If you use delegate pattern, data flow is below.
SecondViewController -> SecondViewControllerDelegate -> SecondCoordinator -> SecondCoordinatorDelegate -> FirstCoorfinator -> FirstViewController
If you use closure callback pattern, you will pass a closure as a parameter of
start method or initializer of SecondCoordinator.

Display PDF file in Swift 4

import UIKit
import WebKit
class ViewController: UIViewController, UIDocumentInteractionControllerDelegate {
var docController: UIDocumentInteractionController!
override func viewDidLoad() {
super.viewDidLoad()
docController = UIDocumentInteractionController.init(url: URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(urlVal!))
docController.delegate = self
docController.presentPreview(animated: true)
}
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
}
Above code I'm not able to display the pdf file. Can anyone help?
By seeing your code it seems that you missed to add UIDocumentInteractionControllerDelegate delegate method.
class ViewController: UIViewController,UIDocumentInteractionControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
var docController = UIDocumentInteractionController.init(url: URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(urlVal!))
docController.delegate = self
docController.presentPreview(animated: true)
}
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
}
OR
You can also view PDF by loading it into WKWebView.
override func viewDidLoad() {
super.viewDidLoad()
let pdfFilePath = Bundle.main.url(forResource: "iostutorial", withExtension: "pdf")
let urlRequest = URLRequest.init(url: pdfFilePath!)
webView = WKWebView(frame: self.view.frame)
webView.load(request)
self.view.addSubview(webView)
}
Basically you are missing the UIDocumentInteractionControllerDelegate implementation. For preview, you should implement this method
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController
Return which ViewController need to display the Preview. If you pass the self View Controller it will display the PDF preview in the existing view controller modally. Just do this one in your View controller if you want to display the preview in the same controller.
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
If you are already doing this in your code, there is high chance of PDF URL path might be wrong.
I would use the Quicklook framework instead, it supports a wide range of document types:
iWork documents
Microsoft Office documents
PDF files
Images
Text files
Rich-Text Format documents
Comma-Separated Value files (csv)
Supports sharing of the relevant documents as well and is easy to implement.
Follow this tutorial on how to do it in Swift: https://www.appcoda.com/quick-look-framework/
Swift 5
Import this framework
import SafariServices
Then call this sentences whenever you need
if let url = URL(string: "YOUR_PDF_URL") {
let config = SFSafariViewController.Configuration()
config.entersReaderIfAvailable = true
let vc = SFSafariViewController(url: url, configuration: config)
present(vc, animated: true)
}
The code will present a Safari view with the pdf on it.

Calling function from another ViewController in swift

I have already looked in Stackoverflow but I can't get an answer. I want to create function that stop playing the sound in another ViewController. But when I clicked the stop button, it cracked and showed "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)". This is my code.
First ViewController
import UIKit
import AVFoundation
class FirstVC: UIViewController {
var metronome: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
do {
let resourcePath1 = Bundle.main.path(forResource: "music", ofType: "mp3")
let url = NSURL(fileURLWithPath: resourcePath1!)
try metronome = AVAudioPlayer(contentsOf: url as URL)
metronome.prepareToPlay()
metronome.play()
} catch let err as NSError {
print(err.debugDescription)
}
}
and another Viewcontroller is
import UIKit
class SecondVC: UIViewController {
var metronomePlay = FirstVC()
#IBAction func stopBtnPressed(_ sender: Any) {
metronomePlay.metronome.stop() //"EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)"
}
}
As of swift 4.1 today, this code worked for me:
Put this in sending controller:
NotificationCenter.default.post(name: Notification.Name(rawValue: "disconnectPaxiSockets"), object: nil)
Put this in receiving controller viewDidLoad() or viewWillAppear():
NotificationCenter.default.addObserver(self, selector: #selector(disconnectPaxiSocket(_:)), name: Notification.Name(rawValue: "disconnectPaxiSockets"), object: nil)
and then the following function in your receiving controller class:
#objc func disconnectPaxiSocket(_ notification: Notification) {
ridesTimer.invalidate()
shared.disconnectSockets(socket: self.socket)
}
Swift 5:
Put this in the Action
NotificationCenter.default.post(name: Notification.Name("NewFunctionName"), object: nil)
Put this in viewdidload() in a different viewcontroller (where is the function you want to use)
NotificationCenter.default.addObserver(self, selector: #selector(functionName), name: Notification.Name("NewFunctionName"), object: nil)
The function
#objc func functionName (notification: NSNotification){ //add stuff here}
I hope I was helpful
You are creating a NEW copy of FirstVC and calling stop on something that is not yet initialised.
You should really use a delegate in this case, something like
protocol controlsAudio {
func startAudio()
func stopAudio()
}
class FirstVC: UIViewController, controlsAudio {
func startAudio() {}
func stopAudio() {}
// later in the code when you present SecondVC
func displaySecondVC() {
let vc = SecondVC()
vc.delegate = self
self.present(vc, animated: true)
}
}
class SecondVC: UIViewController {
var delegate: controlsAudio?
// to start audio call self.delegate?.startAudio)
// to stop audio call self.delegate?.stopAudio)
}
So you are passing first VC to the second VC, so when you call these functions you are doing it on the actual FirstVC that is in use, rather than creating a new one.
You could do this without protocols if you like by replacing the var delegate: controlsAudio? with var firstVC: FirstVC? and assigning that, but I wouldn't recommend it
I use this way to call my functions from another viewControllers:
let sendValue = SecondViewController();
sendValue.YourFuncion(data: yourdata);
You can call function from other viewControllers in many ways.
Two ways that are already discussed above are by delegates & protocols and by sending notifications.
Another way is by passing closures to your second viewController from firstVC.
Below is the code in which while segueing to SecondVC we pass a closure to stop the metronome.
There will be no issue because you are passing the same firstVC (not creating a new instance), so the metronome will not be nil.
class FirstVC: UIViewController {
var metronome: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
do {
let resourcePath1 = Bundle.main.path(forResource: "music", ofType: "mp3")
let url = NSURL(fileURLWithPath: resourcePath1!)
try metronome = AVAudioPlayer(contentsOf: url as URL)
metronome.prepareToPlay()
metronome.play()
} catch let err as NSError {
print(err.debugDescription)
}
let secondVC = SecondVC()
secondVC.stopMetronome = { [weak self] in
self?.metronome.stop()
}
present(secondVC, animated: true)
}
}
class SecondVC: UIViewController {
var metronomePlay = FirstVC()
var stopMetronome: (() -> Void)? // stopMetronome closure
#IBAction func stopBtnPressed(_ sender: Any) {
if let stopMetronome = stopMetronome {
stopMetronome() // calling the closure
}
}
}
var metronomePlay = FirstVC()
you are creating a new instance on FirstVC, instead you should perform the function on the same instance that of already loaded FirstVC.
Updating #Scriptable's answer for Swift 4
Step 1 :
Add this code in your view controller, from which you want to press button click to stop sound.
#IBAction func btnStopSound(_ sender: AnyObject)
{
notificationCenter.post(name: Notification.Name("stopSoundNotification"), object: nil)
}
Step 2:
Now its final step. Now add this below code, to your result view controller, where you want to automatically stop sound.
func functionName (notification: NSNotification) {
metronomePlay.metronome.stop()
}
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "functionName",name:"stopSoundNotification", object: nil)
}
You are initialising metronome in viewDidLoad method of FirstVC.
In SecondVC, you are initialising metronomePlay as a stored property, but never asking for ViewController's view and thus viewDidLoad of FirstVC is not getting called which results in metronome(stored property) not getting initialised.
You initialize metronome on FirstVC in viewDidLoad, which won't happen until you load the view of metronomePlay instantiated in SecondVC.
You have to call _ = metronomePlay.view, which will lazily load the view of SecondVC and subsequently execute viewDidLoad, before actually calling metronomePlay.metronome.
Try this in SecondVC. var metronomePlay = FirstVC().metronome
Either use the notification process to stop from anywhere or use same FirstVC instance from SecondVC class.

Use internal func from a class to another class in swift

I have a profile class and settings class
profile class contains an internal function
class Profile: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
internal func profileSelectFromGallery(sender: Profile){
let myPickerController = UIImagePickerController()
myPickerController.delegate = sender;
myPickerController.sourceType =
UIImagePickerControllerSourceType.PhotoLibrary
sender.presentViewController(myPickerController, animated:true, completion: nil)
}
}
I want to use profileSelectFromGallery in setting class and I have two tries below
class SettingsVC: UITableViewController {
// I will call this private function on a click events
private func selectFromGallery(){
// let profile = Profile()
// profile.profileSelectFromGallery(self)
Profile.profileSelectFromGallery(self)
}
}
The above codes results into Cannot convert value of type 'SettingsVC' to expected argument type 'Profile' since profileSelectFromGallery needs a parameter of a class Profile so what i want to do is change sender so that i can use it from any of my class and not just my Profile class.
So the problem is that you can't convert a SettingsVC into a Profile. If you look at the method signature you'll see it's expecting a Profile:
internal func profileSelectFromGallery(sender: Profile)
You are trying to pass in a SettingVC in selectFromGallery()
Inside profileSelectFromGallery you want the sender to be both a UIViewController and a UIImagePickerControllerDelegate. There's a couple ways you could do this:
The simplest is to change the method signature. You'd do something like this:
internal func profileSelectFromGallery(sender: UIImagePickerControllerDelegate){
guard let vc = sender as? UIViewController else {
return
}
let myPickerController = UIImagePickerController()
myPickerController.delegate = sender;
myPickerController.sourceType =
UIImagePickerControllerSourceType.PhotoLibrary
vc.presentViewController(myPickerController, animated:true, completion: nil)
}
Theres 2 main things here: sender is changed to the proper delegate method, and theres a guard statement to cast it to a VC for the presentViewController call.
The much more awesome way to do this is to use protocol extensions!
extension UIImagePickerControllerDelegate where Self: UIViewController, Self: UINavigationControllerDelegate {
func profileSelectFromGallery() {
let myPickerController = UIImagePickerController()
myPickerController.delegate = self
myPickerController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
self.presentViewController(myPickerController, animated:true, completion: nil)
}
}
Basically what I'm doing here is adding a method for every UIImagePickerControllerDelegate thats also an UIViewController and an UINAvigationControllerDelegate. This means that I can call it on both Profile and SettingVC (once you add the necessary delegates to SettingVC). All you would need to do is:
let profile = Profile()
profile.profileSelectFromGallery()
let settingVC = SettingVC()
settingVC.profileSelectFromGallery()
Declare new protocol as:
protocol PickerProtocol : UIImagePickerControllerDelegate, UINavigationControllerDelegate {
}
Now your Profile class will look like:
class Profile: UIViewController, PickerProtocol {
//Option 1
internal func profileSelectFromGallery(contoller: UIViewController, pickerProtocol: PickerProtocol){
let myPickerController = UIImagePickerController()
myPickerController.delegate = pickerProtocol
myPickerController.sourceType =
UIImagePickerControllerSourceType.PhotoLibrary
contoller.presentViewController(myPickerController, animated:true, completion: nil)
}
//Option 2
internal func profileSelectFromGalleryOption2(sender : UIViewController? ) {
var viewContoller : UIViewController = self
if let unwrappedSender = sender {
viewContoller = unwrappedSender
}
let myPickerController = UIImagePickerController()
if let pickerProtocol = viewContoller as? PickerProtocol {
myPickerController.delegate = pickerProtocol
} else {
myPickerController.delegate = self //Assign self as default
}
myPickerController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
viewContoller.presentViewController(myPickerController, animated:true, completion: nil)
}
}
class SettingsVC1: UITableViewController {
// I will call this private function on a click events
private func selectFromGallery(){
let profile = Profile()
profile.profileSelectFromGallery(self, pickerProtocol:profile)
profile.profileSelectFromGalleryOption2(self)
//Or
profile.profileSelectFromGalleryOption2(nil)//profile itself delegate and presenting controller
}
}
// OR
class SettingsVC2: UITableViewController, PickerProtocol {
// I will call this private function on a click events
private func selectFromGallery(){
let profile = Profile()
profile.profileSelectFromGallery(self, pickerProtocol:self)
profile.profileSelectFromGalleryOption2(self)
//Or
profile.profileSelectFromGalleryOption2(nil)//profile itself delegate and presenting controller
}
}
I would use POP (protocol oriented programming) like this:
protocol SelectorProtocol: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
}
extension SelectorProtocol where Self: UIViewController {
func profileSelectFromGallery() {
let myPickerController = UIImagePickerController()
myPickerController.delegate = self;
myPickerController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
self.presentViewController(myPickerController, animated:true, completion: nil)
}
}
class Profile: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate, SelectorProtocol {
func foo() {
profileSelectFromGallery()
}
}
class SettingsVC: UITableViewController, SelectorProtocol {
// I will call this private function on a click events
private func selectFromGallery(){
profileSelectFromGallery()
}
}
You are trying to statically call profileSelectFromGallery: even though it is an instance method.
Try changing the method definition to:
internal static func profileSelectFromGallery(sender: Profile){
As for being able to use any class as a delegate, create a custom Protocol and ensure that the sender conforms to this protocol. See here (specifically the heading titled Protocols) for more information: http://www.raywenderlich.com/115300/swift-2-tutorial-part-3-tuples-protocols-delegates-and-table-views
perhaps the following will work:
class SettingsVC: UITableViewController {
// I will call this private function on a click events
private func selectFromGallery(){
let prof = Profile()
prof.profileSelectFromGallery(prof)
}
}

Resources