Pra*_*ogg 39 ios ios-keyboard-extension
谷歌的自定义iOS应用程序Gboard具有一项有趣的功能,无法使用iOS SDK中的公共API实现(从iOS 10开始). 我想确切地知道Google如何完成以编程方式弹出Gboard中App Switching堆栈中的一个应用程序的任务.
自定义iOS键盘有两个主要组件:容器应用程序和键盘应用程序扩展.键盘应用程序扩展程序在单独的操作系统进程中运行,只要用户在手机上需要文本输入的任何应用程序中,该进程就会启动.
这些是使用Gboard可以遵循的近似步骤,以查看以编程方式返回到以前的应用程序的效果:
我假设Google使用私有API,通过使用Objective-C运行时内省探索状态栏的视图层次结构,并以某种方式合成tap事件或调用公开的目标/操作.我已经很好地探索了这个,并且能够在状态栏中找到有趣的UIView子类,比如UIStatusBarBreadcrumbItemView,它包含一个UISystemNavigationAction数组.我正在继续探索这些类,希望我能找到一些复制用户交互的方法.
我知道使用私有API是一种很好的方式来让您的应用程序提交从App Store中被拒绝 - 这不是我想在答案中解决的问题.我主要想找到关于Google如何完成以编程方式弹出Gboard中应用程序切换堆栈中的一个应用程序的具体任务的具体答案.
ken*_*ytm 36
...虽然不是通过探索视图层次结构或事件注入.
当语音到文本操作完成后,我们可以从Xcode或Console中检查它调用该-[AVAudioSession setActive:withOptions:error:]方法的syslog .所以我对Gboard应用程序进行了逆向工程,并寻找与此相关的堆栈跟踪.
爬上调用堆栈,我们可以找到-[GKBVoiceRecognitionViewController navigateBackToPreviousApp]方法,并...
...... _systemNavigationAction?是的,绝对私密的API.
由于class_getInstanceVariable是公共API并且"_systemNavigationAction"是字符串文字,因此自动检查程序无法记录私有API使用情况,并且人工审阅者可能看不到"跳回到以前的应用程序"行为有任何问题.或者可能是因为他们是谷歌而你不是......
执行"跳回以前的应用程序"操作的实际代码如下所示:
@import UIKit;
@import ObjectiveC.runtime;
@interface UISystemNavigationAction : NSObject
@property(nonatomic, readonly, nonnull) NSArray<NSNumber*>* destinations;
-(BOOL)sendResponseForDestination:(NSUInteger)destination;
@end
inline BOOL jumpBackToPreviousApp() {
Ivar sysNavIvar = class_getInstanceVariable(UIApplication.class, "_systemNavigationAction");
UIApplication* app = UIApplication.sharedApplication;
UISystemNavigationAction* action = object_getIvar(app, sysNavIvar);
if (!action) {
return NO;
}
NSUInteger destination = action.destinations.firstObject.unsignedIntegerValue;
return [action sendResponseForDestination:destination];
}
Run Code Online (Sandbox Code Playgroud)
特别地,该-sendResponseForDestination:方法执行实际的"返回"动作.
(由于API没有记录,Gboard实际上使用的API 不正确.他们使用了错误的签名-(void)sendResponseForDestination:(id)destination.但实际上所有数字1都是相同的,所以Google开发人员这次很幸运)
更新:此 hack 在 iOS 17 上停止工作,Gboard 也无法再执行此操作
@kennytm 答案的Swift 版本:
@objc private protocol PrivateSelectors: NSObjectProtocol {
var destinations: [NSNumber] { get }
func sendResponseForDestination(_ destination: NSNumber)
}
func jumpBackToPreviousApp() -> Bool {
guard
let sysNavIvar = class_getInstanceVariable(UIApplication.self, "_systemNavigationAction"),
let action = object_getIvar(UIApplication.shared, sysNavIvar) as? NSObject,
let destinations = action.perform(#selector(getter: PrivateSelectors.destinations)).takeUnretainedValue() as? [NSNumber],
let firstDestination = destinations.first
else {
return false
}
action.perform(#selector(PrivateSelectors.sendResponseForDestination), with: firstDestination)
return true
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2031 次 |
| 最近记录: |