How to Customize QuickLook(QLPreviewControlller) navigation bar? - ios

I am trying to remove all the right-side BarButtonItem in QuickLook (QLPreviewController) in iOS 15 and Xcode 13.2 and later.
My requirement is to only show search button in right-side of navigation bar. I tried many types of solutions but I'm not able to meet this requirement. I also read QuickLook Document provided by Apple but I'm not having success. For more understanding show this Apple document: Apple QuickLook Document Link.

If you want to remove the ability to edit a document while using QLPreviewController, you need to implement the previewController(_:editingModeFor:) delegate method.
Make sure you set the delegate property of your QLPreviewController (along with the dataSource).
Then implement:
func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
return .disabled
}
This will disable editing and remove the edit icon next to the search icon.

Related

How to dismiss context menu (UIContextMenu) programmatically?

I have an iOS app. Some UIViewControllers have automatically updated content, it means that app can get update (simple JSON) any time (but no more often than about 1 time in half an hour). View controllers have UICollectionView with UIContextMenu for iOS >= 13.0.
Some times arises a situation when the user has called the context menu, but the content of cell and its menu are no longer relevant. UICollectionView already reloaded (with resorting cells), but context menu still shown.
I know when the update happens in my app and I want to dismiss open menu in this moment.
So my question is - how to dismiss context menu programmatically?
Thanks for any help!
Hello Please try this solution.
func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
animator.preferredCommitStyle = .dismiss
}

UIDocumentPickerViewController buttons look disabled but work -- why?

I am using UIDocumentPickerViewController to export images to the Files app in iOS 11. The picker comes up, and I am able to pick a destination for the files. I get the callback that the files were moved and I can see them in Files, so that appears to be working fine. However, the UI is wrong -- the Add and Cancel buttons look disabled, but they actually work fine. The buttons don't look this way if I just use Save to Files in the Share Sheet.
This is my code:
UIDocumentPickerViewController *docPicker =
[[UIDocumentPickerViewController alloc] initWithURLs:self.assetURLs inMode:UIDocumentPickerModeExportToService];
docPicker.delegate = self;
[vc presentViewController:docPicker animated:YES completion:nil];
Things I have tried:
1) I have tried both Move and Export types
2) I have enabled the iCloud capability (all three items: key-value, Document, and CloudKit) - no difference in the experience [in fact, the code worked even without these set, though the docs say otherwise])
Here is an screenshot of what the UI looks like. As you can see, the UI says it will add the item to the chosen directory (and it, in fact, does if I tap on Add):
Anyone know how to get the buttons to appear blue and look enabled? I have not found any sample code online that I could try -- if someone has a pointer, I can try that to see if it's something about my configuration or code.
I'm not a big fan of temporarily changing the global appearance like suggested in the approved answer. You can just reset the appearance for UIDocumentPickerViewController and the bar buttons will return to their original blue.
if #available(iOS 11.0, *) {
UINavigationBar.appearance(whenContainedInInstancesOf: [UIDocumentBrowserViewController.self]).tintColor = nil
}
Based on your comment, your app is setting the tintColor using UINavigationBar.appearance. This will affect all navigation bars, including those of system navigation controller such as UIDocumentPickerViewController.
I've dealt with this in one of my own apps. One solution is to subclass UIDocumentPickerViewController and to use your subclass anywhere you need the picker view. In your subclass, override viewDidLoad and set UINavigationBar.appearance.tintColor back to nil. And also override viewWillDisappear to reset UINavigationBar.appearance.tintColor back to the desired color.

Disabling Dock in iOS programmatically

Is it possible to disable the dock that pops up in iOS?
This is my View Controller. Notice that it has a draggable view controller in the footer.
But when I try to pull it up quickly, the dock shows up:
Is there any way to disable it?
I think the closest you can get is iOS 11's preferredScreenEdgesDeferringSystemGestures(), which will show an indicator at the bottom but not pull up the dock on the first swipe. For example, in your view controller:
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
return [.bottom]
}
In my experience it still eats the swipe gesture, but it still gives the user a second chance to hit the right target.
On iOS <11 however, this behavior can only be obtained by hiding the status bar.
Edit:
Usually when faced with implementing a design choice like this, I try to offer a second, non-interfering gesture as a backup, such as a tap in that area, that has the same effect.
As in iOS 11, you cannot disable the dock in an application, nor in Settings. I'd suggest providing a larger area for swiping up from the bottom.
Normally such conflicts should be avoided, as they degrade user experience: how do you know that the user does not actually want to use the dock?
But if you really want, you can override the preferredScreenEdgesDeferringSystemGestures() method in the root controller to specify which edges should NOT (immediately) trigger system gestures.
e.g.
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
return .bottom
}

