preview page for uploaded image - swift

I am making a multiple images upload process; I want user to preview their chose images and add some description for images. In here, I make a collection view inside a table view, I want the images show in collection view cell, but I don’t know what I should code to call the images that user picked.
This is image picker controller
#objc func btnClick() {
let picker = YPImagePicker(configuration: config)
picker.didFinishPicking { [unowned picker] items, _ in
if let photo = items.singlePhoto {
print(photo.fromCamera) // Image source (camera or library)
print(photo.image) // Final image selected by the user
print(photo.originalImage) // original image selected by the user, unfiltered
print(photo.modifiedImage) // Transformed image, can be nil
print(photo.exifMeta) // Print exif meta data of original image.
}
picker.dismiss(animated: true, completion: nil)
}
present(picker, animated: true, completion: nil)
picker.didFinishPicking { [unowned picker] items, cancelled in
for item in items {
switch item {
case .photo(let photo):
print(photo)
case .video(let video):
print(video)
}
}
picker.dismiss(animated: true, completion: nil)
}
}
performSegue(withIdentifier: "UploadTableViewController", sender: nil)
here is in the tableView controller
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("UploadCollectionViewCell",
forIndexPath: indexPath)
cell.image?.UIImageView =
return cell
}

I think you have to assign the image to your collection view cell in cellForItemAt:indexPath. So do something like:
cell.imageView.image = items[indexPath.row]
What else is missing is that once you have picked an image you have to update the collectionView. So just reload it by calling:
collectionView.reloadData()
Hope it helps.

In CellForRowAt, it is necessary to set the corresponding Image in UIImageView of cell.
cell.image = imageData[indexPath.item]
imageData is a group of Image data that User can select.
Then reload collectionView at any time.
collectionView.reloadData()
example code.
func collectionView(collectionView: UICollectionView,
            cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  let cell = collectionView.dequeueReusableCellWithReuseIdentifier("UploadCollectionViewCell",
forIndexPath: indexPath)
  cell.image = imageData[indexPath.item]
  // It works with either item or row. But for CollectionView, .item is preferable.
  collectionView.reloadData()
  return cell
}

Related

Swift TableView data only loading after scrolling to bottom

I have a code where I am fetching images from URLs and displaying them in a tableview, however the images only show up inside tableview when scrolling down to far bottom of the screen, as show in the image below.
TableView before scrolling to bottom, screenshot
TableView after scrolling to bottom, screenshot
Code for the fetching images and tableView
#IBOutlet weak var tableView: UITableView!
let imageArray = [UIImage()]
let urlArray = ["https://media.api-sports.io/football/teams/50.png","https://media.api-sports.io/football/teams/47.png","https://media.api-sports.io/football/teams/49.png","https://media.api-sports.io/football/teams/46.png","https://media.api-sports.io/football/teams/48.png"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: "ImageCell", bundle: nil), forCellReuseIdentifier: "imageCell") // Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! ImageCell
cell.clubImage.load(url: URL(string: urlArray[indexPath.row])!)
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return urlArray.count
}
}
extension UIImageView {
func load(url: URL) {
DispatchQueue.global().async { [weak self] in
if let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
self?.image = image
}
}
}
}
}
}
How can I show the images as soon as the app loads?
Thanks
The issue is that the table view is calculating the size of the cell before an image has been loaded. The image view in the cell defaults to a height of zero since it has no content yet. I'd suspect something in the table view implementation causes the cells sizes to be recalculated when reaching the bottom which allows them to appear.
One solution could be to add a constraint to the image view so that it has a fixed size before an image is loaded. The simplest is if all cells can have the same size. If they need different sizes, that would need to be set in cellForRowAt either before loading the image, or the image loading would need to be moved so it's done outside of the cell allowing the cell to be reloaded with the correct size later.
Make your load method with completion:
func load(url: URL, completion: #escaping (UIImage?) -> Void) {
DispatchQueue.global().async { in
if let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
completion(image)
}
}
}
}
}
Then in cellForRawAt call the method and add your loaded imaged to your imageView:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! ImageCell
cell.clubImage.load(url: URL(string: urlArray[indexPath.row])!) { image in
// display your image here...
}
return cell
}

