When I open my application it asks me for permission to access my photos. That is all all right. But after accepting, the screen turns black and shows no photos and in my console it says that it has found no photos. Although I have photos on my device.
import UIKit
import Photos
class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var imageArray = [UIImage]()
override func viewDidLoad() {
grabPhotos()
}
func grabPhotos(){
let imgManager = PHImageManager.defaultManager()
let requestOptions = PHImageRequestOptions()
requestOptions.synchronous = true
requestOptions.deliveryMode = .HighQualityFormat
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"CreationDate" , ascending: false)]
if let fetchResult : PHFetchResult = PHAsset.fetchAssetsWithMediaType(.Image, options: fetchOptions){
if fetchResult.count > 0 {
for i in 0..<fetchResult.count{
imgManager.requestImageForAsset(fetchResult.objectAtIndex(i) as! PHAsset, targetSize: CGSize(width: 200, height: 200), contentMode: .AspectFill, options: requestOptions, resultHandler: {
image, error in
self.imageArray.append(image!)
})
}
}
else{
print("You have got not photos!")
self.collectionView?.reloadData()
}
}
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
let imageView = cell.viewWithTag(1) as! UIImageView
imageView.image = imageArray[indexPath.row]
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let width = collectionView.frame.width / 3 - 1
return CGSize(width: width, height: width)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 1.0
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
return 1.0
}
}
Most likely the problem here is that you're asking for assets only once. The first time you ask, you get nothing. When you call PHAsset.fetchAssetsWithMediaType, it does two things at the same time: It asks the user for authorization, and because authorization hasn't been granted yet (the authorization prompt is still on screen), it returns an empty fetch result. (And because the fetch result is empty, you can't
If that's the problem, you should see a non-empty fetch result the second time you run your app (because authorization is a one-time thing and persists across launches).
There are a few ways to deal with this...
One is simply to request authorization directly first using requestAuthorization() and fetch assets only after that call's completion handler fires (with a positive result).
Another is to register() your view controller (or some other controller class of yours) as an observer of the Photos library before performing a fetch. Your first fetch before authorization still returns an empty result, but as soon as the user has authorized your app your observer gets a photoLibraryDidChange() call from which you can access the full, "real" fetch results.
Beyond that, there are some other things you're doing that aren't best practice here...
You're requesting images from the PHImageManager for every asset in the library, regardless of whether the user is going to see them. Consider the case of someone whose iCloud Photo Library contains tens of thousands of assets — only the first 30 or so are going to fit on your collection view at once, and the user may never scroll all the way to the end, but you're still fetching thumbnails for Every Image Ever. (And in the case of iCloud assets not on the local device, trigging network activity for lots of thumbnails the user might never see.)
You're getting thumbnails from PHImageManager synchronously and sequentially. Once you get the authorization issue dealt with, your app will probably crash because your for i in 0..<fetchResult.count { imgManager.requestImageForAsset loop blocks the main thread for too long.
Apple provides canonical example code that demonstrates how to use the Photos app to do things like displaying a collection view full of thumbnails. Take a look at AssetGridViewController.swift in that project to see some of the things they're doing:
use PHCachingImageManager to prefetch batches of thumbnails based on the visible area of a collection view
use PHPhotoLibraryObserver to react to changes in a fetch result, including animated collection view updates
handling the interaction between asynchronous fetches and the collection view data source
inserting new assets in the Photos library
1) Subclass a UICollectionViewCell:
import ...
//put this e.g. to the top of your swift file, right after import statements
class PhotoViewCell: UICollectionViewCell {
#IBOutlet weak var myImageView: UIImageView!
}
2) Create a prototype for you cell:
2.1 design it (you may have done before)
2.2 enter an identifier
2.3 enter a class
2.4 CTRL drag from storyboard: from the cell's imageView to the #IBOutlet line (from the step 1)
3) Update your cellForItem method:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as! PhotoViewCell {
cell.myImageView.image = imageArray[indexPath.row]
return cell
}
use a class for cell , put imageview in your cell make it an outlet
now write this
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! 'your class name for cell'
cell.Image.image = imageArray[indexPath.row]
return cell
}
Related
I am trying to recreate this thing. I've created in Storyboard skeleton. Here's the idea of my code:
Fetch images from URL's array with help of the function getThumbnailFromImage
Add UIImage's with my thumbnails in array webImages
Add in ViewController reusable cell MyCollectionView
...
But here I am with this))) (Don't mind absence of Auto Layout). What am I doing wrong? I think that the problem is with reloadData() but I don't know where to put it.
ViewController:
//
// ViewController.swift
// youtube-clone
//
// Created by мас on 16.08.2022.
//
import Foundation
import UIKit
import YouTubePlayer
import AVFoundation
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var url: [URL?] = [
URL(string: "https://www.youtube.com/watch?v=KhebpuFBD14"),
URL(string: "https://www.youtube.com/watch?v=UfNdNrRHpUw"),
URL(string: "https://www.youtube.com/watch?v=CX-BdDHW0Ho"),
URL(string: "https://www.youtube.com/watch?v=NIOMtSzfpck")
]
var webImages: [UIImage] = []
var currentPage: Int = 0
#IBOutlet var myPage: UIPageControl!
#IBOutlet weak var buttonInfo: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
myPage.currentPage = 0
myPage.numberOfPages = webImages.count
}
// MARK: - Collection View Setup
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return webImages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionCell
getThumbnailFromImage(url: url[indexPath.row]!, completion: { image in
self.webImages.append(image!)
})
cell.myWebImage.image = webImages[indexPath.row]
cell.myWebImage.layer.cornerRadius = 20
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
myPage.currentPage = indexPath.row
}
// MARK: - Layout Setup // IGNORE IT
func setupLayout() {
buttonInfo.layer.cornerRadius = 25
buttonInfo.imageView!.transform = CGAffineTransform(rotationAngle: 180 * .pi / 180)
self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
}
// MARK: - Videos Thumbnail Fetcher
func getThumbnailFromImage(url: URL, completion: #escaping ((_ image: UIImage?) -> Void)) {
DispatchQueue.global().async {
let asset = AVAsset(url: url)
let avAssetImageGenerator = AVAssetImageGenerator(asset: asset)
avAssetImageGenerator.appliesPreferredTrackTransform = true
let thumbnailTime = CMTimeMake(value: 7, timescale: 1)
do {
let cgThumbImage = try avAssetImageGenerator.copyCGImage(at: thumbnailTime, actualTime: nil)
let thumbImage = UIImage(cgImage: cgThumbImage)
DispatchQueue.main.async {
completion(thumbImage)
}
}
catch {
print(error.localizedDescription)
}
}
}
}
Reusable Cell AKA MyCollectionCell:
import UIKit
class MyCollectionCell: UICollectionViewCell {
#IBOutlet var myWebImage: UIImageView!
}
P.s.: YouTubePlayer is custom pod from GitHub, it's not currently used.
You do NOT have to use AVAssetImageGenerator, Simply you can use Youtube API to fetch the thumbnail images as .jpg image by video id,
and each YouTube video has four generated images.
https://img.youtube.com/vi/{id}/0.jpg
https://img.youtube.com/vi/{id}/1.jpg
https://img.youtube.com/vi/{id}/2.jpg
https://img.youtube.com/vi/{id}/3.jpg
Example
https://img.youtube.com/vi/KhebpuFBD14/0.jpg
And then it is preferred to use a third party to load this image as its displayed in a list, like https://github.com/SDWebImage/SDWebImage or https://github.com/onevcat/Kingfisher and you will NOT be worry about Concurrency or caching.
A couple of thoughts:
#matt is right in the comment - getThumbnailFromImage will likely not have called the completion block by the time cellForItemAt returns.
From what is visible in the code you posted, webImages.count will still be 0 when your collection view checks numberOfItemsInSection. If the number of items is 0, cellForItemAt may never get called so the call to getThumbnailFromImage wouldn't even be reached. (I'm not sure if the white box in your screenshot is part of a cell or another view element. If a cell is being displayed, I'm assuming you're populating webImages somewhere else before the collection view gets laid out).
One way you could work around these issues is by giving each cell a URL rather than a thumbnail. That way the cell can be displayed while the image is still loading. The cell could look something like this:
class MyCollectionCell: UICollectionViewCell {
#IBOutlet var myWebImage: UIImageView!
func configure(urlString: String) {
guard let self = self, let url = URL(string: urlString) else {
return
}
getThumbnailFromImage(url: url, completion: { [weak self] image in
self?.myWebImage.image = image
})
}
// Move `getThumbnailForImage` function to here, or give the cell a delegate to call back to the VC with if you don't want any networking in the view itself
}
The cellForItemAt function in the VC would need to be changed to something like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionCell
cell.configure(urlString: url[indexPath.row])
cell.myWebImage.layer.cornerRadius = 20 // This should probably live in the cell since the parent doesn't actually need to know about it!
return cell
}
An added benefit of this approach is that you're not referencing a separate array of images that could theoretically end up being in the wrong order if there's a mistake somewhere in the code. You could get rid of the webImages array entirely and use urls.count in numberOfItemsInSection instead - or eventually the number of elements returned from an API somewhere.
Side note - make sure you add [weak self] at the beginning of any closure that references self to avoid trying to access it after it's been deallocated! Currently the call to getThumbnailFromImage doesn't have that :)
Also, note that I changed to a guard statement for checking that the URL exists. This is much safer than force unwrapping a URL(string:) value, especially if you ever end up getting the strings from a dynamic source.
I am trying to create an array of images/videos that are selected from a collection view and store them using Core Data. I have an array of the selected cells and it is dynamic because it allows for the user to add or subtract images to the array by selecting or deselecting the cell. My code currently uses IndexPath to accomplish this but I believe I need to convert it to an UIImage array so that I can add it to Core Data based on another solution I saw. So how do I convert the selectedCells array below to a UIImage array?
var selectedCells: [IndexPath] = []
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? ImageCell {
selectedCells.append(indexPath)
cell.index = selectedCells.count
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
guard let idx = selectedCells.firstIndex(of: indexPath) else { return }
selectedCells.remove(at: idx)
let curSelected: [IndexPath] = collectionView.indexPathsForSelectedItems ?? []
collectionView.reloadData()
let saveY = collectionView.contentOffset.y
collectionView.performBatchUpdates({
curSelected.forEach { pth in
collectionView.selectItem(at: pth, animated: false, scrollPosition: .centeredVertically)
}
}, completion: { _ in
collectionView.contentOffset.y = saveY
})
}
UPDATE:
The reason I decided to use Core Data is because the users will need to be able to edit the images but if they stop in the middle of it and come back the users progress needs to be saved. If they close the app the progress will be lost. I no longer think I need to store the image itself, just a reference to the the image in the library. Please correct me if I am wrong.
You dont even need to hold your own selectedCells, there is a property called "indexPathsForSelectedItems" for this purpose. You are overcomplicating things with your reload strategy.
Then use those indexpaths to access your datasource like you do in cellForRow(at: indexPath).
So lets say you have a helper called func object(at indexPath: IndexPath) -> YourObjectModel
then you can "convert" the indexpath array using array's map functionality.
Additionally, an image should not end up being stored in coredata ... only a reference to it.
I would like to download multiple URLs from string in array, and display images in collectionView. I succeed to display four images (4 visible cells on my screen) and when I scroll, that begins the download of the 2 other images.
I want that these 6 images are downloaded in the same time (and I don't have to scroll for beginning the other download). After this, I want to display the total time spent to download and display images in my collectionView.
What I am doing wrong ?
Here is my code :
import UIKit
import AlamofireImage
import Alamofire
class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
var players = [
["image_Name":"https://images.unsplash.com/photo-1420768255295-e871cbf6eb81?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&s=4b94ef340bd7f6cac580fbc76af326af"],
["image_Name":"https://images.unsplash.com/photo-1465411801898-f1a78de00afc?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&s=149d27223217c0fa63c7dd8f1e8d23f6"],
["image_Name":"https://images.unsplash.com/photo-1466853817435-05b43fe45b39?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&s=a3b629b7e0c4f710ce119f219ae5b874"],
["image_Name":"https://images.unsplash.com/photo-1467404899198-ccadbcd96b91?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&s=66eef8db7a0aa4119c6c8d7ba211f79f"],
["image_Name":"https://images.unsplash.com/photo-1470322346096-ecab3914cab7?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&s=83863ba23662871baf6434c6000e00bd"],
["image_Name":"https://images.unsplash.com/photo-1473624566182-509e437512f4?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&s=54c3ec6d1fee824d62e6fa76676ddf17"]
]
var methodStart = Date()
#IBOutlet weak var mycall: UICollectionView!
var selectedIndex=[Int]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.players.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:playerCell = collectionView.dequeueReusableCell(withReuseIdentifier: "playerCell", for: indexPath) as! playerCell
let url = URL(string:self.players[indexPath.row]["image_Name"]!)
if indexPath.row == 0 {
methodStart = Date()
}
Alamofire.request(url!).responseImage { response in
// debugPrint(response)
// print(response.request)
// print(response.response)
// debugPrint(response.result)
if let image = response.result.value {
cell.playerImage.image = image
}
}
if indexPath.row == self.players.count - 1 {
let methodFinish = NSDate()
let executionTime = methodFinish.timeIntervalSince(methodStart as Date)
print("Execution time: \(executionTime)")
}
return cell
}
}
Problem in your code -
you said you want those 6 images to be downloaded at the same time and when user scrolls the 5th and 6th image should display immediately. but this is not happening because the cellForItemAtIndexPath function for the 5th and 6th cell called when you scroll. this is the default behavior and you cannot invoke this before user scrolled.
Quick fix solution -
Download all the images outside cellForItemAtIndexPath function, like in either viewDidLoad or viewDidAppear by looping the array of players and then reload the collection view after completion of download of the last one.
Permanent and best solution -
Implement lazy loading and caching of images using either Heneke or SDWebImage to display image in cells. And use prefetch option in those libraries to fetch images before they were shown in cells. To use prefetch option you need to loop through the array and pass the urls to the fetching and storing functions of the library and just load image in cell according to the syntax of that library and it will manage the displaying of image when it downloaded and you don't have to worried about that.
When you are dealing with network calls, you should update UI in main queue. One more valid point i want to add here is Alamofire has a special library for image download/resize. Please have a look at AlamofireImage Library
Alamofire.request(url!).responseImage { response in
if let image = response.result.value {
DispatchQueue.main.async {
cell.playerImage.image = image
cell.playerImage.setNeedsLayout()
}
}
}
I am using Realm as the alternative for coredata for the first time.
Sadly, I had this bumpy scrolling issue(It is not too bad, but quite obvious) for collectionView when I try Realm out. No data were downloaded blocking the main thread, I use local stored image instead.
Another issue is when I push to another collectionVC, if the current VC will pass data to the other one, the segue is also quite bumpy.
I am guessing it is because of the way I write this children property in the Realm Model. But I do not know what might be the good way to compute this array of array value (merging different types of list into one)
A big thank you in advance!!
Here is the main model I use for the collectionView
class STInstitution: STHierarchy, STContainer {
let boxes = List<STBox>()
let collections = List<STCollection>()
let volumes = List<STVolume>()
override dynamic var _type: ReamlEnum {
return ReamlEnum(value: ["rawValue": STHierarchyType.institution.rawValue])
}
var children: [[AnyObject]] {
var result = [[AnyObject]]()
var tempArr = [AnyObject]()
boxes.forEach{ tempArr.append($0) }
result.append(tempArr)
tempArr.removeAll()
collections.forEach{ tempArr.append($0) }
result.append(tempArr)
tempArr.removeAll()
volumes.forEach{ tempArr.append($0) }
result.append(tempArr)
return result
}
var hierarchyProperties: [String] {
return ["boxes", "collections", "volumes"]
}
}
Here is how I implement the UICollectionViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.alwaysBounceVertical = true
dataSource = STRealmDB.query(fromRealm: realm, ofType: STInstitution.self, query: "ownerId = '\(STUser.currentUserId)'")
}
// MARK: - datasource:
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
guard let dataSource = dataSource else { return 0 }
return dataSource.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! STArchiveCollectionViewCell
guard let dataSource = dataSource,
dataSource.count > indexPath.row else {
return cell
}
let item = dataSource[indexPath.row]
DispatchQueue.main.async {
cell.configureUI(withHierarchy: item)
}
return cell
}
// MARK: - Open Item
func pushToDetailView(dataSource: [[AnyObject]], titles: [String]) {
guard let vc = storyboard?.instantiateViewController(withIdentifier: STStoryboardIds.archiveDetailVC.rawValue) as? STArchiveDetailVC
else { return }
vc.dataSource = dataSource
vc.sectionTitles = titles
self.navigationController?.pushViewController(vc, animated: true)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let dataSource = self.dataSource,
dataSource.count > indexPath.row else {
return
}
let item = dataSource[indexPath.row]
self.pushToDetailView(dataSource: item.children, titles: item.hierarchyProperties)
}
Modification(more codes on configureUI):
// configureUI
// data.type is an enum type
func configureUI<T: STHierarchy>(withHierarchy data: T) {
print("data", kHierarchyCoverImage + "\(data.type)")
titleLabel.text = data.title
let image = data.type.toUIImage()
self.imageView.image = image
}
// toUIImage of enum data.type
func toUIImage() -> UIImage {
let key = kHierarchyCoverImage + "\(self.rawValue)" as NSString
if let image = STCache.imageCache.object(forKey: key) {
return image
}else{
print("toUIImage")
let defaultImage = UIImage(named: "institution")
let image = UIImage(named: "\(self)") ?? defaultImage!
STCache.imageCache.setObject(image, forKey: key)
return image
}
}
If your UI is bumpy when you're scrolling, it simply means the operations you're performing in collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell are too heavy.
Realm itself is structured in such a way that reading data from objects is very fast, so you shouldn't be seeing substantial dropped frames if all you're doing is populating a cell with values from Realm.
A couple of considerations:
If you're calling item.children inside the cellForItem block method, since you're manually looping through and paging in every Realm object doing that, that will cause frame drops. If you are, it'd be best to either do that ahead of time, or re-desing the logic to only access those arrays when absolutely needed.
You mentioned you're including images. Even if the images are on disk, unless you force image decompression ahead of time, Core Animation will lazily decompress the image at draw time on the main thread which can severely kill scroll performance. See this question for more info.
The cellForItemAt method call should already be on the main thread, so configuring your cell in a DispatchQueue.main.async closure seems un-necessary, and given that it's not synchronous, may be causing additional issues by running out of order.
Collection views are notoriously hard for performance since entire rows of cells used to be created and configured in one run loop iteration. This behavior was changed in iOS 10 to spread cell creation out across multiple run loop iterations. See this WWDC video for tips on optimizing your collection view code to take advantage of this.
If you're still having trouble, please post up more of your sample code; most importantly, the contents of configureUI. Thanks!
Turned out I was focusing on the wrong side. My lack of experience with Realm made me feel that there must be something wrong I did with Realm. However, the true culprit was I forgot to define the path for shadow of my customed cell, which is really expensive to draw repeatedly. I did not find this until I used the time profile to check which methods are taking the most CPU, and I should have done it in the first place.
I make a network call in ViewDidLoad to get objects (first 25), then I make another call in willDisplayCell to get the rest of the objects. I'm Using PINReMoteImage in the code below. It works, but the problem is that as you scroll through the collection view, a cell will have one picture then another picture will appear over it. How can I improve this UI? I thought updateWithProgress was supposed to deal with this by using a blur until the image is loaded but it doesn't seem to be working?
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PinCollectionViewCell
if let pinImageURL = self.pins[indexPath.row].largestImage().url {
cell.pinImage?.pin_updateWithProgress = true
cell.pinImage?.pin_setImageFromURL(pinImageURL, completion: ({ (result : PINRemoteImageManagerResult) -> Void in
if let image = result.image {
self.imageArray.append(image)
}
}))
}
The problem is that I'm reusing cells, but I wasn't resetting the image. So I simply added cell.pinImage?.image = nil to the above. Once I found out that was the problem I added an UIActivityViewIndicatorViewto the cell and stop it when the image comes in.