How to set maximum files to select in UIDocumentPickerViewController - ios

I need to set a number of files that can be selected by enabling the UIDocumentPickerViewController allowsMultipleSelection, but I didn't find any properties that I could use to set this.
It is possible?

In order to do this, you'll need to implement UIDocumentPickerDelegate's didPickDocumentsAt. It would look something like this:
class YourViewController: UIDocumentPickerViewController {
let maxDocs = 3
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
extension YourViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
// check to make sure you haven't hit your document cap specified above
guard urls.count < maxDocs else { return }
// if you pass the guard, business as usual
}
}
I haven't worked with this class before, so there may be some rough edges, but that's the physics for poets way of how you'd do it. You may need to refine it a bit, as I'm not sure where the URLs for the delegate method come from. You could throw in a breakpoint when this method is called and do po urls to see what's in there.
In looking at the delegate methods available for this class, I don't see one for selecting an individual document, so you'll need to tinker around to see what happens with the array of URLs that's a parameter in the delegate method and figure out how much bookkeeping you need to do to handle state toggling between selected and !selected.

You'll need to use :
UIDocumentBrowserViewController
instead of UIDocumentPickerViewController. It will allow you to select multiple items :
let document = UIDocumentBrowserViewController(forOpeningFilesWithContentTypes: ["public.text", "com.apple.iwork.pages.pages", "public.data"])
func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL]) {
print("result.........\(documentURLs)")
}
override func viewDidLoad() {
super.viewDidLoad()
document.delegate = self
document.allowsPickingMultipleItems = true
}
And don't forget to add UIDocumentBrowserViewControllerDelegate to your VC.

Related

Using Markup via UIDocumentInteractionController

I am trying to support Apple's Markup of PDFs via UIDocumentInteractionController for files in my Documents folder on iPad. I want the documents edited in-place, so my app can load them again after the user is finished. I have set the Info.plist options for this, and the in-place editing does seem to work. Changes are saved to the same file.
When I bring up the UIDocumentInteractionController popover for the PDF, I am able to choose "Markup", which then shows the PDF ready for editing. I can edit it too. The problem is when I click "Done": I get a menu appear with the options "Save File To..." and "Delete PDF". No option just to close the editor or save.
The frustrating thing is, I can see via Finder that the file is actually edited in-place in the simulator, and is already saved when this menu appears. I just want the editor to disappear and not confuse the user. Ie I want "Done" to be "Done".
Perhaps related, and also annoying, is that while the markup editor is visible, there is an extra directory added to Documents called (A Document Being Saved By <<My App Name>>), and that folder is completely empty the whole time. Removing the folder during editing does not change anything.
Anyone have an idea if I am doing something wrong, or if there is a way to have the Done button simply dismiss?
In case others have this issue, I believe it is a bug in UIDocumentInteractionController in how it sets up the QLPreviewController it uses internally. If I proxy the delegate of the QLPreviewController, and return .updateContents from previewController(_:editingModeFor:), it works as expected.
Here is my solution. The objective is simple enough, but actually capturing the private QLPreviewController was not easy, and I ended up using a polling timer. There may be a better way, but I couldn't find it.
import QuickLook
class ViewController: UIViewController, UIDocumentInteractionControllerDelegate {
/// This wraps the original delegaet of the QLPreviewController,
/// so we can return .updateContents from previewController(_:editingModeFor:)
let delegateProxy = QLPreviewDelegateProxy()
var documentInteractionController = UIDocumentInteractionController()
/// A timer we use to update the QL controller
/// Ideally, we would use callbacks or delegate methds, but couldn't
/// find a satisfactory set to do the job. Instead we poll (like an animal)
var quicklookControllerPollingTimer: Timer?
/// Use this to track the preview controller created by UIDocumentInteractionController
var quicklookController: QLPreviewController?
/// File URL of the PDF we are editing
var editURL: URL!
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
quicklookControllerPollingTimer?.invalidate()
}
#IBAction func showPopover(_ sender: Any?) {
documentInteractionController.url = editURL
documentInteractionController.delegate = self
documentInteractionController.presentOptionsMenu(from: button.bounds, in: button, animated: true)
quicklookControllerPollingTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [unowned self] timer in
guard quicklookController == nil else {
if quicklookController?.view.window == nil {
quicklookController = nil
}
return
}
if let ql = presentedViewController?.presentedViewController as? QLPreviewController, ql.view.window != nil {
self.quicklookController = ql
// Extra delay gives UI time to update
DispatchQueue.main.asyncAfter(deadline: .now()+0.1) {
guard let ql = self.quicklookController else { return }
delegateProxy.originalDelegate = ql.delegate
ql.delegate = delegateProxy
ql.reloadData()
}
}
}
}
}
class QLPreviewDelegateProxy: NSObject, QLPreviewControllerDelegate {
weak var originalDelegate: QLPreviewControllerDelegate?
/// All this work is just to return .updateContents here. Doing this makes it all work properly.
/// Must be a bug in UIDocumentInteractionController
func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
.updateContents
}
}

