I have two Viewcontroller A and B. A will download all the photo in collectionview then I can click on single cell to go to Viewcontroller B which is the tableview to show only this single image that passed from A.
But its working fine when first lunch the app but once I add new photo in the collectionview then I clicked this new photo to go to Viewcontroller B but B didn't show the correct photo showing some other photo. I print out the photo data postImg in viewcronller A and B the data is the same but B is not showing the correct photo. Why? When I rebuild the program it's working the issues seems only happened when I add new photo.
Here is code to load the pic from server
func loadPosts(){
let query = PFQuery(className: "posts")
query.whereKey("username", equalTo:PFUser.current()!.username!)
query.skip = self.picArray.count // skip the already loaded images
query.findObjectsInBackground { (objects, error) in
if error == nil {
if let objects = objects{
for object in objects{
self.collectionView?.performBatchUpdates({
let indexPath = IndexPath(row: self.uuidArray.count, section: 0)
self.uuidArray.append(object.value(forKey: "uuid") as! String)
self.picArray.append(object.value(forKey: "pic") as! PFFile)
self.collectionView?.insertItems(at: [indexPath])
}, completion: nil)
}
}
} else{
print(error!.localizedDescription)
}
}
}
Then var postImgData = [UIImage]() is the array used to save all the photos that downloaded from above function and this viable is defined at beginning of CLASS Viewcontroll A.
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return picArray.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//define cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! pictureCell
picArray[indexPath.row].getDataInBackground { (data, error) in
if error == nil {
cell.picImg.image = UIImage(data: data!)
self.postImgData.append(UIImage(data: data!)!)
} else {
print(error!.localizedDescription)
}
}
return cell
}
Then i click the cell to go to viewcontroller B . postImg is defined at beginning of Viewcontroller B as global viable to get the image date that you click on Viewcontroller A cell.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
postImg = postImgData[indexPath.row]
let post = storyboard?.instantiateViewController(withIdentifier: "postVC") as! postVC
self.navigationController?.pushViewController(post, animated: true)
}
Then in Viewcotnroller B to show the photo
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! postCell
cell.picImg.image = postImg
return cell
}
First:
picArray[indexPath.row].getDataInBackground { (data, error) in
if error == nil {
cell.picImg.image = UIImage(data: data!)
self.postImgData.append(UIImage(data: data!)!) <== WHY do you append array?
} else {
print(error!.localizedDescription)
}
you should probably init array beforehand and set data like self.postImgData[indexPath.row] = UIImage(data: data!)!
That how we ensure correct order of images
2
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let post = storyboard?.instantiateViewController(withIdentifier: "postVC") as! postVC
// Why dont use post.postImg = postImgData[indexPath.row]?
// Do not use Global vars, because you can change it from anywhere without noticing
self.navigationController?.pushViewController(post, animated: true)
}
So I suggest moving postImg var to controller B
Related
I've been working with Swift and iOS for several months now. I want to download a new image when it comes to CollectionView's penultimate item. I'm downloading, but the reloadData function is constantly redrawing and collectionview is top. How can I prevent it from going to the top?
private func getRandomPhoto() {
let url = "https://source.unsplash.com/random/400x800"
do{
var a = try Data(contentsOf: URL(string: url)!)
images.append(UIImage(data: a)!)
} catch {
print("err")
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
and here is my controller view codes
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ImageCollectionViewCell
cell.imageView.image = images[indexPath.row]
return cell
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
for cell in collectionView.visibleCells {
let indexPathh = collectionView.indexPath(for: cell)
if indexPathh?.row == images.count-1 {
let url = "https://source.unsplash.com/random/400x800"
getRandomPhoto()
}
}
}
}
I made a small video for the problem. I hope it's clear.
https://github.com/Egemenclr/StajCase/blob/master/Örnek/problem.gif
Don't reload the whole table, instead insert rows for newly downloaded images
insertItems(at indexPaths: [IndexPath])
suggestion: Don't download the same image only again and again. Download initially and cache it with NSCache. Try to fetch from cache when you need the image again.
I am using a UICollectionView inside UITableViewCell. I am able to select the cells inside the UICollectionView. But when i try to get the UICollectionView or selected cells, the result is always null.I have been stuck on this for a long time. i included my code below for your reference.
class WeekDaysSelCell: UITableViewCell,UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
var weekdays = ["S", "M", "T", "W", "T", "F", "S"]
var weekdaysSelected = [String]()
#IBOutlet var weeklyDaysColView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
self.weeklyDaysColView.delegate = self
self.weeklyDaysColView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : WeekDaysCollCell = weeklyDaysColView.dequeueReusableCell(withReuseIdentifier: "weekday", for: indexPath) as! WeekDaysCollCell
cell.weekDayLabel.text = weekdays[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell : WeekDaysCollCell = self.weeklyDaysColView.cellForItem(at: indexPath) as! WeekDaysCollCell
if (cell.backgroundColor == UIColor.gray) {
cell.backgroundColor = UIColor.clear
weekdaysSelected.removeAll { $0 == String(indexPath.row)}
//print("Removed from weekdaysSelected:", indexPath.row)
} else {
cell.backgroundColor = UIColor.gray
cell.isSelected = true
//weeklyDaysColView.selectItem(at: indexPath, animated: true, scrollPosition: [])
weekdaysSelected.append(String(indexPath.row))
//print("Added to weekdaysSelected:", indexPath.row)
}
}
}
// Trying to get the collection view from inside a willMove(toParent parent: UIViewController?) method.
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil
{
if let delegate = self.delegate {
print("Inside If condition")
// Code that i use to get the cell
let cell3 = tableView.dequeueReusableCell(withIdentifier: "cell3") as! WeekDaysSelCell
print(cell3.weekdaysSelected)
print(cell3.weeklyDaysColView.indexPathsForSelectedItems)
// Trying to pass selected cells
//delegate.repeatCustomSelection(selectedIdx: String(lastSelection.row),repeatCustomSel: repeatCustomSelection)
}
}
}
You are trying to get a reusable cell in willMove(toParent parent: UIViewController?) , this is not going to return you a expected cell.
You need to get the cell , using a indexPath .
func cellForRow(at indexPath: IndexPath) -> UITableViewCell?
#andyPaul, is right you are generating the new cell in willMove(toParent parent: UIViewController?). Instead of that you have to pass the indexpath pf collection view when ever user selected any cell to your controller from the tableView Cell Class.
Now What is TypeAlias you can read from this link about type alias:- https://www.programiz.com/swift-programming/typealias
Create the typeAlias on above of your tableViewCell Class like this:-
typealias closureBlock = (_ isCapture : AnyObject?) ->()
class tableViewCellClass: UITableViewCell {
var callBack: closureBlock?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
Just Go to CollectionView didSelectItemAt Method and use this code after your coding
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell : WeekDaysCollCell = self.weeklyDaysColView.cellForItem(at: indexPath) as! WeekDaysCollCell
if (cell.backgroundColor == UIColor.gray) {
cell.backgroundColor = UIColor.clear
weekdaysSelected.removeAll { $0 == String(indexPath.row)}
//print("Removed from weekdaysSelected:", indexPath.row)
} else {
cell.backgroundColor = UIColor.gray
cell.isSelected = true
//weeklyDaysColView.selectItem(at: indexPath, animated: true, scrollPosition: [])
weekdaysSelected.append(String(indexPath.row))
//print("Added to weekdaysSelected:", indexPath.row)
}
guard let callBackClosure = self.callBack else {
return
}
callBackClosure(indexPath as AnyObject)
// You can pass any value here either indexpath or Array.
}
}
Now you have to initialise this closure so that it can check whether in which controller it will return the value when you assign the value from CollectionView didSelectItemAt Method.
Go to your ViewController Class where you have added the tableview and their datasources.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//You have to pass your tableview Cell Instance here and their reuse Identifier
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCellClass", for: indexPath) as! tableViewCellClass
cell.callBack = { [weak self] (selectedIndexPath) -> ()in
// You will get the current selected index path of collection view here, Whenever you pass any index path from collectionView did SelectItem Method.
print(selectedIndexPath)
}
return cell
}
In my project CollectionView in TableViewCell isn't display and I added
cell.collectionView.reloadData()
in my code. After I added, CollectionView displayed in TableViewCell, but
ScrollView isn't smooth. How to solve this problem. If someone have any experience or ideas help me. Thanks.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "homecell") as! HomeCategoryRowCell
let mainThemeList = mainHomeThemeTable[(indexPath as NSIndexPath).row]
DispatchQueue.main.async {
cell.categoryTitle.text = mainThemeList.main_name
cell.collectionView.reloadData()
}
return cell
}
CollectionView in my project,
extension HomeCategoryRowCell : UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
debugPrint("assetsTable count \(assetsTable.count)")
return assetsTable.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "videoCell", for: indexPath) as! HomeVideoCell
let list = assetsTable[(indexPath as NSIndexPath).row]
let url2 = NSURL(string:list.poster_image_url)
let data2 = NSData(contentsOf:url2! as URL)
// debugPrint(list.poster_image_url)
DispatchQueue.main.async(){
cell.movieTitle.text = list.name
cell.imageView.image = UIImage(data: data2! as Data)
}
return cell
}
}
Download Image Url Data in collectionView: UICollectionView, cellForItemAt indexPath: IndexPath delegate - ASYNCHRONOUSLY
Create a Class
First cache the image Data with respect to key Image URL
Why do we need to cache the data?
Because - While scrolling we don't need to download Image Every Time so Cache the already downloaded Image Data and return the cache data
I created a class name: AsyncImageLoader then I created a function which is a completion handler which returns the data after completion of downloading Image
class AsyncImageLoader {
static let cache = NSCache<NSString, NSData>()
class func image(for url: URL, completionHandler: #escaping(_ image: UIImage?) -> ()) {
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
if let data = self.cache.object(forKey: url.absoluteString as NSString) {
DispatchQueue.main.async { completionHandler(UIImage(data: data as Data)) }
return
}
guard let data = NSData(contentsOf: url) else {
DispatchQueue.main.async { completionHandler(nil) }
return
}
self.cache.setObject(data, forKey: url.absoluteString as NSString)
DispatchQueue.main.async { completionHandler(UIImage(data: data as Data)) }
}
}
}
How to use AsyncImageLoader Class?
See below How I used in collectionView: UICollectionView, cellForItemAt indexPath: IndexPath
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let yourCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionCell", for: indexPath) as! CustomCollectionCell
yourCell.url = URL(string: "\(item_ImageUrl)")
return yourCell
}
CustomCollectionCell
class CustomCollectionCell: UICollectionViewCell {
#IBOutlet weak var cellImageDisplayView: UIImageView!
var url: URL? {
didSet {
if let url = url {
cellImageDisplayView.image = nil
AsyncImageLoader.image(for: url, completionHandler: { (image) in
DispatchQueue.main.async {
if let img = image {
self.cellImageDisplayView.image = img
}else{
let dummyImage = UIImage(named: "img_u")
self.cellImageDisplayView.image = dummyImage
}
}
})
}else{
let dummyImage = UIImage(named: "img_u")
self.cellImageDisplayView.image = dummyImage
}
}
}
}
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
After I added above code , my project become more smooth. I'll try the best and back with the best solution. Thanks
This is because you are directly getting data from url and then image from data in main thread. You should use image downloading library to download the image from url
Here is the good third party to download image from url asynchronously.
https://github.com/rs/SDWebImage
Try with this I am 100% sure it will solve your problem.
Thanks
How to hide the cell collectionview in section if json data is nil. im loading collectionview in a tableview cell.my code is belowim showing array of images in collection view i have used third party for horizontalautomatic scroll as third party for collection view
func setCollectionData(_collectionData: [Any]) {
self.collectionData = _collectionData as NSArray
collectionViewObj.setContentOffset(CGPoint(x:0,y:0), animated: true)
for i in 0 ..< collectionData.count {
self.strings = StringClass()
self.strings.vehicleImage = (collectionData[i] as AnyObject).value(forKey: "img_name") as? String
self.strings.vehicleTitle=(collectionData[i] as AnyObject).value(forKey: "p_name") as? String
self.modelArray.append(self.strings)
}
collectionViewObj.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
if self.collectionData.count >= 4 {
return 4
}
else
{
return collectionData.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ProductCollectionViewCustomCell", for: indexPath)as! ProductCollectionViewCustomCell
strings = modelArray[indexPath.row]
cell.ProductTitleLabel.text = strings.vehicleTitle
let URLBaseString = "http://vehiclebuzzzz.com/"
let url = URL(string:URLBaseString .appending(strings.vehicleImage!))
let dataurl = try? Data(contentsOf: url!)
cell.productImageViewObj.image=UIImage(data: dataurl!)
return cell
}
func collectionView(_ collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout,sizeForItemAt indexPath: IndexPath) -> CGSize
override this method and return zero size CGSize.zero if the json data is nil else return actual size of cell
OR
remove nil data from array and call collectionView.reloadData()
Update the collectionData array with the new data & then perform collectionViewObj.reloadData()
-If you does not want show collection view. just hide the collection view until you get the data.
-When you data make it visible
For some reason, my collectionViewCell's are unrecognized when they are selected. It's not until another cell is touched afterwards that the previous cell is recognized. To explain how I realized this, I added the following code to my collectionView's didDeselectItemAtIndexPath method: println("user tapped on cell # \(indexPath.row)"). When I run the app and select a cell, my console doesn't respond until I tap another cell, then it reads the println I added. For instance, if I select the first cell, the debugger doesn't print anything until i select another cell, then the console reads "user tapped on thumbnail # 0".
Now I've added an animation to my collectionView that enlarges each cell on selection, so this is how I know it isn't an indexPath issue because the cell indexPath # that is printed in the console is the correct cell that is enlarged in the view, but like i said, the cell isn't animated on its selection, not until i select another cell afterwards.
Here is my collectionView delegate and dataSource logic:
// MARK: UICollectionViewDataSource
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.books.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! MyCollectionViewCell
// Configure the cell
let book = self.books[indexPath.row]
let coverImage = book.coverImage
if coverImage == nil {
book.fetchCoverImage({ (image, error) -> Void in
if self.collectionView != nil {
collectionView.reloadItemsAtIndexPaths([indexPath])
}
})
} else {
let imageView = cell.imageView
imageView.image = book.coverImage
}
return cell
}
override func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! MyCollectionViewCell
let book = self.books[indexPath.row]
self.selectedImageView = cell.imageView
if !isModeModal {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("DetailViewController") as! DetailViewController
controller.imageSelected = book.coverImage
self.navigationController?.pushViewController(controller, animated: true)
}
println("user tapped on thumbnail # \(indexPath.row)")
}
}
Why is this behavior occurring?
I didn't run your code, but I think, that the problem could be caused by didDeselectItemAtIndexPath. Try to use didSelectItemAtIndexPath instead.
If necessary you can add deselectItemAtIndexPath :
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
collectionView.deselectItemAtIndexPath(indexPath: NSIndexPath, animated: Bool)
}