iOS如何检测屏幕截图?

156

这个在应用商店上的Snapchat应用程序可以让你分享带有自毁功能的图片。你只能在X秒内查看这些照片。如果你试图使用home-power键组合键在显示图片时截屏,它会告诉发送者你尝试截图了。

SDK的哪个部分让你检测用户正在截屏?我不知道这是可能的。


1
看起来早期截屏会先调用 -applicationDidEnterBackground:。现在不确定。 - iDev
伙计们。另一个线程有答案:https://dev59.com/CHI95IYBdhLWcg3w2h56#2122117 - me2
1
请查看这个链接:https://dev59.com/KF7Va4cB1Zd3GeqPNty6#8711894,它说这已经不再可能了。也许你可以尝试一下并告诉我们结果。 - iDev
在互联网上还没有看到有人提到过这一点,但我认为如果您使用Xcode来截取屏幕截图(从组织器窗口中的设备),那么应用程序绝对无法知道。它必须监视相机胶卷以查看接收到的Snapchat照片时添加的任何照片,并通过Xcode绕过这个问题(无需越狱)。 - smileyborg
跟进:测试了这个理论并确认该应用程序无法检测到Xcode截图。然而,我意识到有趣的是,在iOS 6上,应用程序必须明确授予访问照片的权限...但是这个应用程序仍然可以在不允许访问照片的情况下检测到截屏!它一定是使用另一种检测方法——我注意到当使用Home+Sleep按钮方法时,活动照片也会从屏幕上移除。因此,该应用程序可以可靠地监视与此截屏过程相关的某些模式,也许是通过GestureRecognizer? - smileyborg
7个回答

370

iOS 7及以后版本已不再调用其他答案中所述的touchesCancelled: withEvent:,当用户截屏时。这将导致Snapchat无法正常运行,因此添加了一些新的解决方案。现在,解决方案很简单,只需使用NSNotificationCenter将观察者添加到UIApplicationUserDidTakeScreenshotNotification即可。

以下是示例:

Objective C

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationUserDidTakeScreenshotNotification
                                                  object:nil
                                                   queue:mainQueue
                                              usingBlock:^(NSNotification *note) {
                                                 // executes after screenshot
                                              }];

Swift

NotificationCenter.default.addObserver(
    forName: UIApplication.userDidTakeScreenshotNotification,
    object: nil,
    queue: .main) { notification in
        //executes after screenshot
}

3
使用这个方法和 touchesCancelled:withEvent: 可以让你在所有(较新的)iOS版本中检测到截屏。 - Joshua Gross
63
这并不能防止截屏。它只能让应用程序知道已经被截屏了。根据UIApplication类的参考文档,当用户按下Home和Lock按钮进行截屏时,会发出UIApplicationUserDidTakeScreenshotNotification通知。这个通知不包含userInfo字典,而且是在截屏完成后才被发送的。 - badweasel
7
@badweasel 正确。考虑到这个通知遵循常规的Cocoa命名惯例,单词“Did”意味着它是在事情发生后发布的。在这种情况下,没有“Will”的等效词,并且据我所知,没有办法使用公共API防止用户截屏。 - Mick MacCallum
1
请注意,我给了你一个+1。 我最初误读了OP的问题,并认为问题是如何检测以防止某些事情-因为那就是我正在寻找的答案。 因此,我只是在评论中添加了澄清,因为我希望很多人来到这个问题都在寻找那个答案。 我也从“did”一词中假定了这一点,但文档使其更加清晰。 在我的应用程序中,我允许人们编辑照片,但其中一些工具需要IAP。 但我让他们在购买之前尝试。 所以我想在捕获之前检测以添加水印。 无法完成。 - badweasel
1
@MickMacCallum 你为什么要在主队列上执行它? - kidsid49
显示剩余8条评论

26

18
iOS 7之后不再适用。请参考以下iOS7+的解决方案。 - Joe Masilotti
6
乔说的是正确的。提问者应该取消选中这个作为正确答案。 - God of Biscuits
可以使用 UIApplication​User​Did​Take​Screenshot​Notification 来检测 iOS 7+ 中的截屏操作。 - Amit Tandel

16

以下是使用Swift中的闭包完成的方法:

func detectScreenShot(action: () -> ()) {
    let mainQueue = NSOperationQueue.mainQueue()
    NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationUserDidTakeScreenshotNotification, object: nil, queue: mainQueue) { notification in
        // executes after screenshot
        action()
    }
}

detectScreenShot { () -> () in
    print("User took a screen shot")
}

Swift 4.2

->

Swift 4.2

func detectScreenShot(action: @escaping () -> ()) {
    let mainQueue = OperationQueue.main
    NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: mainQueue) { notification in
        // executes after screenshot
        action()
    }
}

这已经作为标准函数包含在:

https://github.com/goktugyil/EZSwiftExtensions

免责声明:这是我的存储库


嘿,我尝试了这个代码,它很好用,但是你能解释一下代码里面发生了什么吗?我刚学Swift,有点难读懂。 - aecend
这是那种“能用就别动”的代码之一。你不需要学习它的功能,因为在这里使用的框架非常罕见。 - Esqarrouth
但是如果你不知道闭包是如何工作的,你应该查看一下。基本上,当你调用detect screen shot函数时,无论你放在括号里的内容都会被发送为一个动作函数。 - Esqarrouth
复制粘贴的原因 - Esqarrouth
在didDisappear中移除观察者无效,有什么解决方法吗?我收到了多个通知。 - Yaroslav Dukal
显示剩余3条评论

8

Swift 4+

NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: OperationQueue.main) { notification in
           //you can do anything you want here. 
        }

通过使用这个观察器,您可以找出用户何时截屏,但不能阻止他。


2

最新的SWIFT 3

func detectScreenShot(action: @escaping () -> ()) {
        let mainQueue = OperationQueue.main
        NotificationCenter.default.addObserver(forName: .UIApplicationUserDidTakeScreenshot, object: nil, queue: mainQueue) { notification in
            // executes after screenshot
            action()
        }
    }

viewDidLoad 中调用这个函数。
detectScreenShot { () -> () in
 print("User took a screen shot")
}

然而,
NotificationCenter.default.addObserver(self, selector: #selector(test), name: .UIApplicationUserDidTakeScreenshot, object: nil)

    func test() {
    //do stuff here
    }

正常工作。我看不出使用mainQueue的任何好处...


2
问题是要求在截屏之前得到通知。这告诉你已经在截屏之后了。 - rmaddy
1
@rmaddy,你在哪里看到这个问题是在问如何在之前得到通知的?我只是改进了上面我的答案,不确定你的评论意图.. - Yaroslav Dukal
问题是:“检测用户正在截屏”。如果 OP 想要事后知道,问题应该写成:“检测用户已经截屏”。 - rmaddy

1

看起来没有直接的方法来检测用户是否按下了Home + 电源按钮。根据this所述,早期可以使用达尔文通知实现,但现在不再起作用。由于Snapchat已经这样做了,我的猜测是他们正在检查iPhone相册,以检测是否在这10秒钟内添加了新图片,并以某种方式与当前显示的图像进行比较。也许进行了一些图像处理来进行比较。只是一个想法,也许你可以尝试扩展它以使其工作。请查看此处以获取更多详细信息

编辑:

看起来他们可能正在检测UITouch取消事件(屏幕截图取消触摸),并根据此博客向用户显示此错误消息:iOS上如何检测屏幕截图(如SnapChat)

在这种情况下,您可以使用 - touchesCancelled:withEvent:方法来感知UITouch取消以检测此操作。您可以在此委托方法中删除图像并向用户显示适当的警报。
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];

    NSLog(@"Touches cancelled");

    [self.imageView removeFromSuperView]; //and show an alert to the user
}

你似乎在正确的地方有很好的联系,可以得到关于这个问题的明确答案 ;) - smileyborg
这更像是一种有根据的猜测,而不是一个明确的答案。不幸的是,我没有任何联系来得到一个确切的答案。如果他们没有使用任何私有API,那么我能想到的唯一方法就是基于某些算法检测图像添加到相册并将该图像与屏幕上的当前图像进行比较。 - iDev
但是,考虑到他们可以在不请求访问设备照片和相机胶卷的情况下完成这项操作...那一定是其他原因吧?我的理论是与他们让您长按接收到的照片消息以查看它有关,当您按下“主页+锁定”按钮时,操作系统立即表现得好像没有手指触摸屏幕。也许这种情况发生时没有touchesEnded:withEvent(或类似的回调),因此他们可以监视这种独特的事件模式?我可能完全错了,但这是我目前唯一的理论。 - smileyborg
把手指放在屏幕上,不要抬起来,试着按下另外两个按钮。我猜还是显示那个信息。所以他们可能正在使用一些私有API,并设法将其放入应用商店。 - iDev
2
自iOS 7起不再支持。 - God of Biscuits

0

Swift 4 示例

示例 #1 使用闭包

NotificationCenter.default.addObserver(forName: .UIApplicationUserDidTakeScreenshot, 
                                       object: nil, 
                                       queue: OperationQueue.main) { notification in
    print("\(notification) that a screenshot was taken!")
}

带选择器的示例#2

NotificationCenter.default.addObserver(self, 
                                       selector: #selector(screenshotTaken), 
                                       name: .UIApplicationUserDidTakeScreenshot, 
                                       object: nil)

@objc func screenshotTaken() {
    print("Screenshot taken!")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接