How to define functions for class in different file in Swift?

I have a swift class that has about 10 different functions in it. It's over 400 lines of code and needs to be broken up. I want to put some functions in different files. What is the best way to do this? Maybe inheritance?
You can create different files and then put some method in there within extension.
Example:
class MyMessyViewController: UIViewController {
var oneVariable: String = ""
override func viewDidLoad() {
super.viewDidLoad()
anotherFunctionFromThisExtension()
}
func one(){
}
func two(){
}
func three(){
}
}
Then Create a new file and put more functions into this file within an extension.
extension MyMessyViewController {
func anotherFunctionFromThisExtension() {
oneVariable = "I made this change from this File"
print(oneVariable)
}
}
Best practice if you have Delegates, Collection/TableViews in you view controller, you can separate them with extensions, just instead of simple extension MyMessyViewController { } write like extension MyMessyViewController: UICollectionViewDelegate, UICollectionViewDataSource { }

Refactoring CNContactPicker UI Code using the delegate pattern in Swift

I have implemented the CNContactPickerViewController ContactsUI provided by iOS in iOS10 succesfully in a view controller so I can have a user select multiple contacts to invite to an event. I am trying to reduce the size of this single view controller by implementing the delegate pattern, and am stuck on a black screen. I have looked at a few resources, and think I am calling the delegate and defining the protocol accordingly. I have a view controller, CreateEventViewController and it implements my self defined ContactsToInviteDelegate. This protocol is as follows:
protocol ContactsToInviteDelegate : class {
//array of array of KV-pairs where inner array is {"email":"email#gmail.com", "phone": "+18965883371"}
//array of JSON objects to upload
func contactsToInvite(_ contactsStructure: [[String:String]])
}
My ContactPickerViewController self defined class is as follows:
class ContactPickerViewController: UIViewController, CNContactPickerDelegate {
//class variables
let phoneNumberKit = PhoneNumberKit()
weak var delegate: ContactsToInviteDelegate?
var contactsToSendInvitesTo = [[String:String]]()
func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
contacts.forEach { contact in
let phoneNum = contact.phoneNumbers.first
var stringPhoneNumber = String()
do{
let phoneNumber = try self.phoneNumberKit.parse((phoneNum?.value.stringValue)!, withRegion: "US", ignoreType:true)
stringPhoneNumber = "+1\(phoneNumber.adjustedNationalNumber())"
print(stringPhoneNumber)
}
catch {
print("phone number parsing error")
}
let contactDisplayName = contact.givenName
print("displayName: \(contactDisplayName)" )
let contactEmail = contact.emailAddresses.first?.value ?? ""
print("email: \(contactEmail)")
self.contactsToSendInvitesTo.append(["email":contactEmail as String, "phone":stringPhoneNumber])
}
delegate?.contactsToUpload(self.contactsToSendInvitesTo)
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
print("cancel contact picker")
}
func contactPicker(_ picker: CNContactPickerViewController,didSelectContactProperties contactProperties: [CNContactProperty]) {
}
}
And in the CreateEventViewController I am calling the delegate when i click the invite users button and implementing the method of the protocol to just attempt to print the final structure displaying contacts emails and phone numbers to send invitations to:
func selectContactsPicker() {
let cnPicker = ContactPickerViewController()
cnPicker.delegate = ContactPickerViewController() as? ContactsToInviteDelegate
self.present(cnPicker, animated:true, completion:nil)
}
func contactsToInvite(_ contactsStructure: [[String : String]]) {
print(contactsStructure)
}
This code without refactoring to try to use the delegate pattern worked before. I had all these functions within one single view controller, but with all the logic required this file itself is extending beyond 400+ lines. My problem now is that after attempting to refactor using the delegate pattern, when i click the button to trigger selectContactsPicker all I see is a black screen. I don't know what I am doing wrong, but I have a feeling it is this function itself. I am not quite sure what the body of this function should be in order to delegate the responsibility to the correct controller, or how to display it properly. Examples I saw used storyboards and segues, such as this. I looked at other examples for using delegates but I think my problem is a bit too specific and I don't know how to ask in a more general sense. If I did, I would probably not have this problem to begin with, as then I would probably properly understand how to implement the delegate pattern.
A delegate does not have to be a view controller. This is a convenient pattern when a view controller manages elements requiring delegates - rather than instantiate separate objects just let the view controller implement the protocol.
There are a number of ways to manage unruly view controllers which grow too large.
One simple way is to use extensions. To add a delegate protocol to an existing view controller:
extension SomeViewController : CNContactPickerDelegate {
... implement contact picker delegate methods
}
This can nicely compartmentalise your source code making it easier to read.
If you want to use a separate class instance as the delegate, that can be done quite easily too.
Declare your delegate class, either in the same source file or another:
class MyPickerDelegate : NSObject, CNContactPickerDelegate {
... implement contact picker delegate methods
}
note the class must inherit from NSObject, but does not need to be a UIViewController.
In the code where you fire up the contact picker:
picker = CNContactPickerViewController()
self.pickerDelegate = MyPickerDelegate()
picker.delegate = self.pickerDelegate
self.present(picker, animated: true)
Note picker view controller only keeps a weak reference to the delegate, so you must make sure to keep a strong reference to the object somewhere. Here I am using a property pickerDelegate

