I have a collection view with custom cells. I want to populate cells with images by fetching data from two places. One is default image array and another is array which is fetched after parsing xml web services. So the final array count is summation of default array count and array count of web services.
How to fetch the images in cellForItemAt method of collection view?
can Anyone help me out in this regard?
From above Question
let cell:SubCategoryCollectionViewCell = self.collectionview3.dequeueReusableCell(withReuseIdentifier: "Cell2", for: indexPath) as! SubCategoryCollectionViewCell
if defaultAnimalArray.count-1 >= indexPath.row{ // ex: 0...7
let item = defaultAnimalArray[indexPath.row]
cell.subThumbImg?.image = UIImage(named: item as! String)
}else{
//If now defaultAnimalArray.count = 8, indexPath = 8 , But array = 0...4, then,
let item = arrayFromAPI[indexPath.row-defaultAnimalArray.count] // 8-8 = 0
cell.subThumbImg?.image = UIImage(named: item as! String)
}
return cell
Alternative 1:-
Declare defaultArray as var.
Before return defautlArray.count+arrayFromAPI.count merge image array also . i.e., defaultArray = defaultArray+arrayFromAPI and return variable becomes return defaultArray.count as well as no changecellForItemAtIndexPath.
Alternative 2:
Declare defaultArray as var and Don't create arrayFromAPI. Just append new images in defaultArray in XML Parsing. After appending a new image reload UICollectionView and also return variable becomes return defaultArray.count
Alternative 3:
With the same code , the item on cellForItemAtIndexPath becomes,
let item = defaultAnimalArray+arrayFromAPI[indexPath.row]
set
collectionView.delegate = nil
collectionView.dataSource = nil
Until you get all your images from xml web services. When you get all your data then update your dataSource array like you said
count = default array count + array count from web services
and then set delegate and dataSource of collectionView. Then in
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "identifierCell", for: indexPath) as! UICollectionViewCell
/// Show Image Code here
let url : String = dataSource[indexPath.row]
if let _image = UIImage(named: url){
// This If Will Load the image if it save locally
} else {
// Image does not save locally, download image from URL
}
return cell
}
I have update my answer please look and tell me if you found anything difficult.
One thing you can use SDWebImage third party to download image from URL.
Related
I am using the collection view to show the gif's on the list.
Now facing the cell reusable issue while scrolling the cells up or down of collection view.
Like itemA is on first place in the list and itemB is on the second place in the list.
but when I scroll the data in the collection view. the places of items got misplaced. like some time itemA gone on 5th place or sometimes anywhere in the list.
i know i think this is the use with reusable cell, but don't know how to salve this.
Plss help.
Collection view cellForItemAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GifCell", for: indexPath as IndexPath) as? GifCell else {
fatalError()
}
if gifArr.count > 0 {
let urlString = self.gifArr[indexPath.row]
let url = URL(string: urlString)!
DispatchQueue.global().async {
let imageData = try? Data(contentsOf: url)
let imageData3 = FLAnimatedImage(animatedGIFData: imageData) // this is the 3rd pary library to show the gifs on UIimageview's
DispatchQueue.main.async {
cell.imageView.animatedImage = imageData3
cell.textLabel.text = String(indexPath.row)
}
}
}
return cell
}
In GifCell you could implement prepareForReuse() method:
Performs any clean up necessary to prepare the view for use again.
override func prepareForReuse() {
super.prepareForReuse()
imageView.animatedImage = nil
textLabel.text = ""
}
Note:
at this point, each time cellForItemAt method gets called, the url will be reloaded, so later, you might want find a way to cache the images instead of keep reloading them.
First solution: You can cache data and every time check if there is, use your cache.
you can use this link, but replace UIImage with gift type!
or
try this, I did not test it
if let giftAny = UserDefaults.standard.value(forKey: "giftUrl") {
//cast giftAny to Data
// use cached gift
} else {
// cache gift
let giftData = try? Data(contentsOf: url)
UserDefaults.standard.setValue(giftData, forKeyPath: "giftUrl")
//use gift
}
Second Solution: Don't reuse cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = UICollectionViewCell(style: .default, reuseIdentifier:"Cell")
return cell
}
but in this case, if you have many cells, memory leak is unavoidable.
I have a CollectionViewController in which I contact an API to download images based on a set of coordinates. I call this code in the cellForItemAt function at which time it updates the cell's images in realtime with images from Flickr. This works fine.
However, when scrolling up or down, it recalls this code and updates the cells again, when I'd prefer that it look at the existing cells, identify if they have been filled, and simply not run this code.
I have tried implementing logic before the networking code that checks to see if the imageView.images already exist in a local struct I assign them to, but that doesn't seem to work correctly.
Is there a simple method to tell cellForItemAt "for cells where you already have images, don't look for more"?
Here is my current code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath as IndexPath) as! CollectionViewCell
// Get images = using the URL
FlickrClient.sharedInstance().getImagesFromFlickr(latitude: selectedPin.lat, longitude: selectedPin.lon, page: pageCount) { (pin, error) in
if let pin = pin {
let url = pin.images[indexPath.item].imageURL
let data = try? Data(contentsOf: url)
performUIUpdatesOnMain {
cell.imageView.image = UIImage(data: data!)
cell.imageView.contentMode = .scaleAspectFill
}
}
}
return cell
}
Use SDwebImage libray for loading images from url.
https://github.com/rs/SDWebImage
Call Something like this on cell for row :
let url = pin.images[indexPath.item].imageURL
cell.imageView.sd_setImage(with: url, placeholderImage: UIImage(named: "placeholder.png"))
I am working on an app that can merge multiple single page pdfs into one multiple page pdf and I have them displayed in a UICollectionView. I put the contents of the .documentDirectory into a [String] for setting the data source for the UICollectionView. Everything displays fine but I am having trouble passing each file URL into an array that I can use in a function I modified for my use to merge the documents. I tried passing them with the use of
let indexPath = self.collectionView.indexPathForSelectedItem
This isn't working since it is only passing the indexPath Int values, as well this has casting issues.
This is the function for merging the multiple selected pdfs.
func joinPDF(_ listOfPaths: [Any]) {
var pdfPathOutput = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
pdfPathOutput = pdfPathOutput.appending("/iScan_pdf_\(Int(Date().timeIntervalSince1970)).pdf")
let pdfURLOutput: CFURL? = (URL(fileURLWithPath: pdfPathOutput) as CFURL?)
var numberOfPages: Int = 0
// Create the output context
let writeContext = CGContext(pdfURLOutput!, mediaBox: nil, nil)
for source in listOfPaths {
let pdfURL: CFURL? = (URL(fileURLWithPath: source as! String) as CFURL?)
//file ref
let pdfRef: CGPDFDocument? = CGPDFDocument(pdfURL!)
numberOfPages = pdfRef!.numberOfPages
// Loop variables
var page: CGPDFPage? = nil
var mediaBox = CGRect.zero
// Read the first PDF and generate the output pages
// Finalize the output file
print("GENERATING PAGES FROM PDF 1 (%#)...")
for i in 1...numberOfPages {
page = pdfRef?.page(at: i)
mediaBox = page!.getBoxRect(.mediaBox)
writeContext!.beginPage(mediaBox: &mediaBox)
writeContext!.drawPDFPage(page!)
writeContext!.endPage()
}
}
writeContext!.closePDF()
}
I just need a little help putting the selected cells file URLs into an array. I created the UICollectionView datasource like so.
titles = try FileManager.default.contentsOfDirectory(atPath: documentsDirectories)
titles is a [String] then I have another var called images it is a [UIImage]. I run for in loop to get the images from the titles and then set the datasource like so
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
cell.imageView.image = images[indexPath.item]
I have a function that creates a thumbnail image from the pdfs to display them in the imageView In function didSelectItemAt I have it set up like so.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
if selected == true {
let cell = collectionView.cellForItem(at: indexPath)
cell?.layer.borderWidth = 2.0
cell?.layer.borderColor = UIColor.red.cgColor
}
}
This is where I need to enter the items into an array just not sure how to go about doing that.
Basic logic should be something like this.
you make an arrayOfUrls to hold selected page url
when user select or deselects add or remove urls through arrayOfUrls
when user clicks done button call join with arrayOfUrls
There are better ways but with above info I can help you this much.
Hope this helps...
Updated answer after question update
Having multiple arrays defeats the reason of OOP.. below is the way to do this
make a class with needed variables…say you need image, urlOfPDF and isSelected…
class PDFPage : NSObject {
var title : String = ""
var imageURL : String = "" //you can use uiimage object instead
var urlOfPDF : NSNumber = -1
var isSelected : Bool = false
}
before you reload data in collection view do the mapping and add all objects of PDFPage in an array (say allPDFArray)…
something like this…
for (int i = 0, i<titles.count, i++){
var page: PDFPage = PDFPage()
page.title = titles[i]
page.imageURL = set image for above title[i] here
page.urlOfPDF = set url for above title[i] here
page. isSelected = false
allPDFArray.add(page)
}
now you have all you need locally
in cellForItemAt
let page = allPDFArray[indexpath.row] //now you have your all data in page obj
cell.imageView.image = page.image //or image url
cell.title.text = page.title
cell.isSelected can be used to highlight cell…you will need it as reloading cell will show you wrong results when you scroll
Similarly in didSelectItemAt
let page = allPDFArray[indexpath.row]
page.isSelected = page.isSelected
if page.isSelected then highlight
once user clicks done…
just go through all elements of allPDFArray and see which are selected and make an array here which you can send to joinPDF
I have the following code:
var posts = [EventPosts]() {
didSet {
eventsCollectionView.reloadData()
}
}
//MARK:PLACEHOLDER IMAGES
var eventImagesPlaceholder: [UIImage] = [
UIImage(named: "wildstyle.jpg")!,
UIImage(named: "geilesleben.jpg")!]
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let event = posts[indexPath.row]
let cell = eventsCollectionView.dequeueReusableCellWithReuseIdentifier("eventsCell", forIndexPath: indexPath) as! EventsCollectionViewCell
for element in eventImagesPlaceholder {
cell.eventsImageView.image = element
}
if Reachability.isConnectedToNetwork() == true {
offlineModusLabel.hidden = true
let imgURL = NSURL(string: event.imageUrl)!
cell.eventsImageView.sd_setImageWithURL(imgURL)
activityIndicator.stopAnimating()
} else {
offlineModusLabel.hidden = false
}
return cell
}
If I turn the internet on, four images from the desired source will be parsed and correctly displayed. If I turn the internet off, just the last placeholder image will be displayed 4 times. If I set wildstyle.jpg last, it is displayed four times. If I set geilesleben.jpg last, only that one is.
How can I display BOTH placeholder images. optimally only one time each.
Help is very appreciated.
You could use this, the two placeholder images are displayed in all cells alternately.
If you add more placeholder images to the array, the number of items will be considered automatically.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let event = posts[indexPath.row]
let cell = eventsCollectionView.dequeueReusableCellWithReuseIdentifier("eventsCell", forIndexPath: indexPath) as! EventsCollectionViewCell
if Reachability.isConnectedToNetwork() == true {
offlineModusLabel.hidden = true
let imgURL = NSURL(string: event.imageUrl)!
cell.eventsImageView.sd_setImageWithURL(imgURL)
activityIndicator.stopAnimating()
} else {
offlineModusLabel.hidden = false
cell.eventsImageView.image = eventImagesPlaceholder[indexPath.row % eventImagesPlaceholder.count]
}
return cell
}
In your code always the last image is displayed because of the repeat loop which assigns all images to the same image view and keeps the last image.
Add
override func prepareForReuse() {
self.eventsImageView.image = nil
}
in your cell's class
for element in eventImagesPlaceholder {
cell.eventsImageView.image = element
}
change this loop statment to like this
let element = eventImagesPlaceholder.objectAtIndex(indexPath.row) as! UIImage
cell.eventsImageView.image = element
for element in eventImagesPlaceholder {
cell.eventsImageView.image = element
}
This iterates for every cell you have. Basically every cell gets every image and keeps the last. pretty straight forward
What you want to do is sth like this:
cell.eventsImageView.image = eventImagesPlaceholder[someKindOfIndex]
Instead of that for loop.
Like when you know you have 4 cells and 4 images in your placeholder variable, just take indexPath.row as "someKindOfIndex"
I am making an iOS app in Swift and I am running into a obstacle I seem to be stuck on. I have a collectionview populated by an string array, which are the names of the images I am populating the image within the collectionview cells:
var tableData: [String] = ["cricket1.png", "cricket1.png", "cricket1.png"]
I've linked up the images to the collectionview with the following code:
//How many cells there are is equal to the amount of items in tableData (.count property)
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tableData.count
}
//Linking up collectionView with tableData
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: CricketCell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CricketCell
cell.imgCell.image = UIImage(named: tableData[indexPath.row])
return cell
}
When I have the user tap the cell, the image goes from cricket1.png to cricket2.png:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("Cell selected")
var cell = collectionView.cellForItemAtIndexPath(indexPath) as! CricketCell
if cell.imgCell.image == UIImage(named:"cricket1.png"){
var cell = collectionView.cellForItemAtIndexPath(indexPath) as! CricketCell
cell.imgCell.image = UIImage(named:"cricket2.png")
}
Now.. here is where I am having trouble. I am currently trying to save data in tableData, however when I do, it always saves it as ["cricket1.png", "cricket1.png", "cricket1.png"]. Even if the image has been tapped and changed to "cricket2.png". Even if all the images on the screen is cricket2.png, when I save tableData, it saves it as ["cricket1.png", "cricket1.png", "cricket1.png"]. I am aware it is because I am storing the variable tableData I declared earlier, but is there any way I can grab a string array of what is on the screen/the current state of the collectionview?
Any help would be appreciated!
Thank you!
You need to update the data in the array yourself. It's independent from image.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("Cell selected")
//1: Get the index for which data in array you need to update
let index = indexPath.row
//I don't think this comparison may server your purpose. When you new an UIImage object, it's a different one than the original image. You may want to just compare the data
let unSelectedImage = "cricket1.png"
if self.tableData[index] != unselectedImage {
//2: Update data in array
var cell = collectionView.cellForItemAtIndexPath(indexPath) as! CricketCell
let selectedImageName = "cricket2.png"
self.tableData[index] = selectedImageName
cell.imgCell.image = UIImage(named: selectedImageName)
}
}
So in this case, even you refresh the table, image will loading according to the new tableData.
I would recommend that u use another property to hold the state of the cell. For example,
var selectedState = [true, false, true, true, true]
Upon tapping the image, update the image and selectedState array accordingly.
For example,
if selectedState[indexPath.row] {
cell.imgCell.image == UIImage(named:"cricket1.png")
else {
cell.imgCell.image = UIImage(named:"cricket2.png")
}
Once you are ready to grab the state of the tapped cells, you can use the selectedState array variable.
You can't use the cell to persist any state as the cell can get dequeued by collectionView and may lost its state as you scroll.
Hope this helps.