Animating search bar

I want to do something similar to the search bar in the IOS 10 Music app.
How might I do this? Here are some pictures:
Do you want to implement Search bar with scope? Please elaborate.
Sample :
[self.searchDisplayController.searchBar setScopeButtonTitles:#["Apple Music",#"Your Library"]];
Check below properties in File Inspector:
Capabilities - Show Cancel Button
Capabilities - Show Scope Bar

Embedding Share Icons Inline Within Another View

I am attempting to implement a "Share" feature in my iOS app with a similar look and feel to that of the Google Photos iOS app:
The bottom two rows of icons are what I care about. They look nearly identical to those displayed when using the UIActivityViewController.
In the Google Photos app, these icons appear inline with the "select photos" portion of the screen (e.g. you can still interact with the upper portion of the screen). However, the documentation for the UIActivityViewController states that "On iPhone and iPod touch, you must present [the view controller] modally."
This is the difference that is really important to me -- I'd like for the "share" icons to display inline with the rest of my content, rather than having a modal that is displayed on top of my content.
Is it possible to use the UIActivityViewController to achieve a similar effect shown in the screenshot above? If not, is there a recommended approach that I might use to implement this sort of functionality?
As discussed in another answer, reverse engineering UIActivityViewController is the only option in order to be able to achieve a similar effect. I tried this using iPhone 6s - 10.3 Simulator. The following findings may not be accurate for iOS 9.x or 11.x or above.
A. Find out all internal variables for UIActivityViewController
var variablesCount: UInt32 = 0
let variables = class_copyIvarList(UIActivityViewController.self, &variablesCount)
for i in 0..<variablesCount {
if let variable = variables?[Int(i)] {
let name = String(cString: ivar_getName(variable))
let typeEncoding = String(cString: ivar_getTypeEncoding(variable))
print("\(name)\n\(typeEncoding)\n\n")
}
}
free(variables)
The ones those got my attention at first sight are (in order) -
_activityViewController
#"UIViewController"
_contentController
#"_UIActivityViewControllerContentController"
_activityAlertController
#"UIAlertController"
On inspecting them further, I found out that _contentController is the one we should be looking for. We need to look one level deeper in hierarchy for UICollectionViewController to get to where we want to be.
if let activityContentController = activityVC.value(forKeyPath: "_contentController") as? UIViewController {
print("Found _contentController!")
for child in activityContentController.childViewControllers {
print(String(describing: child))
if child is UICollectionViewController {
print("Found UICollectionViewController!")
break
}
}
}
Why did I look for UICollectionViewController?
Debug View Hierarchy has the answer for this.
I tried adding this as a childViewController to my UIViewController -
self.addChildViewController(child)
child.didMove(toParentViewController: self)
self.view.addSubview(child.view)
child.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
child.view.topAnchor.constraint(equalTo: self.view.topAnchor),
child.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
child.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
child.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
IT SHOWS UP CORRECTLY ONLY IF YOU HAVE LOADED/PRESENTED UIActivityViewController FIRST.
I was able to achieve this using a silent present/dismiss call -
self.present(activityVC, animated: false, completion: {
self.dismiss(animated: false, completion: nil)
})
IS THIS APP STORE SAFE? - Most likely not.
As soon as you start stealing the view(s) or viewController(s) from UIKit's standard components, the behavior is not stable and it will break with upcoming updates for sure.
What Google Photos has is the result of way more advanced reverse engineering. In above implementation, you can't see More option screen. The hierarchy UIActivityViewController expects is broken.
Hope this helps.
Okay, I thought about this and I did some intensive research on the web but nobody ever seemed to needed to modify it like you want. So here are my guesses how Google engineers solved this:
They reverse engineered the UIActivityViewController and call some private APIs to get the same icons to show up and the reordering controllers
They use the UIViewController transitioning API and hack the view hierarchy of a modally presented UIActivityViewController, removing the cancel button and adding some custom views on top of its view
An indicator for the second option could be that the top of the presented "sheet" has a white background while the bottom has the greyish color.
Unfortunately I'm not very fit with the transitioning API as I'm just about to learn it but my understanding is that you can provide a custom object as the transitioning delegate.
This object then gets called when you present/dismiss or push/pop a UIViewController. It will get a reference to both, the presenting and the presented view controller and you can have fun with both of their views.
This should make it "quiet" easy to remove and add some subviews, change frames and colors etc while still having all the default behavior.
I hope this answer helps you to achieve what you want. However, be aware that the structure of the controller could change at any time so always make sure to also test agains betas so that you don't get caught by surprise when apple releases an update which breaks your UI. :)

Resources