问题
假设您想反复拍摄某人的秘密照片,而不会被抓住。最好的iPhone应用程序是什么?内置的相机应用程序足够吗?
有可能要考虑的事情。首先,如果有人看着您的肩膀并注意到您的屏幕正在显示相机供稿,您可能会被抓住。因此,您需要确保在拍摄时没有人在您身后。此外,为了拍照所需的水龙头手势与其他常见的手势(滚动,短信)可以区分。一个人可以猜测您只是从看手和肢体语言来拍照。
要考虑的另一件事是使用绿色摄像头指示器。当iOS应用程序即将拍照时,它必须首先显示与此类似的提示:
用户确认,应用程序可以在任何给定的时刻拍摄照片,而无需提供任何可见的迹象。由于在道德上秘密拍摄用户的照片是错误的(我们不希望Gmail或Instagram拍摄我们的秘密照片),因此自iOS 14:
以来添加了绿色摄像头指示器一旦相机活跃,绿色指示器就会变为绿色,即使照片保存或在任何地方传输。因此,即使我们有一个只在屏幕上显示任何特殊内容的程序,绿色指标也会披露相机处于活动状态的事实。
最后,iOS具有特殊的声音效果,让所有人都知道拍摄照片:
没有这种声音的照片拍照的唯一方法是在拍摄时完全静音。
解决方案
我一直在研究这一点,并最终创建了一个解决上述所有问题的小程序,并使用户能够更加自信地拍摄秘密照片。
- 该程序显示黑屏(没有相机提要)。
- 该程序反复使用一秒钟间隔拍照(不需要用户手势)。
- 由于该程序控制屏幕的亮度并将其降低到最小可能的值,因此即使是绿色的,使用摄像机的指示器几乎是看不见的,而且总体上,屏幕似乎已关闭。
- 无需静音设备:即使设备设置为高音量,相机射击器声音也会在编程中被静音。
- 为了快速逃脱,触摸屏幕将立即导致该程序退回iPhone的主屏幕。
重要的是要注意,我不鼓励任何人实际使用此程序。毕竟,它是邪恶。该项目的目的只是证明创建这样的程序是多么容易。此外,该代码仅使用众所周知的系统API,而无需访问任何私人系统方法,因此从理论上讲,它可以通过AppStore评论(如果在具有其他有价值内容的较大应用程序中嵌入为秘密模块)。
该程序的代码在以下地址上在GitHub上公开可用:
https://github.com/arixegal/BlackEye/tree/MediumTutorial
但是,在本文中,我将演示如何逐步创建此程序。需要对快速编程语言的基本知识。
一般设置
我们需要创建一个Xcode项目和一个空白的单页应用程序。
- 打开现代版本的Xcode。
- 选择创建一个新的Xcode项目。
- 在“可用模板”窗口中,选择ios->应用模板
输入该项目,团队和组织标识符的名称。语言应为 swift ,界面应为故事板。
这将创建一个显示空白屏幕的应用程序。接近最终游戏的一步。为了测试它,键入命令+r或选择product-> run。
如何显示黑屏
由于我们的应用程序接口是基于故事板的,并且我们已经拥有管理主屏幕显示的视图控制器,以显示黑屏,我们要做的就是更改此背景屏幕到黑色。这可以直接从接口构建器完成。
- 在项目导航器中,选择文件main.storyboard
- 在故事板的文档概述中,选择“视图元素”(在视图控制器场景下 - >视图控制器)
- 在视图的属性检查员中,将背景更改为黑色。
这将创建一个显示黑屏的应用程序。为了测试它,键入命令+R或选择“ product-> run”。重要的是选择颜色黑色,而不是其他似乎是黑色的颜色,例如标签颜色。选择标签颜色而不是黑色作为背景颜色,将导致黑色或白色屏幕,具体取决于iPhone的深色模式设置。无论iPhone的深色或轻度模式如何,选择黑屏总是会导致黑屏。
如何隐藏状态栏
如果我们现在运行该应用程序,我们可能会看到一个完全黑屏幕。但是,如果我们运行的模拟器或设备将应用程序设置为“暗模式”,则屏幕将不完全黑。黑暗模式是通过在设置 - >显示与亮度 - >外观下选择“黑暗”来实现的。
现在,当我们现在运行应用程序时,设备处于黑暗模式时,顶部状态栏将显示各种元素,例如时间,电源和WiFi指示器。在光模式下,状态栏元素都是黑色的,所以我们无法看到它们,但是现在由于我们处于黑暗模式,它们是轻巧的。
我们希望屏幕像关闭一样出现。这意味着我们必须隐藏状态栏。这可以通过在目标属性中添加两个新的键值对来实现。
- 在项目导航器中,选择最高的元素,这将是项目本身。
- 在选择该项目的情况下,在目标下下,选择主要目标并导航到 info tab。
- 单击 plus 按钮,在将鼠标悬停在列表项目上时,它变得可见,并添加以下键: uiviewControllerBaseedStatusBarappEarance
- 将新添加的键的值设置为 no
- 添加另一个键: uistatusbarhidden
- 将上述新添加的键设置为是YES
您会注意到,一旦添加并显示了键,名称 uistatusbarhiddend 将显示为 - 状态栏最初是隐藏的,而名称< strong> uiviewControllerBaseadStatusBarappeArance 显示为基于控制器的状态栏外观。没关系。
现在运行该应用程序时,您会发现屏幕是完全黑色的,并且所有状态栏元素都缺少,而不论iPhone的深色或光模式如何。这正是我们想要的。
如何响应点击事件
点击屏幕时,我们希望该应用完全退出。为此,我们需要收听水龙头。有很多方法。在此项目中,我们将以编程为黑色按钮。
- 在项目导航器中,选择 viewConroller.swift
- 在“类声明”下,添加以下代码:
private lazy var curtainView: UIView = {
let size = UIScreen.main.bounds.size
let btn = UIButton(
frame: CGRect(
x: 0,
y: 0,
width: size.width,
height: size.height
)
)
btn.backgroundColor = UIColor.black
btn.addTarget(self, action: #selector(quit), for: .touchDown)
return btn
}()
- 添加退出方法
/// Will quit the application with animation
@objc private func quit() {
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
/// Sleep for a while to let the app goes in background
sleep(2)
exit(0)
}
- 将按钮添加到视图
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(curtainView)
}
上述退出方法是从以下讨论中的答案之一复制的:
https://stackoverflow.com/questions/355168/proper-way-to-exit-iphone-application
尝试运行应用程序并测试屏幕时的行为。如果一切都正确完成,则在屏幕上进行点击会导致应用退出。
如何拍照
有许多在线教程和如何通过编程操作iPhone摄像机的示例。他们中的大多数人都有太多的代码用于我们的目的,因为操作相机的常规方式涉及在屏幕上显示相机馈电,在我们的情况下,这是不需要的。
- 在 viewController.swift 中,在文件顶部添加行 import avoundation 。
- 在“班级声明”下添加以下两个实例:
private let photoOutput = AVCapturePhotoOutput()
private let session = AVCaptureSession()
添加以下2种方法:
private func setupCaptureSession() -> AVCaptureSession? {
session.sessionPreset = .photo
guard let cameraDevice = AVCaptureDevice.default(for: .video) else {
print("Unable to fetch default camera")
return nil
}
guard let videoInput = try? AVCaptureDeviceInput(device: cameraDevice) else {
print("Unable to establish video input")
return nil
}
session.beginConfiguration()
session.sessionPreset = AVCaptureSession.Preset.photo
guard session.canAddInput(videoInput) else {
print("Unable to add videoInput to captureSession")
return nil
}
session.addInput(videoInput)
guard session.canAddOutput(photoOutput) else {
print("Unable to add videoOutput to captureSession")
return nil
}
session.addOutput(photoOutput)
photoOutput.isHighResolutionCaptureEnabled = true
session.commitConfiguration()
DispatchQueue.global(qos: .background).async { [weak self] in
self?.session.startRunning()
}
return session
}
private func takePhoto() {
photoOutput.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {[weak self] in
self?.takePhoto()
}
}
添加方法后,您会注意到该项目将不会编译。问题在于,我们将 self 作为capturephoto方法的代表,但是 self ,在这种情况下,这是 uiviewController 实例t支持avcapturephotocaptredElegate协议。稍后我们将需要此支持,以保存照片并静音声音。请记住这一点,为了解决汇编错误,我们将添加以下存根:
extension ViewController: AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {}
func photoOutput(_ output: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) {}
}
那么到目前为止我们有什么?两种方法,一种用于配置相机会话,另一个用于拍照(以一秒钟的间隔反复)。但是我们仍然需要连接它们,因此最后一步是添加第一个调用:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if UIImagePickerController.isSourceTypeAvailable(.camera) {
if let _ = setupCaptureSession() {
takePhoto()
} else {
print("Failed to establish capture session")
}
} else {
print("Camera not available") // Would be true in Simulator
}
}
现在运行项目时,构建应该成功。但是,在添加了第一个电话后,我们有一个运行时迷。 此应用程序崩溃了,因为它试图在没有使用说明的情况下访问对隐私敏感的数据。为了解决这个问题,我们需要在目标中添加更多关键值对。当我们在这里时,我们将添加一个用法说明,以访问相机和另一个用法描述,以将照片保存到相册中(稍后将在稍后实施)。
- 在项目导航器中,选择最高的元素,这将是项目本身。
- 在选择该项目的情况下,在目标下下,选择主要目标并导航到 info tab。
- 单击 plus 按钮,在将鼠标悬停在列表项目上时,它变得可见,然后添加以下(字符串)键: nscamerausausagedescription
- 将新添加的键的值设置为â,以拍摄秘密照片。
- 添加另一个(字符串)键: nsphotolibraryAddusagedescription
- 将上述新添加的键的值设置为â,以存储秘密照片
现在运行该应用程序时,您应该可以看到摄像机访问权限对话。授权后,您将能够听到相机射击器的声音效果,并看到激活指示器的绿色摄像头。
如何存储照片
在此程序中,我们将每张照片存储在最近的相册中。如果我们愿意,我们也可以将它们存储在远程服务器上,但这需要配置后端,并且超出了此演示的目的。
存储照片是通过调用系统方法 uiimagewritetosavedphotosalbum 。
来实现的。- 找到空的代表方法dodfinishProcessingphoto,该方法已添加,并插入以下机构:
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
guard let data = photo.fileDataRepresentation() else {
print("Processing did finish with no data")
return
}
guard let image = UIImage(data: data) else {
print("Processing did finish with invalid image data")
return
}
UIImageWriteToSavedPhotosAlbum(
image,
self,
#selector(image(_:didFinishSavingWithError:contextInfo:)),
nil)
print("Photo taken")
}
添加以下方法获得成功 /失败状态:
@objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
if let error = error {
print("Save error: \(error.localizedDescription)")
} else {
print("Saved!")
}
}
现在运行该应用程序时,您应该可以看到Gallery访问权限对话。授权后,您将可以在“照片应用程序”中看到新照片。尝试运行该应用程序几秒钟,看看是否添加了照片。
如何隐藏使用摄像头指示器
iOS应用程序可以控制屏幕亮度。在应用程序运行时将屏幕亮度设置为最小可能的值,这将导致使用摄像头指示器几乎无法可见。
我们将添加一个专门用于控制屏幕亮度的单独类:
final class DimUnDim {
static let shared = DimUnDim()
private var originalBrightness = UIScreen.main.brightness
func dim() {
print("dim")
UIScreen.main.wantsSoftwareDimming = true
UIScreen.main.brightness = 0.0
}
func unDim() {
print("unDim")
UIScreen.main.brightness = originalBrightness
}
}
当应用程序不活动时,我们将在应用程序处于活动状态并调用UNDIM时致电DIM。编辑文件** scenedelegate.swift **并添加以下内容:
func sceneDidBecomeActive(_ scene: UIScene) {
DimUnDim.shared.dim()
}
func sceneWillResignActive(_ scene: UIScene) {
DimUnDim.shared.unDim()
}
只要应用程序正在运行,这是很好的。如果它活跃,则屏幕是深色的,但是如果用户切换到主屏幕或另一个应用程序,则屏幕亮度保留了原始值。
但是,如果用户通过触摸屏幕手动终止应用程序,则屏幕将保持黑暗,因为未调用 scepencewillresignactive 。这是内在的。我们将通过在退出之前将另一个呼叫添加到undim来解决此问题。
找到戒烟方法,该方法如前所述,并添加呼叫:
/// Will quit the application with animation
@objc private func quit() {
DimUnDim.shared.unDim() // Restore normal screen brightness
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
/// Sleep for a while to let the app go in background
sleep(2)
exit(0)
}
现在运行项目时,屏幕亮度应完全按照我们需要的行为行为。
如何禁用射击器声音
找到前面添加的空委托方法Willcapturephotofor,并插入以下机构:
func photoOutput(_ output: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
// dispose system shutter sound
AudioServicesDisposeSystemSoundID(1108)
}
以下讨论中的答案之一复制了上述方法:
就是这样。
最终注释
如本项目所示,可以拍摄秘密照片的能力违背了保护用户隐私的当前持续趋势。如前所述,可以通过控制屏幕亮度来操纵使用绿色摄像头指示器。 Apple将来可以预防iOS版本。崩溃的应用程序在相机处于活动状态时试图降低屏幕亮度(正如我们看到我们无法生产摄像机 - 使用示例时崩溃的那样),没有任何问题。
是否可以创建一个类似的程序来记录视频而不是拍摄静物照片?我不知道,但值得尝试。