Populate UICollectionView with images from CoreData

I am trying to populate my UICollectionView with my data from CoreData database. The problem is that I want to show a photo in Collection Cell and using data to create UIImage - this task can take a while. With the current solution the images are loaded approx. after 3 seconds but all the other data is already shown in collection view.
How should I add the loading overlay and know when all the images are ready to hide it, or what is correct approach?
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.reuseIdentifier, for: indexPath) as? LocationCollectionViewCell {
cell.name.text = viewModel.locations[indexPath.row].name
cell.unlocked.text = viewModel.locations[indexPath.row].unlocked ? "Unlocked" : "Locked"
if let data = viewModel.locations[indexPath.row].image {
DispatchQueue.global(qos: .background).async {
let image = UIImage(data: data)
DispatchQueue.main.async {
cell.image.image = self.viewModel.locations[indexPath.row].unlocked ? image : image?.grayscale()
}
}
} else {
cell.image.image = viewModel.locations[indexPath.row].unlocked ? UIImage(named: "noun_Akropolis_403786") : UIImage(named: "noun_Akropolis_403786")?.grayscale()
}
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.reuseIdentifier, for: indexPath)
return cell
}
}
So #peter you can try follow things:
You can add loader in cell and show/hide it when image is nil or not.
For that create an array where when any image is loaded just append that index there. Until array doesnt contain all index show loader in screen else hide loader.
For handling error you can simply use try catch. If image is not loading or it is falied then you shouldn provide dummy or placeholder image. So that loader will be removed at one time.

CollectionViewCell: Access to data of a cell through indexPath?

What I have:
I have a CollectionViewCell as .xib + .swift files.
Every cell gets their data from the database.
Inside every cell I have a like button.
What I want:
When I press the like button of a certain cell, I want to be able to read this data so I can change it and write the new data in the Database. So I want to change the like attribute of the dataset of a certain cell and save it in the DB
What I tried:
I have the indexPath of the cell but how can I read the data of the cell?
#IBAction func likeButton(_ sender: UIButton) {
var superview = self.superview as! UICollectionView
let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to:superview)
let indexPath = superview.indexPathForItem(at: buttonPosition)
print(superview.cellForItem(at: indexPath!))
// Change picture
if sender.currentImage == UIImage(systemName: "heart.fill") {
sender.setImage(UIImage(systemName: "heart"), for: .normal)
} else {
sender.setImage(UIImage(systemName: "heart.fill"), for: .normal)
}
}
UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.identifier,
for: indexPath) as! MyCollectionViewCell
cell.backgroundColor = .white
let newShoeCell = shoesArray?.randomElement()
// Fill cells with data
cell.imageView.image = UIImage(named: newShoeCell!.imgName)
cell.shoeTitle.text = newShoeCell!.title
cell.price.text = String(newShoeCell!.price)
cell.ratingNumberLabel.text = String(newShoeCell!.meanRating)
cell.floatRatingView.rating = newShoeCell!.meanRating
if newShoeCell!.liked {
cell.likeButtonOutlet.setImage(UIImage(systemName: "heart.fill"), for: .normal)
} else {
cell.likeButtonOutlet.setImage(UIImage(systemName: "heart"), for: .normal)
}
return cell
}
You need to change your thinking. It is not the data "of a cell" A cell is a view object. It displays information from your data model to the user, and collects input from the user.
You asked "...how can I read the data of the cell?" Short answer: Don't. You should be saving changes into your data model as you go, so once you have an index path, you should use it to index into your data model and get the data from there.
You need to figure out which IndexPath the tapped button belongs to, and fetch the data for that IndexPath from your data model.
If you look at my answer on this thread I show an extension to UITableView that lets you figure out which IndexPath contains a button. Almost the exact same appraoch should work for collection views. The idea is simple:
In the button action, get the coordinates of the button's frame.
Ask the owning table/collection view to convert those coordinates to
an index path
Use that index path to fetch your data.
The only difference is that for collection views, the method you use to figure out which IndexPath the button maps to is indexPathForItem(at:) instead of indexPathForRow(at:)
I suggest you add a property to your cell
class MyCell: UITableViewCell {
var tapAction: (() -> Void)?
...
#IBAction func likeButton(_ sender: UIButton) {
tapAction?()
...
}
}
and set it in view controller
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.tapAction = { [weak self] in
// call to database or whatever here and then reload cell
tableView.reloadRows(at: [indexPath], with: .automatic)
...
}
In general, cell should never care what its indexPath is, nor should it make calls to superview

