在另一个视图中内联嵌入共享图标

Embedding Share Icons Inline Within Another View

我正在尝试在我的 iOS 应用程序中实现一个 "Share" 功能,其外观和感觉与 Google 照片 iOS 应用程序相似:

下面两行图标是我关心的。它们看起来与使用 UIActivityViewController 时显示的几乎相同。

在 Google 照片应用中,这些图标显示在屏幕的 "select photos" 部分(例如,您仍然可以与屏幕的上半部分进行交互)。但是,UIActivityViewController 的文档指出 "On iPhone and iPod touch, you must present [the view controller] modally."

这是对我来说真正重要的区别 -- 我希望 "share" 图标与我的其余内容内联显示,而不是在顶部显示模式我的内容。

是否可以使用 UIActivityViewController 来实现与上面屏幕截图类似的效果?如果没有,是否有推荐的方法可用于实现此类功能?

好吧,我考虑过这个问题,并且在网上做了一些深入的研究,但似乎从来没有人需要像您想要的那样修改它。所以这是我的猜测 Google 工程师是如何解决这个问题的:

  1. 他们对 UIActivityViewController 进行了逆向工程,并调用了一些私有 API 以显示相同的图标并重新排序控制器
  2. 他们使用 UIViewController 转换 API 并破解模态呈现的 UIActivityViewController 的视图层次结构,删除取消按钮和在其视图之上添加一些自定义视图

第二个选项的指标可能是所呈现的 "sheet" 的顶部为白色背景,而底部为灰色。

不幸的是,我不太适应转换 API,因为我正要学习它,但我的理解是您可以提供自定义对象作为转换委托。

当您 present/dismiss 或 push/pop 一个 UIViewController 时,此对象将被调用。它将获得对呈现视图控制器和呈现视图控制器的引用,您可以从它们的两个视图中获得乐趣。

这将使 "quiet" 易于删除和添加一些子视图、更改框架和颜色等,同时仍然具有所有默认行为。

我希望这个答案能帮助你实现你想要的。但是,请注意控制器的结构随时可能发生变化,因此请始终确保再次测试测试版,这样当苹果发布更新破坏您的 UI 时,您就不会感到意外。 :)

正如另一个答案中所讨论的那样,逆向工程 UIActivityViewController 是实现类似效果的唯一选择。我使用 iPhone 6s - 10.3 Simulator 进行了尝试。对于 iOS 9.x 或 11.x 或更高版本,以下结果可能不准确。

一个。找出 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)

第一眼引起我注意的是(按顺序)-

_activityViewController
@"UIViewController"

_contentController
@"_UIActivityViewControllerContentController"

_activityAlertController
@"UIAlertController"

进一步检查后,我发现 _contentController 是我们应该寻找的那个。我们需要在层次结构中更深入地查看一层,以便 UICollectionViewController 到达我们想要的位置。

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
        }
    }
}

我为什么要找 UICollectionViewController

Debug View Hierarchy 对此有答案。

我尝试将此作为 childViewController 添加到我的 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),
    ])

只有当您首先 LOADED/PRESENTED UIActivityViewController 时才会正确显示。

我能够使用静默 present/dismiss 调用实现此目的 -

self.present(activityVC, animated: false, completion: {
    self.dismiss(animated: false, completion: nil)
})

这个应用商店安全吗? - 很可能不安全。

一旦您开始从 UIKit 的标准组件中窃取 view(s)viewController(s),行为就会不稳定,并且肯定会随着即将到来的更新而中断。

Google Photos 所具有的是更高级逆向工程的结果。 在上面的实现中,您看不到 更多 选项屏幕。 UIActivityViewController 预期的层次结构已损坏。

希望这对您有所帮助。