Slow loading of images in UITableViewCell since Xcode 11 - ios

We have an application that has been running for years using UITableViewCells containing thumbnail images stored locally. The loading of images was never a problem and scrolling was fast. Since the latest Xcode update it has become sticky and images are loading slowly. I've implemented asynchronous loading to try and solve this and scrolling is smooth but there is a significant lag in loading images which is unacceptable.
Has anyone else experienced this? What has changed in Xcode 11 to cause this?
For what it's worth, here is the code I have now:
DispatchQueue.global(qos: .background).async {
let ImageLoaded = g_resources.readTreeImage(id: UInt(initData.IconImageID) ?? 0)
if(initData.IconImageID != nil)
{
DispatchQueue.main.async(execute: {() -> Void in
self.m_treeImageView.image = ImageLoaded
})
}
}

Related

WKInterfaceMap blank rendering

WKInterfaceMap sometimes renders blank in a very simple app I am developing. The map appears completely empty (no grid of lines or anything similar). It looks as though the thread responsible for drawing the tiles gets blocked.
In order to reproduce the issue just add the code below to the extension delegate.
func applicationDidEnterBackground() {
let watchExtension = WKExtension.shared()
// Schedule the background refresh task.
watchExtension.scheduleBackgroundRefresh(withPreferredDate: Date().addingTimeInterval(15.0*60.0), userInfo: nil) { (error) in
// Check for errors.
if let error = error {
print("ExtensionDelegate: \(error.localizedDescription)")
return
}
print("ExtensionDelegate: Background Task scheduled successfuly")
}
}
Add a WKInterfaceMap to the main view and run once on the simulator. Close the app using the crown and stop it from XCode. Wait for at least 15 minutes and open the app again directly from the simulator.
The map renders then as in the image below.

UICollectionView removing an item issue

I have created a CollectionView where displays the photos from the camera roll, just like the default photo app of iPhone (one cell per photo)
I need to implement a delete functionality, so I successfully created a delete action. The problem is when I am trying to reload the data of the collection view.
To give an example I want to mimic the default's photo app behavior on delete.
Below I have the code which crashes. It is executed after successfully delete action.
func removeFromCollectionView() {
let ip = self._collectionView.indexPathsForVisibleItems
self._itemsCount? -= 1
_collectionView.deleteItems(at: ip)
_collectionView.performBatchUpdates({
self._collectionView.reloadItems(at: ip)
}, completion: nil)
}
The error is:
This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
Also, I have tried to wrap the code into
DispatchQueue.main.async { }
I tried to be explanatory let me know if you need more, does anyone has an idea? Thanks in advance.
EDIT:
I did the below changes based on comments. The code does not crash but does not delete the photo
func removeFromCollectionView() {
let ip = self._collectionView.indexPathsForVisibleItems
self._itemsCount? -= 1
_collectionView.performBatchUpdates({
self._collectionView.deleteItems(at: ip)
}, completion: { finished in
if finished {
print("finished")
self._collectionView.reloadData()
}
})
}

Firebase Storage download task is not completing after the app has spent some time in the background

