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?
2 Answers
Answers 1
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. :)
Answers 2
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.
0 comments:
Post a Comment