Programmatically disabling screenshot in App - swift

I want to prevent taking screenshot of a page in app.
how to do it programmatically so that screenshots cannot be taken.
Found code to detect screenshot. Can it be deleted as soon as a screenshot is taken?
let mainQueue = NSOperationQueue.mainQueue()
NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationUserDidTakeScreenshotNotification,
object: nil,
queue: mainQueue) { notification in
// executes after screenshot
}

There is no way to prevent ScreenShots but you can prevent Screen Recording
through this code.
func detectScreenRecording(action: #escaping () -> ()) {
let mainQueue = OperationQueue.main
NotificationCenter.default.addObserver(forName: UIScreen.capturedDidChangeNotification, object: nil, queue: mainQueue) { notification in
// executes after screenshot
action()
}
}
//Call in vewWillApper
detectScreenRecording {
print(UIScreen.main.isCaptured)
if UIScreen.main.isCaptured {
//your vier hide code
print("self.toHide()")
} else {
// self.sceneDeleg(ate?.window?.isHidden = false
//your view show code
print("self.toShow()")
}
}

There is absolutely no way to completely prevent user from taking screenshot during the app process, and that's because you do not have access to delete photos in the photo gallery of the user. It would totally be a security issue if you could access your user's photos.
However, there are ways to partially prevent screenshots, as described here: Prevent screen capture in an iOS app

Technically that is possible, via the Photos framework, the docs for which can be found here.
Example code can be found here.
However, this will ask the user's permission first, and then again to confirm deletion; so possibly not the ideal solution. Unfortunately this is as good as it gets as Apple has the Camera Roll fairly locked down.

You cannot prevent user from taking screenshot, however, you can hide the content while a screenshot is taken, Use this code to do so..
extension UIView {
func hideContentOnScreenCapture() {
DispatchQueue.main.async {
let field = UITextField()
field.isSecureTextEntry = true
self.addSubview(field)
field.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
field.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
self.layer.superlayer?.addSublayer(field.layer)
field.layer.sublayers?.first?.addSublayer(self.layer)
}
}
}
Usage:
yourView.hideContentOnScreenCapture()

Related

Check if Screen is recording swift

I am trying to check if the screen is recording before allowing a following action, I initially tried to use ReplayKit to automatically record, but this isn't a viable solution because it doesn't allow for recording outside the app, so basically what I want to do is check if the user has began screen recording using the IOS Control Centre recorder, before allowing them to execute another piece of code.
Something like:
func handleScreen() {
var isRecording: Bool = false
if ScreenIsRecording { //(this is what i don't know how to check)
isRecording = true
}
if isRecording == true {
// execute this code.
}
}
I am open to other solutions of being able to execute screen recording, but it has to be able to record all screens not just the in-app screen.
Thanks
UIScreen includes the UIScreen.isCaptured property which you should be able to reference to determine if the screen is likely being recorded. However this will also return true if the device is being AirPlayed or otherwise broadcast:
A value of YES indicates the system is actively recording, mirroring,
or using AirPlay to stream the contents of the screen.
if UIScreen.mainScreen().isCaptured {
isRecording = true
}
I just tested this on iOS 16, and even though the documentation says that isCaptured should return true for AirPlay. It does not when I tested it!
Observe changes to isCaptured with NotificationCenter:
NotificationCenter.default.addObserver(self, selector: #selector(screenCaptureDidChange),
name: UIScreen.capturedDidChangeNotification,
object: nil)
Handle the notification:
#objc func screenCaptureDidChange() {
debugPrint("screenCaptureDidChange.. isCapturing: \(UIScreen.main.isCaptured)")
if !UIScreen.main.isCaptured {
//TODO: They stopped capturing..
} else {
//TODO: They started capturing..
debugPrint("screenCaptureDidChange - is recording screen")
}
}
This notification does NOT get fired when you start AirPlay, and if you start screen recording while AirPlaying, when you stop recording the notification will get fired but UIScreen.main.isCaptured is false even though AirPlay is still active.

Access to Photos on iOS(Swift), have to try twice to get picture library to show up. It doesn't show up the first time, but show's the second time