I am downloading an image from Firebase storage as follows:
let storage = FIRStorage.storage()
// Create a storage reference from our storage service
let storageRef = storage.reference(forURL: "MY_STORAGE_URL")
let imageRef = storageRef.child("Path_to_image")
// Download image in memory
let downloadTask = imageRef.data(withMaxSize: 1 * 1024 * 1024) {
(data, error) -> Void in
if (error != nil) {
//Handle the error
} else {
guard let imageData = data else {
print("Unable to unwrap image data.")
return
}
let downloadedImage = UIImage(data: imageData)
//Do some stuff with the image
}
}
I am also monitoring what happens with the download using the following observers:
// Observe changes in status
downloadTask.observe(.resume) { (snapshot) -> Void in
// Download resumed, also fires when the download starts
}
downloadTask.observe(.pause) { (snapshot) -> Void in
// Download paused
}
downloadTask.observe(.progress) { (snapshot) -> Void in
// Download reported progress
}
downloadTask.observe(.success) { (snapshot) -> Void in
// Download completed successfully
}
downloadTask.observe(.failure) { (snapshot) -> Void in
//Download failed
}
This all works just fine when the app is first started. However, I am getting problems if the app enters the background and I play around with some other applications (Facebook, Twitter, etc.), then bring the app back to the foreground. I also have problems if I leave the app open and running in the foreground for greater than or equal to 1 hour.
The problem is that the completion handler in let downloadTask = imageRef.data(withMaxSize: blah blah blah (in the first block of code above) is never called. If the completion handler is never called, I can never unwrap the data and attempt to use the image in my application.
Also, in the downloadTask observers, the only completion handlers that get fired are .resume and .progress. The .success or .failure events are never triggered. This seems to be a Firebase Storage bug to me, but I am not sure. Has anyone else encountered a similar issue? I don't understand why the code would work just fine from a fresh launch, but then after some time in the foreground or after some time in the background the image download stops working. Thanks in advance for any input you may have.
This is currently the expected behavior, unfortunately. Firebase Storage (at present) is foreground only: if the app is backgrounded, we haven't persisted the upload URL, and can't upload in the background nor restart it after it gets out of the background, so it probably is killed by the OS and the item isn't uploaded.
It's The Next Big Thing™ we'd like to tackle (our Android SDK makes it possible, though not easy), but unfortunately for now we haven't made more progress on this.
As a bit of a side note, your observers won't exist after the activity change--downloadTask is gone once the app is backgrounded, so when it comes back into the foreground, we basically need a method that retrieves all tasks that are currently backgrounded, and allows you to hook observers back up. Something like:
FIRStorage.storage().backgroundedTasks { (tasks) -> Void in
// tasks is an array of upload and download tasks
// not sure if it needs to be async
}

Why does Example app using Photos framework use stopCachingImagesForAllAssets after each change?

So I'm looking at the Photos API and Apple's sample code here
https://developer.apple.com/library/ios/samplecode/UsingPhotosFramework/Introduction/Intro.html
and its conversion to swift here
https://github.com/ooper-shlab/SamplePhotosApp-Swift
I have integrated the code into my project so that a collectionView is successfully updating itself form the library as I take photos. There is one quirk: Sometimes cells are blank, and it seems to be connected to stopCachingImagesForAllAssets which Apple calls each time the library is updated at the end of photoLibraryDidChange delegate method.
I can remove the line and it fixes the problem, but surely there is a reason Apple put it there in the first place? I am concerned with memory usage.
// MARK: - PHPhotoLibraryChangeObserver
func photoLibraryDidChange(changeInstance: PHChange) {
// Check if there are changes to the assets we are showing.
guard let
assetsFetchResults = self.assetsFetchResults,
collectionChanges = changeInstance.changeDetailsForFetchResult(assetsFetchResults)
else {return}
/*
Change notifications may be made on a background queue. Re-dispatch to the
main queue before acting on the change as we'll be updating the UI.
*/
dispatch_async(dispatch_get_main_queue()) {
// Get the new fetch result.
self.assetsFetchResults = collectionChanges.fetchResultAfterChanges
let collectionView = self.pictureCollectionView!
if !collectionChanges.hasIncrementalChanges || collectionChanges.hasMoves {
// Reload the collection view if the incremental diffs are not available
collectionView.reloadData()
} else {
/*
Tell the collection view to animate insertions and deletions if we
have incremental diffs.
*/
collectionView.performBatchUpdates({
if let removedIndexes = collectionChanges.removedIndexes
where removedIndexes.count > 0 {
collectionView.deleteItemsAtIndexPaths(removedIndexes.aapl_indexPathsFromIndexesWithSection(0))
}
if let insertedIndexes = collectionChanges.insertedIndexes
where insertedIndexes.count > 0 {
collectionView.insertItemsAtIndexPaths(insertedIndexes.aapl_indexPathsFromIndexesWithSection(0))
}
if let changedIndexes = collectionChanges.changedIndexes
where changedIndexes.count > 0 {
collectionView.reloadItemsAtIndexPaths(changedIndexes.aapl_indexPathsFromIndexesWithSection(0))
}
}, completion: nil)
}
self.resetCachedAssets() //perhaps prevents memory warning but causes the empty cells
}
}
//MARK: - Asset Caching
private func resetCachedAssets() {
self.imageManager?.stopCachingImagesForAllAssets()
self.previousPreheatRect = CGRectZero
}
I was having the same result.
Here's what fixed the issue for me:
Since performBatchUpdates is asynchronous, the resetCachedAssets gets executed possibly while the delete/insert/reload is happening, or even between those.
That didn't sound nice to me. So I moved the line:
self.resetCachedAssets()
to the first line of the dispatch_async block.
I hope this helps you too.

progressBlock in getDataInBackgroundWithBlock not called

My Swift app uses Parse.com as the backend. Among other things, I store images for user profiles. I store two versions of the same image: one thumbnail and one full sized image. When the user taps on the thumbnail, it is enlarged. The app then loads the full sized version and presents it on top of the enlarged thumbnail when loading is complete. Since the thumbnail image looks blurry while the full sized one is loading, I've implemented a circular progress bar that shows the loading progress, giving visual feedback of what's going on.
This is the method that handles this process (progressCircle is a CAShapeLayer):
func fetchFullImage() {
photoObject.fetchIfNeededInBackgroundWithBlock { (fetchedObject: PFObject!, error: NSError!) -> Void in
if error == nil {
// After fetching object, now we get the full sized image
let userFullImageFile = fetchedObject["fullImage"] as PFFile
userFullImageFile.getDataInBackgroundWithBlock( {
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
println(">> Success! Full image retrieved.")
self.fullImageView.image = UIImage(data: imageData)
// Hide the progress indicator
self.progressCircle.hidden = true
// Fade in and show the image
self.showFullImage()
}
},
progressBlock: {(percentDone: CInt) -> Void in
println("Loading... \(percentDone)")
// Prints out progress percentage under wifi, but not under 3G!
self.progressCircle.strokeEnd = CGFloat(percentDone) / 100.0
})
} else {
println(error)
}
}
}
Now this is the funny part. When I run the app on a real device connected via wifi, everything goes as expected: the progress circle grows as the image is loaded, etc. Now when I turn off wifi and the same device is connected via 3G, the progress circle does not grow. Essentially, what happens is that the progressBlock closure is not called (I can see this because the println statement produces no result). The image loads after a while (the completion closure is called), and is presented correctly, but there is no way for me to show the progress.
I am trying to find the logic behind this. I could almost understand it if it was the opposite (image loading so fast over wifi that the progress was not visible), but over 3G images take a few seconds to load, during which period nothing seems to happen.
Does anyone have an idea of what can keep progressBlock from being called here? I want to be able to provide the "loading in progress" visual feedback.
Thanks in advance!

Resources