I'm making an application in which I do download some stuffs once I connect. I fulfill my CoreData without downloading my pictures. Now, once I connected, I push my segue and get a main screen.
Now what I want is to be able to swap my screens and download the pictures at the same time.
I do download from a Singleton class this way:
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
let tmp = DownloadManagerSingleton.sharedInstance
tmp.startDownloads()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("This is run on the main queue, after the previous code in outer block")
})
})
I can't find any way to download on background mode without the UI being locked until the downloads are finished. Am I doing this wrong or is there another way to fulfill my goal ?
Related
I'm developing a watchOS app that executes regularly in the background using background refresh tasks. A while ago I put in a call to update the watch's complication at each background refresh. This quickly burned through all of the complication updates and, I believe, the background budget for the app. I assumed it would reset at the beginning of the next day, but this doesn't seem to be the case. Since then, even though I've removed the complication refresh code altogether the app does not consistently execute background refreshes. I've deleted the app and reinstalled it and waited about a week but it only executes these refreshes regularly when run through Xcode (on the device), not when run independently. Any ideas?
Update:
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for backgroundTask in backgroundTasks {
switch backgroundTask {
case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
connectivityTask.setTaskCompleted()
case let refreshTask as WKApplicationRefreshBackgroundTask:
MainInterfaceController.scheduleTask()
refreshTask.setTaskCompleted()
case let snapshotTask as WKSnapshotRefreshBackgroundTask:
snapshotTask.setTaskCompleted()
default:
backgroundTask.setTaskCompleted()
}
}
DataProcessor.sharedInstance.dataProcessing()
public static func scheduleTask() {
let fireDate = Date(timeIntervalSinceNow: 10.0)
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: fireDate, userInfo: nil) { (error) in
if (error == nil) {
print("Background task scheduled")
}
}
}
And a task is scheduled when the application deactivates, kicking off the chain. This code was all working previously, which is the confusing part.
I am trying to show an activity indicator while creating a csv file, but it does not show. I am guessing I should use dispatch_async somehow, but I cant figure out how to do this in swift 3.
var activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
override func viewDidLoad() {
super.viewDidLoad()
// activity indicator
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 100 ,y: 200,width: 50,height: 50)) as UIActivityIndicatorView
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
activityIndicator.center = self.view.center
self.view.addSubview(activityIndicator)
}
func writeToCsv() {
self.activityIndicator.startAnimating() // start the animation
let fileName = "events.csv"
let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
var csvText = self.name! + "\n"
csvText += "Date,Start time,End time\n"
// create rest of comma-separated string
for event in self.events! {
let newLine = "\(event.date),\(event.startTime),\(event.endTime)\n"
csvText.append(newLine)
}
// write to csv
do {
try csvText.write(to: path!, atomically: true, encoding: String.Encoding.utf8)
} catch {
print("Failed to create file")
print(error)
}
// create and present view controller with send options
let vc = UIActivityViewController(activityItems: [path as Any], applicationActivities: [])
self.present(vc, animated: true, completion: nil)
self.activityIndicator.stopAnimating() // stop the animation
}
Err, alright bit hard to answer this without a bit more context about your view setup. First of all, make sure your activity indicator is visible without calling the writeCsv method, so you know your view hierarchy is correct. ( I.E. It could be that it is hidden behind some other subview )
Next, in Swift3 Dispatch has been changed to a newer API. I'm not sure whether on OSX they use the raw libdispatch Swift wrapper, but in any case you access it like this:
Background default queue:
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { /* code */ }
Main thread:
DispatchQueue.main.async { /* Mainthread code ( UIKit stuff ) */ }
Your own custom queue for CSV generation blocks:
let queue = DispatchQueue(label: "csvgenerator.queue")
queue.async { /* code */ }
Now for your animating / stopAnimation, make sure you call your UIKit related code from the mainthread to prevent weird glitechs and or crashes
Namely:
DispatchQueue.main.async {
self.activityIndicator?.startAnimating()
}
Another good idea might be to use NSOperationQueue instead. It internally uses GCD I believe, but it does integrate very well into iOS and might make some of the dispatching a lot easier to implement. I myself always use GCD instead, but I have never really had long queeu's of work that needed to be done. One of the advantages of NSOperationQueue is that it is a lot more user friendly in terms of cancelling dispatch blocks.
An interesting session video about NSOperationQueue in the WWDC app written by Dave Delong: WWDC Videos 2015
A small minor changes I'd make to your writeCSV method:
guard let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName) else {
// Should throw an error here, or whatever is handy for your app
return
}
Try to avoid forced unwrapping at all stages where possible.
In methods that have this, you can for instance add "throws" to the end of the function definition so you can use try without the do and catch block, while also being able to throw errors in your guard statement so whatever calls writeCsv can catch the error and more easily display it to the user.
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()
I'm currently using JSQMessagesViewController for showing the image bubble and SDImageWeb to download the images and display it. The way I'm doing this is that someone would send a person a text message containing a url and the sender and receiver would both check for that and if it matches display and download an image from the url. It would show up as an image bubble rather than text message.
Expected behavior
When sending media images or receiving I should have the images eventually load (there's a loading animation on the images). After it loads, I should easily see it next time I go on without loading since it's cached.
Actual behavior
When receiving media images, sometimes it loads right away but sometimes if it tries to load it will load indefinitely. I know it is done downloading because if I trigger a reload of collection view either through messaging something or going back to main view and coming back to the same viewcontroller it will show up. If I close my app and restart it, the pictures I have finished downloading should show up instead it will show up as loading again for indefinitely. It only happens to the ones most recent, the old ones are all okay.
Steps to reproduce
Use SD image to download images for media bubbles
Send images possibly on a slower internet where it takes more than a few milliseconds to load
Fixes I tried
The way I did this was I would receive a message containing a url and I download it and return back a JSQMessage that contains a media bubble. I tried reloading the collectionview at that index or reloading entire collectionview with a completion block in SD web but it ends up with a endless loop.
I'm not sure if this is currently a fault of SDweb or JSQMessageController. I have tried using Kingfisher as an image caching and downloading with relatively the same results.
Code
override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
let message = self.messages[indexPath.item]
if (message.body!.rangeOfString ("An image https://x.com/uploads/") != nil) {
let types: NSTextCheckingType = .Link
let detector = try? NSDataDetector(types: types.rawValue)
guard let detect = detector else {
let JSQTypeMessage = JSQMessage(senderId: message.from, senderDisplayName: message.from, date: message.date, text: message.body)
return JSQTypeMessage
}
let matches = detect.matchesInString(message.body!, options: .ReportCompletion, range: NSMakeRange(0, message.body!.characters.count))
var stringUrl:NSURL?
stringUrl = matches[0].URL!
let tempImageView = UIImageView(image: nil)
tempImageView.sd_setImageWithURL(stringUrl, completed: nil)
let photoImage = JSQPhotoMediaItem(image: tempImageView.image)
// This makes it so the bubble can be incoming rather than just all outgoing.
if !(message.from == self.senderId) {
photoImage.appliesMediaViewMaskAsOutgoing = false
}
let message = JSQMessage(senderId: message.from, displayName: self.senderDisplayName, media: photoImage)
return message
}
let JSQTypeMessage = JSQMessage(senderId: message.from, senderDisplayName: message.from, date: message.date, text: message.body)
return JSQTypeMessage
}
I think what you what to do is use the completion handler that is built into sdWebImage
tempImageView.sd_setImageWithURL(stringUrl, completed: {
//Set your image in here because you know that you have an image
// Then reload the cell
Self.tableView.reloadCellAtIndexPath(indexPath)
})
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?