I call the function. Alright
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
let index = viewControllers?.index(of: viewController)
if index == 2 {
let layout = UICollectionViewFlowLayout()
let photoSelectorController = PhotoSelectorController(collectionViewLayout: layout)
let navController = UINavigationController(rootViewController: photoSelectorController)
present(navController, animated: true, completion: nil)
return false }
return true
}
Photos not showing on first time
I have all of the right things asking for permission and everything..
I then call for the images with these functions. It works, but the second time I hit the button after canceling posting a post..
I'm not sure how to get the images from the library for the first call.
After that it works like a charm, but most users have been telling me this isn't a good experience , if they have to try twice.
I'm trying to reduce friction in the app usage.
It should show the pictures right after the user "Allows" the app access to the pictures so they can post, but I'm not sure what I'm doing wrong for it to show the pictures soon as someone grants access.
var selectedImage: UIImage?
var images = [UIImage]()
var assets = [PHAsset]()
fileprivate func assetsFetchOptions() -> PHFetchOptions {
let fetchOptions = PHFetchOptions()
fetchOptions.fetchLimit = 100
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
fetchOptions.sortDescriptors = [sortDescriptor]
return fetchOptions
}
fileprivate func fetchPhotos() {
let allPhotos = PHAsset.fetchAssets(with: .image, options: assetsFetchOptions())
DispatchQueue.global(qos: .background).async {
allPhotos.enumerateObjects { (asset, count, stop) in
print(asset)
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 200, height: 200)
let options = PHImageRequestOptions()
options.isSynchronous = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options, resultHandler: { (image, info) in
if let image = image {
self.images.append(image)
self.assets.append(asset)
if self.selectedImage == nil {
self.selectedImage = image
}
}
if count == allPhotos.count - 1 {
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
}
})
}
}
}
If you fetchAssets before the user grants privacy access to your app, you'll get a PHFetchResult that's empty.
However, if before making that fetch you register as a photo library observer, you'll get a photoLibraryDidChange callback as soon as the user approves privacy access for the app... from that callback you can access an updated version of your original fetch result (see changeDetails(for:)) that has all of the assets your fetch should have found. Then you can tell your UI to update and display those assets. (This is how Apple's canonical PhotoKit example code works.)
Also, once you have a populated fetch result, please don't request thumbnails for the whole thing the way you're doing.
Users commonly have photo libraries with tens of thousands of assets, many of which are in iCloud and not on the local device. If you synchronously get all thumbnails, you'll take forever, use tons of memory and CPU resources, and generate all kinds of network traffic (slowing things down even more) for resources your user may never see.
PhotoKit is designed to allow easy use in conjunction with UI elements like UICollectionView. A collection view only loads cells that are currently (or soon to be) on screen, even if you've told it you have zillions of items in your collection — similarly, you can request thumbnails only for assets that are visible in your collection view. Wherever you have your per-cell UI setup logic is where you should have your PHImageManager request. (Again, this is what the canonical PhotoKit example code does.)
You can optimize even further by "preheating" the thumbnail fetch/generation process for assets that are soon to be onscreen. And then by managing your "preheating" to cancel such work in progress when further UI updates (e.g. fast scrolling of large collection) make it unnecessary. PHCachingImageManager does this. (And yet again, it's what the canonical Apple sample does. Actually, that sample's a bit out of date, and as such does more work than it needs to on this front — it does its own calculation of what cells are just outside the scroll rect, but since iOS 10 the UICollectionViewDataSourcePrefetching protocol manages that for you.)

How to add listener for all running applications

I want to display a list of all running application names.
Issue: It doesn't add an app that is running after the function is called. Therefore, it doesn't add the app name to the list
simultaneously.
Goal: I want to add a listener, so if a new app is running it will add it to the array simultaneously without restarting the app or recalling the function again.
func allRunningApplications() {
for runningApplication in NSWorkspace.shared.runningApplications {
let appName = runningApplication.localizedName
// Add App Name to Array
array.append(appName)
}
}
I mentioned the "did launch", et. al., notifications because you didn't explain why you wanted to monitor the set of running applications.
If you are only interested in whether a specific app has launched (or quit), it would probably be easier to use the NSWorkspace notifications:
(untested code)
let center = NSWorkspace.shared.notificationCenter
center.addObserver(forName: NSWorkspace.didLaunchApplicationNotification,
object: nil, // always NSWorkspace
queue: OperationQueue.main) { (notification: Notification) in
if let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication {
if app.bundleIdentifier == "com.apple.Terminal" {
// User just launched the Terminal app; should we be worried?
}
}
}
Note that workspace notifications are posted to NSWorkspace's private notification center, not the default notification center, so remember to add your observers there.
You could poll the runningApplications property (check it every x seconds) to test, if there is a new application. But it's not recommended: https://developer.apple.com/documentation/appkit/nsworkspace/1534059-runningapplications
Similar to the NSRunningApplication class’s properties, this property
will only change when the main run loop is run in a common mode.
Instead of polling, use key-value observing to be notified of changes
to this array property.
So use key-value observing on NSWorkspace.shared.runningApplications
A good example can be found here: https://www.ralfebert.de/ios-examples/swift/property-key-value-observer/
For your code it should be something like this:
var observers = [NSKeyValueObservation]()
override func viewDidLoad() {
super.viewDidLoad()
observeModel()
}
func observeModel() {
self.observers = [
NSWorkspace.shared.observe(\.NSWorkspace.runningApplications, options: [.initial]) {(model, change) in
// runningApplications changed, so update your UI or something else
}
]
}
(untested code)
You can try using the notification centre of NSWorkspace.
self.workspace = [NSWorkspace new];
NSArray *myObserver;
myObserver = (NSArray*) [[[NSWorkspace sharedWorkspace] notificationCenter] addObserverForName: NSWorkspaceWillLaunchApplicationNotification object:nil queue:nil usingBlock:^(NSNotification *note)
{
if(note)
{
//do your action
}
}
];
NSWorkspaceWillLaunchApplicationNotification will notify you if any application is about to be launched.