I'm passing data from UITableViewController to UIViewController through protocols and instead of int value I'm getting nil. Why?

I have two UITableViewController, the first one:
protocol FetchUserProfileData {
func getNumberOfRequests()
}
class ListEvents: UITableViewController{
var fetchInfo:FetchUserProfileData?
func getNumberOfRequests() -> Int{
return 12
}
and the UIViewController:
class UserProfileDetails:UIViewController, FetchUserProfileData {
var listEvents: UserListEvents?
func getNumberOfRequests(){
}
override func viewDidLoad(){
listEvents?.fetchInfo = self
print(listEvents?.getNumberOfRequests())
and this line: print(listEvents?.getNumberOfRequests()) gives me a nil value instead of 12... What's wrong here?
---- edit
Ok, now I see that listEvents is empty... So my question is how can I pass that data from ListEvents to UserProfileDetails?
In this code, listEvents is probably nil.
But, the way you use the protocol looks odd to me. I would expect:
getNumberOfRequests in the protocol to return Int
ListEvents should be implementing the protocol, not UserProfileDetails
The empty getNumberOfRequests() in UserProfileDetails should be deleted
You did not set listEvents. When you are using story boards then you should set the fetchInfo not earlier than in (overwriting) prepareForSegue. Google for examples, the web is full of them. When you segue programmatically then you can set the property not before you actually instanticated the new view controller. You are better of using listEvents!.fetchInfo = self because in that case you'll get an exception when listEvents is nil.
I made some change your code and this will pass data from ListEvents to UserProfileDetails.
protocol FetchUserProfileDelegate {
func getNumberOfRequests()->Int
}
class ListEvents: UITableViewController,FetchUserProfileDelegate{
var userProfile: UserProfileDetails?
override func viewDidLoad() {
userProfile = UserProfileDetails()
userProfile?.delegate = self
}
// MARK: FetchUserProfileDelegate
func getNumberOfRequests() -> Int{
return 12 // return as your target Int
}
}
class UserProfileDetails:UIViewController {
var delegate:FetchUserProfileDelegate?
override func viewDidLoad() {
if let _ = delegate{
let resultInt = delegate?.getNumberOfRequests() // get the Int form ListEvents
print(resultInt)
}
}
}
The idea of moving data from one controller to another is very common. Most of the time this is done using a segue. A controller can have a function called prepareForSegue. This function gets called before the transition happens. Inside the prepareForSegue function, the system gives you destination controller object. You take that object and set your data in it. When the transition happens, and your destination controller comes up, it already has the data you want to give to it.
Use Xcode and make a new project. Choose "Master-Detail Application". This will generate the code for you and it is a good example of how to pass data between controllers.

Quick Look Generator iOS

Is there a way to extend the Quick Look Framework on iOS to handle an unknown file type like on Mac? I don't want to have to switch to my app to preview the file, much like viewing image files in email or iMessage. I would like to remove the step of having to select what app to use to open the file.
On Mac they call it a Quick Look Generator, but I can't find a way to do it on iOS
This is how you use Quick Look Framework in iOS
Xcode 8.3.2. Swift 3
First goto Build Phases and add new framework QuickLook.framework under Link Binary with Libraries.
Next import QuickLook in your ViewController Class
Next set delegate method of QuickLook to your ViewController class to access all the methods of QUickLook.framework (see below).
class ViewController: UIViewController , QLPreviewControllerDataSource {
}
Next create instance of QLPreviewController in your class as below:
let quickLookController = QLPreviewController()
Now set datasource in your viewdidload method:
override func viewDidLoad() {
super.viewDidLoad()
quickLookController.dataSource = self
}
Now create an fileURLs array to store all the documents path which you need to pass later to QLPreviewController via delegate methods.
var fileURLs = [URL]()
Now add below methods to your class to tell QLPreviewController about your total number of documents.
func numberOfPreviewItemsInPreviewController(controller: QLPreviewController) -> Int {
return fileURLs.count
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return fileURLs[index] as QLPreviewItem
}
#available(iOS 4.0, *)
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return fileURLs.count
}
Finally the method which shows your docs. You can also check if the type of document you want to Preview is possible to preview or not as below.
func showMyDocPreview(currIndex:Int) {
if QLPreviewController.canPreview(fileURLs[currIndex] as QLPreviewItem) {
quickLookController.currentPreviewItemIndex = currIndex
navigationController?.pushViewController(quickLookController, animated: true)
}
}
For now, if you want to show a preview of a file of a type not handled by the standard QLPreviewController, you have to write something yourself in your own app. You cannot write a custom Quick Look plugin like you can on the Mac.

Resources