在我的 flutter 应用程序中,我希望每当用户截屏时,在某些特定页面上,我的应用程序内容不应该是可见的。为了解决这个问题,我使用了一些本机 kotlin 代码在 android 中实现这一点,但对于 ios,我面临一些问题。
我创建了方法通道来实现此功能,并为此编写了一些本机 swift 代码。
这是我的 AppDelegate 文件:
import UIKit
import Flutter
import flutter_downloader
import GoogleMaps
import FirebaseCore
import FirebaseMessaging
@main
@objc class AppDelegate: FlutterAppDelegate {
var blackOverlayView: UIView?
var methodChannel: FlutterMethodChannel?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
guard let controller = window?.rootViewController as? FlutterViewController else {
print("Error: RootViewController is not FlutterViewController")
return false
}
methodChannel = FlutterMethodChannel(name: "my_channel_name",
binaryMessenger: controller.binaryMessenger)
methodChannel?.setMethodCallHandler { [weak self] (call, result) in
guard let self = self else {
result(FlutterError(code: "ERROR", message: "Self is nil", details: nil))
return
}
switch call.method {
case "preventScreenshots":
print("Inside my prevent screenshot")
DispatchQueue.main.async {
print("Inside 1")
guard let controller = self.window?.rootViewController as? FlutterViewController else {
print("Inside else")
result(FlutterError(code: "ERROR", message: "Root view controller not found", details: nil))
return
}
print("Inside 2")
let field = UITextField()
field.isSecureTextEntry = true
print("Inside 3")
field.translatesAutoresizingMaskIntoConstraints = false
controller.view.addSubview(field)
print("Inside 4")
NSLayoutConstraint.activate([
field.centerYAnchor.constraint(equalTo: controller.view.centerYAnchor),
field.centerXAnchor.constraint(equalTo: controller.view.centerXAnchor)
])
print("Inside 5")
}
result(nil)
case "allowScreenshots":
DispatchQueue.main.async {
controller.view.subviews.forEach { view in
if view is UITextField {
view.removeFromSuperview()
}
}
}
result(nil)
case "showBlackOverlay":
self.showBlackOverlay()
result(nil)
case "hideBlackOverlay":
self.hideBlackOverlay()
result(nil)
case "addScreenshotListener":
self.addScreenshotObserver()
result(nil)
case "removeScreenshotListener":
self.removeScreenshotObserver()
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
FirebaseApp.configure()
GMSServices.provideAPIKey("my_api_key")
GeneratedPluginRegistrant.register(with: self)
FlutterDownloaderPlugin.setPluginRegistrantCallback(registerPlugins)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
Messaging.messaging().delegate = self
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
@objc private func handleScreenshotNotification() {
DispatchQueue.main.async {
guard let controller = self.window?.rootViewController as? FlutterViewController else { return }
print("Listened to screenshot, now calling method")
self.methodChannel?.invokeMethod("preventScreenshots", arguments: nil)
}
}
private func addScreenshotObserver() {
NotificationCenter.default.addObserver(
self,
selector: #selector(self.handleScreenshotNotification),
name: UIApplication.userDidTakeScreenshotNotification,
object: nil
)
print("Screenshot listener added")
}
private func removeScreenshotObserver() {
NotificationCenter.default.removeObserver(
self,
name: UIApplication.userDidTakeScreenshotNotification,
object: nil
)
print("Screenshot listener removed")
}
private func showBlackOverlay() {
guard let window = self.window else { return }
if blackOverlayView == nil {
blackOverlayView = UIView(frame: window.bounds)
blackOverlayView?.backgroundColor = UIColor.black
blackOverlayView?.isUserInteractionEnabled = false
window.addSubview(blackOverlayView!)
}
blackOverlayView?.isHidden = false
}
private func hideBlackOverlay() {
blackOverlayView?.isHidden = true
}
}
extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
print("Firebase registration token: \(String(describing: fcmToken))")
let dataDict: [String: String] = ["token": fcmToken ?? ""]
NotificationCenter.default.post(
name: Notification.Name("FCMToken"),
object: nil,
userInfo: dataDict
)
}
}
private func registerPlugins(registry: FlutterPluginRegistry) {
if (!registry.hasPlugin("FlutterDownloaderPlugin")) {
FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!)
}
}
这是我如何调用我的方法通道:
class AppMethodChannels {
static const _channel = MethodChannel("eduvate_admin/channel");
///Currently implemented for android only
///This method prevents user from taking screenshots
static void preventScreenshots() {
if (Platform.isAndroid || Platform.isIOS) {
_channel.invokeMethod('preventScreenshots');
}
}
///Currently implemented for android only
///This method allows user to take screenshots
static void allowScreenshots() {
if (Platform.isAndroid || Platform.isIOS) {
_channel.invokeMethod('allowScreenshots');
}
}
///Shows a black overlay on the screen.
///Currently implemented for iOS only
static void showBlackOverlay() {
if (Platform.isIOS) {
_channel.invokeMethod('showBlackOverlay');
}
}
///Hides the black overlay from the screen.
///Currently implemented for iOS only
static void hideBlackOverlay() {
if (Platform.isIOS) {
_channel.invokeMethod('hideBlackOverlay');
}
}
static void setScreenshotListener() {
if (Platform.isIOS) {
print("Setting ss listener");
_channel.invokeMethod("addScreenshotListener");
}
}
}
我正在页面的初始状态下调用
setScreenshotListener
。
现在,我不知道 swift,但据我所知,当我从我的方法通道调用
addScreenshotListener
时,
NotificationCenter.default.addObserver(
self,
selector: #selector(self.handleScreenshotNotification),
name: UIApplication.userDidTakeScreenshotNotification,
object: nil
)
这部分代码将运行,它是某种监听器,它将触发该函数,每当通知到来时,该函数就会传递到选择器中。
现在,在
handleScreenshotNotification
方法中,我调用了
self.methodChannel?.invokeMethod("preventScreenshots", arguments: nil)
应该触发我上面声明的 preventScreenshots
方法。
现在,这个打印语句正在打印,
print("Listened to screenshot, now calling method")
但是我的preventScreenshots
方法中的所有打印语句都没有打印,即我的方法没有被调用。
因此,我尝试在监听器中编写相同的代码,而不是调用函数,而是打印所有语句,但我无法实现所需的输出。
有人可以帮我解决这个问题吗?比如,为什么我的方法没有被调用以及为什么空白/空屏幕的代码不起作用?
我找到了使用这个包的解决方案https://pub.dev/packages/screen_protector
这对于 ios 和 android 来说工作得很好,因为它会显示一个空白的屏幕截图视图。我认为我们可以更改它的代码并显示我们的自定义小部件/图像。