Prototype cell + image + delegate

So this is what happened: I created a tableView and delegated some stuff to it from another controller. The textLabels from the cells are working fine, but whenever I add cell.imageView?.image, all cells change to that specific image.
Any ideas on how to fix it? The textLabels are static but the images are not.
Here I receive the image to the tableView:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell!
let row = indexPath.row
let titulo = arrayInfo[row]
let image = imgR
cell.textLabel!.text = titulo
cell.imageView?.image = image
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "add") {
let view = segue.destinationViewController as! EsseVaiPassar
view.delegate = self
view.delegateimg = self
}
}
and here I pass it from the controller to the other one with the tableView:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage
imagemRecebida.contentMode = .ScaleAspectFit
imagemRecebida.image = pickedImage
delegateimg?.passImage(pickedImage!)
print("IMAGE PICKED: \(pickedImage)")
dismissViewControllerAnimated(true, completion: nil)
}
func imagePickerControllerDidCancel(picker: UIImagePickerController) {
dismissViewControllerAnimated(true, completion: nil)
}
The code is working as intended. For your textview, you are asking the value arrayInfo[row]. So for each cell as the indexPath.row changes the respective element from the array is displayed in the text field.
But for your image, it is just a single image and not an array. You aren't using indexPath.row to select a particular image.
To add different images to your tableView, just create an array of image like you have created an array of String. Just implement the logic you have implemented for the textView cell.imageView?.image = image[indexPath.row]
To add image only to the first cell and leave the remaining cells blank use this
if indexPath.row == 0
{
cell.imageView?.image = image
}
In `cellForRowAtIndexPath' you have this code:
...
let image = imgR
cell.textLabel!.text = titulo
cell.imageView?.image = image
...
You are assigning imgR to the image constant, then you are assigning image to imageView?.image, so every cell will get the image stored in the variable imgR.
If you have multiple images, you need to put logic to pull the correct image for the correct row (usually an array of items).

UICollectionView Not Updated with custom UIViewController

I created a custom UIViewController and extended it to implement: UICollectionViewDataSource, UICollectionViewDelegate. In story board, I added UICollectionView and made reference from my controller to this UICollectionView (and setup the delegates for data source and collection view delegate).
I obviously implemented the minimal requirements for these 2 delegates. Now, since i sometimes asynchronously load images, I call cell.setNeedsLayout(), cell.layoutIfNeeded(), cell.setNeedsDisplay() methods after image download done (cell.imageView.image = UIImage(...)). I know all these methods are called. However, the images on the screen were not updated.
Did I miss something? Thank you!
Edit: Add Sample code - how update was called -
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MyCollectionViewCell
....
let cellImage = ServiceFacade.sharedInstance.getImageFromCaching(image!.url)
if cellImage == nil {
// set image to default image.
cell.cellImage.image = UIImage(named: "dummy")
ServiceFacade.sharedInstance.getImage(image!.url, completion: { (dImage, dError) in
if dError != nil {
...
}
else if dImage != nil {
dispatch_async(dispatch_get_main_queue(),{
let thisCell = self.collectionView.dequeueReusableCellWithReuseIdentifier(self.reuseIdentifier, forIndexPath: indexPath) as! MyCollectionViewCell
thisCell.cellImage.image = dImage
thisCell.setNeedsLayout()
thisCell.setNeedsDisplay()
thisCell.layoutIfNeeded()
})
}
else{
// do nothing
}
})
}
else {
cell.cellImage.image = cellImage!
cell.setNeedsDisplay()
}
// Configure the cell
return cell
}
Chances are the UICollectionView has cached an intermediate representation of your cell so that it can scroll quickly.
The routine you should be calling to indicate that your cell needs to be updated, resized, and have it's visual representation redone is reloadItemsAtIndexPaths with a single index path representing your cell.