I have a MKMapView where the user can choose if he wants to use Apple Maps or an alternative map source.
On that map I draw a MkPolyline showing the current heading. That line updates once a second by removing and adding the line.
My app does a lot of other calculations, and while in debug mode the CPU level is around 20% when using Apple maps. If I add the MKTileOverlay to use an alternative map source, the CPU level increases to around 140%.
Does anybody know the reason for this? What's different when using a MkTileOverlay?
Had a same problem, took me a while to figure it out but here it is:
This will happen when your tile overlay's override func loadTile(at path: MKTileOverlayPath, result: #escaping (Data?, Error?) -> Void) has an error (for example because of url error or image decoding) and the result callback's first parameter is set null. To solve it, I return an empty image data instead. Here is an example:
class MyTileOverlay: MKTileOverlay {
private lazy var emptyImageData: Data? = {
UIGraphicsBeginImageContext(tileSize)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image?.pngData()
}()
override func loadTile(at path: MKTileOverlayPath, result: #escaping (Data?, Error?) -> Void) {
super.loadTile(at: path) { (data, error) in
if let data = data,
var image = UIImage(data: data){
if UIScreen.main.traitCollection.userInterfaceStyle == .dark {
image = image.invert()
}
result(image.pngData(), error)
}else {
result(self.emptyImageData, error)
}
}
}
Related
My app as the functionality of choosing multiple images from the app main screen, and save the selected images to the user gallery.
As an example (image from google):
After the user clicking "save" I am doing the following in order to save the chosen images to the user's gallery.
Running through all of the images and saving each image that on clicked.
func saveSelectedImagesToDevice() {
for imageList in imagesListCells {
for image in imageList.images {
if image.selectionState == .onClicked {
downloadImage(from: image.url)
}
}
}
}
Downloading each image
func downloadImage(from url: String) {
guard let url = URL(string: url) else {return}
getData(from: url) { data, response, error in
guard let data = data, error == nil else { return }
guard let image = UIImage(data: data) else {return}
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
}
}
private func getData(from url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
#objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
if let _ = error {
self.delegate?.savedImage(proccessMsg: "Error adding images to the gallery, pleast make sure you enabled photos permissions in phone setting")
}
}
The thing is, because the saving process is asynchronies, in case error occurs in one of the process of downloading an image, I would like to stop all of the other asynchronies processes that running on the background.
At the moment, in case of error, the error been called for each one of the images.
Any ideas how can I manage it different in order to keep the process asynchronies but to be able to stop all processes in case of error?
You would have to change completely the architecture of the download to make it cancellable. A data task is cancellable, but yours is not because you have not retained any way of referencing it.
Apple suggests to not using the shared instance if you want to create multiple sessions. You could try to achieve this by creating a single session instance and invalidate it as soon as you receive an error.
Keep in mind that if you want to re-start the session you need to re instantiate a new one.
e.g.
let session = URLSession(configuration: .default)
func downloadImage(from url: String) {
guard let url = URL(string: url) else {return}
session.dataTask(with: url) { [weak self] data, response, error in
guard let self = self else { return }
if let error = error {
print("You have an error: ",error.localizedDescription)
self.session.invalidateAndCancel()
}else if let data = data,
let image = UIImage(data: data) {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
}
}.resume()
}
I have a problema with this method:
func DownloadImages(uid: String, indice: Int) {
DispatchQueue.global(qos: .background).async {
let refBBDD = FIRDatabase.database().reference().child("users").child(uid)
refBBDD.observeSingleEvent(of: .value, with: { snapshot in
let snapshotValue = snapshot.value as? NSDictionary
let profileImageUrl = snapshotValue?.value(forKey: "profileImageUrl") as! String
let storage = FIRStorage.storage()
var reference: FIRStorageReference!
if(profileImageUrl == "") {
return
}
print("before")
reference = storage.reference(forURL: profileImageUrl)
reference.downloadURL { (url, error) in
let data = NSData(contentsOf: url!)
let image = UIImage(data: data! as Data)
print("image yet dowload ")
self.citas[indice].image = image
DispatchQueue.main.async(execute: { () -> Void in
self.tableView.reloadRows(at: [IndexPath(row: indice, section: 0)], with: .none)
//self.tableView.reloadData()
print("image loaded")
})
}
print("after")
})
}
}
I want to download images in background mode. I want follow using app, but the UI has frozen until methods not entry in reloadRows.
Is it possible run in true background mode and can i follow using the app??
Trace program:
before
after
before
after
...
before
after
before
after
image yet dowload --> here start UI frozen
image loaded
image yet dowload
image yet dowload
...
image yet dowload
image yet dowload
image loaded
image loaded
...
image loaded
image loaded
image yet dowload ------> here UI is not frozen
image loaded
The problem is caused by this line: let data = NSData(contentsOf: url!).
That constructor of NSData should only be used to read the contents of a local URL (a file path on the iOS device), since it is a synchronous method and hence if you call it to download a file from a URL, it will be blocking the UI for a long time.
I have never used Firebase, but looking at the documentation, it seems to me that you are using the wrong method to download that file. You should be using func getData(maxSize size: Int64, completion: #escaping (Data?, Error?) -> Void) -> StorageDownloadTask instead of func downloadURL(completion: #escaping (URL?, Error?) -> Void), since as the documentation states, the latter only "retrieves a long lived download URL with a revokable token", but doesn't download the contents of the URL itself.
Moreover, you shouldn't be force unwrapping values in the completion handler of a network request. Network requests can often fail for reasons other than a programming error, but if you don't handle those errors gracefully, your program will crash.
Your problem is in this line let data = NSData(contentsOf: url!) and let's now see what apple says about this method below
Don't use this synchronous method to request network-based URLs. For
network-based URLs, this method can block the current thread for tens
of seconds on a slow network, resulting in a poor user experience, and
in iOS, may cause your app to be terminated.
So it clearly states that this method will block your User Interface.
I have a UITable-View with a List of Users and their Profile Pictures.
I am loading the pictures (http://api/pictures/{userid}) one by one for each player asynchronous:
func loadImageAsync(imageUrl: URL, completionHandler handler: #escaping (_ success: Bool, _ image: UIImage?) -> Void){
DispatchQueue.global(qos: .userInitiated).async { () -> Void in
if let imgData = try? Data(contentsOf: imageUrl), let img = UIImage(data: imgData) {
DispatchQueue.main.async(execute: { () -> Void in
handler(true, img)
})
} else {
handler(false, nil)
}
}
In the completion handler in the cellForRowAt-Index-Fuction, I am setting the pictures.
loadImageAsync(imageUrl: imageUrl!, label: ip) { (success, image, backlabel) -> Void in
if(success){
cell.profilePictureView.image = image
}
}
However, when I scroll very fast, some pictures get loaded in the wrong cells.
To prevent reuse-issues, I am "resetting" the image view after every reuse:
override func prepareForReuse() {
profilePictureView.image = UIImage(named: "defaultProfilePicture")
}
But why are still some images loaded false when scrolling fastly?
hmmm, this is what I thought too.
__Update:
So, I extended the function with a Label Parameter (type Any), that is returned back as it was put in the function. I tried to compare the parameter (is used the indexpath) with the current indexpath. Actually, this should work - shouldn't it?!
loadImageAsync(imageUrl: imageUrl!, label: ip) { (success, image, backlabel) -> Void in
cell.loader.stopAnimating()
if (backlabel as! IndexPath == indexPath) {
//set image...
But however, it doesn't show any effect. Do you know why or have any other solutions to fix this?
The issue is that if you scroll fast, the download may take long enough that by the time it's complete, the cell in question has scrolled off the screen and been recycled for a different indexPath in your data model.
The trick is to ask the table view for the cell at that indexPath in the completion block and only install the image if you get a cell back:
loadImageAsync(imageUrl: imageUrl!, label: ip, for indexPath: IndexPath) { (success, image, backlabel) -> Void in
if(success){
let targetCell = tableview.cell(for: indexPath)
targetCell.profilePictureView.image = image
}
}
EDIT:
Redefine your loadImageAsync function like this:
func loadImageAsync(imageUrl: URL,
indexPath: IndexPath,
completionHandler handler: #escaping (_ success: Bool,
_ image: UIImage?,
_ indexPath: IndexPath ) -> Void) { ... }
EDIT #2
And by the way, you should really save your images to disk and load them from there rather than loading from the internet each time. I suggest using a hash of the image URL as a filename.
Modify loadImageAsync as follows:
Check to see if the file already exists on disk. If so, load it and return it.
If the file does not exist, do the async load, and then save it to disk using the hash of the URL as a filename, before returning the in-memory image.
Because your completionHandler can be called after the cell has been reused for the next user, and possibly another image request for the cell has been fired. The order of events (reuse/completion) is not predictable, and in fact a later async request could complete before an earlier one.
i need your help with GCD in swift, i just started using this tool and i'm little confused. So let's say i have a function foo() that taking photo and saving it to var, inside this function there is another function that checking if there are faces on that photo and returning true or false. Next, when user taps usePhotoFromPreview button it will save photo through custom delegate whenever there aren't any faces on that photo like this:
var image: UIImage?
var checkingPhotoForFaces: Bool?
func foo(){
let videoConnection = imageOutput?.connection(withMediaType: AVMediaTypeVideo)
imageOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: { (imageDataSampleBuffer, error) -> Void in
if let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer) {
image = UIImage(data: imageData)
self.checkingPhotoForFaces = self.detect(image: image)
}
})
}
func detect(image: UIImage) -> Bool{
.....
}
func usePhotoFromPreview(){
self.dismiss(animated: true, completion: {
if self.checkingPhotoForFaces == false{
self.delegate?.takenPhoto(photo: self.image!)
print("photo taken")
}else{
self.delegate?.takenPhoto(photo: nil)
print("no photo taken")
}
})
}
So now the tricky part, i'd like to make detect function to be executed asynchronously and only when it's done execute usePhotoFromPreview. detect func i suppose should be on main thread because of CoreImage implementation. I simply made something like this:
func foo(){
...
DispatchQueue.global().async {
self.checkingPhotoForFaces = self.detect(image: stillImage)
}
...
}
but the problem is when user taps too quick on that button it won't work, cuz detect func is still processing, so i think i need some kind of queue but i'm confused which one.
btw. please, do not cling to the lack of some parameters above, i'm writing it on the fly and that's not the point
Thank you for your help
Change detect as commented above:
func detect(image: UIImage, completion: ()->(detectedFaces: Bool)){
//.....
completion(true|false)
}
Then
if let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer) {
image = UIImage(data: imageData)
self.detect(image) { detectedFaces in
self.checkingPhotoForFaces = detectedFaces
//Enable button?
}
}
I've written my code for retrieving images from my Parse data console so that they will appear in my Storyboard for my Swift app, but I'm not sure how to make an IBOutlet connect to the UIImageView I added to my Storyboard so that the image from Parse will actually appear in its place. Here's the code I have so far in my ViewController:
var imageResources : Array<UIImage> = []
override func viewDidLoad() {
super.viewDidLoad()
func loadImages(){
var query2 = PFQuery(className: "VoteCount")
query.findObjectsInBackgroundWithBlock ({(objects:[AnyObject]!, error: NSError!) in
if(error == nil){
let imageObjects = objects as [PFObject]
for object in objects {
let thumbNail = voteCount1["image"] as PFFile
thumbNail.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
let image = UIImage(data:imageData)
self.imageResources.append(image!)
println(image)
}
})
}
}
else{
println("Error in retrieving \(error)")
}
})
}
}
What kind of IBOutlet do I make that connects to the UIImageView I've added to my Main.storyboard? I'm just not sure what to name it that will call the image from Parse that I'm trying to retrieve.
control-drag from your UIImageView to your Swift file, outside the methods but inside the implementation. name it myImageView.
You can't call println on an image.
Then after the line if (error == nil) { try the following
if let image = UIImage(data:imageData) {
dispatch_async(dispatch_get_main_queue()) {
self.myImageView.image = image
}
}
Or something to that effect. You need to be sure:
the image exists
you do anything with UI updates on the main thread (that's what dispatch_async(dispatch_get_main_queue()) {} accomplishes
Try it and let me know if that helps. I'm on the road right now but when I get a chance to sit I'll circle back and check this out.
One last trick: you can put a breakpoint on lines where you get an image. Then when the application pauses, you can hover over the word image in the line if let image = UIImage(data:imageData) { and click on the eye icon in the popup. You should then be able to view the image from Parse. If the image doesn't appear when you click the eye icon, you probably have additional debugging to do (does the image exist, is your logic up to that point correct, etc).