Why does the iPhone seem to freeze when using a while loop with swift?

I am trying to take a picture every 2 seconds by using a while loop. but when I try this the screen freezes.
This is the function that takes the photo:
func didPressTakePhoto(){
if let videoConnection = stillImageOutput?.connectionWithMediaType(AVMediaTypeVideo){
videoConnection.videoOrientation = AVCaptureVideoOrientation.Portrait
stillImageOutput?.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: {
(sampleBuffer, error) in
if sampleBuffer != nil {
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)
let dataProvider = CGDataProviderCreateWithCFData(imageData)
let cgImageRef = CGImageCreateWithJPEGDataProvider(dataProvider, nil, true, .RenderingIntentDefault)
let image = UIImage(CGImage: cgImageRef!, scale: 1.0, orientation: UIImageOrientation.Right)
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
//Adds every image taken to an array each time the while loop loops which will then be used to create a timelapse.
self.images.append(image)
}
})
}
}
To take the picture I have a button which will use this function in a while loop when a variable called count is equal to 0, but when the end button is pressed, this variable is equal to 1, so the while loop ends.
This is what the startPictureButton action looks like:
#IBAction func TakeScreanshotClick(sender: AnyObject) {
TipsView.hidden = true
XBtnTips.hidden = true
self.takePictureBtn.hidden = true
self.stopBtn.hidden = false
controls.hidden = true
ExitBtn.hidden = true
PressedLbl.text = "Started"
print("started")
while count == 0{
didPressTakePhoto()
print(images)
pressed = pressed + 1
PressedLbl.text = "\(pressed)"
print(pressed)
sleep(2)
}
}
But when I run this and start the timelapse the screen looks frozen.
Does anyone know how to stop the freeze from happening - but also to add each image taken to an array - so that I can turn that into a video?
The problem is that the method that processes clicks on the button (TakeScreanshotClick method) is run on the UI thread. So, if this method never exits, the UI thread gets stuck in it, and the UI freezes.
In order to avoid it, you can run your loop on the background thread (read about NSOperation and NSOperationQueue). Occasionally you might need to dispatch something from the background thread to the UI thread (for instance, commands for UI updates).
UPDATE: Apple has a really great documentation (best of what I've seen so far). Have a look at this: Apple Concurrency Programming Guide.
You are calling the sleep command on the main UI thread, thus freezing all other activity.
Also, I can't see where you set count = 1? Wouldn't the while loop continue forever?

WatchKit 2 Complication Text Only Shows Up in Preview

I'm trying to develop a very simple complication for watchkit2 that says "Hi" from a simple text provider.
I've managed to achieve some strange behavior; I can see the text when the complication is clicked or when you are previewing it from the customize watchface screen, but not when the watchface is displayed. Have a look:
Any ideas what might be causing this?
My text provider looks like this
var textProvider: CLKSimpleTextProvider
override init() {
textProvider = CLKSimpleTextProvider()
textProvider.text = "Hi"
textProvider.shortText = "HI"
textProvider.tintColor = UIColor.whiteColor()
super.init()
}
And my get getPlaceholderTemplateForComplication looks like
func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
switch complication.family {
case .ModularSmall:
let stemplate = CLKComplicationTemplateModularSmallSimpleText()
stemplate.tintColor = UIColor.whiteColor()
stemplate.textProvider = textProvider
handler(stemplate)
case .CircularSmall:
let stemplate = CLKComplicationTemplateCircularSmallSimpleText()
stemplate.tintColor = UIColor.whiteColor()
stemplate.textProvider = textProvider
handler(stemplate)
default:
handler(nil)
}
}
While customizing watch face, Apple Watches calls getPlaceholderTemplateForComplication:withHandler: to show placeholder text. Since you've implemented it - you can see "Hi". That is cool.
But when watch face displayed, it calls another methods, such as:
getCurrentTimelineEntryForComplication:withHandler:
getTimelineEntriesForComplication:beforeDate:limit:withHandler:
getTimelineEntriesForComplication:afterDate:limit:withHandler:
And it seems like you're not implemented them. So implementing these method will resolve your issue.
You can find more detailed information about these methods in this WWDC 2015 Tutorial: https://developer.apple.com/videos/wwdc/2015/?id=209