I call to an image picker when a cell is tapped in a collection view:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
switch indexPath.row {
case 2:
imagePicker.delegate = self
imagePicker.sourceType = .savedPhotosAlbum;
imagePicker.allowsEditing = false
self.present(imagePicker, animated: true, completion: nil)
default:
break
}
}
Then I want to upload an image to my cell's imageView:
func imagePickerController(picker: UIImagePickerController!, didFinishPickingImage image: UIImage!, editingInfo: NSDictionary!){
self.dismiss(animated: true, completion: { () -> Void in
})
//imageView.image = image ---> need imageview in a cell wit indexpath = 2
}
How do I implement the delegates and protocols?
You can try
Option #1
arr[2] = image
let cell = collectionView.cellForItem(at:IndexPath(row:2,section:0)) as! cellName
cell.imageView.image = image
Option #2 ( Recommended ) as it's a bad idea to access the cell out of table
is to edit the image in model , then reload the table/indexPath
func imagePickerController(picker: UIImagePickerController!, didFinishPickingImage image: UIImage!, editingInfo: NSDictionary!){
self.dismiss(animated: true, completion:nil)
arr[2] = image
let indexPath = IndexPath(item: 2, section: 0)
self.collectionView.reloadItems(at: [indexPath])
}
Plus cellForItemAt should have
let cell = ///
cell.imageView.image = arr[indexPath.row]
In the image picker delegate, you need to update the data model used to populate your collection view. Then you tell the collection view to reload that associated row.
This assumes of course that your cellForItemAt is written to set the cell's image appropriately from the data model.
Related
So in my app I currently have a tableView with custom cells that have an imageView in them. Here is an image for reference to see what It look like and here is the code that defines the custom cell:
class GearComponentTableViewCell: UITableViewCell {
var delegate:Stepper?
var tableViewCellPosition: Int! = nil
// Image
#IBOutlet weak var itemImage: UIImageView!
// Name
#IBOutlet weak var itemName: UILabel!
// Weight
#IBOutlet weak var itemWeight1: UILabel!
#IBOutlet weak var itemWeight2: UILabel!
// Quanity
#IBOutlet weak var itemQuanity: UILabel!
#IBAction func stepperPressed (_ sender: UIStepper!) {
if (sender.value == 1) {
sender.value = 0
delegate?.stepperWasPressed(didIncrease: true, namePassed: itemName.text!, userindexPath: tableViewCellPosition)
} else if (sender.value == -1) {
sender.value = 0
delegate?.stepperWasPressed(didIncrease: false, namePassed: itemName.text!, userindexPath: tableViewCellPosition)
}
}
// Notes
#IBOutlet weak var itemNotes: UILabel!
}
These cells are tappable and when tapped they present an alert with options for adding an image to the cell you chose:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("User selected item: \(indexPath.row) and name is \(itemArray[indexPath.row].name)")
showPopUp(itemChosen: indexPath)
}
Here is the logic for the pop up/Alert:
func showPopUp(itemChosen: IndexPath) {
let alert = UIAlertController(title: "Add Photo To Item", message: .none, preferredStyle: .actionSheet)
// alert actions
let action1 = UIAlertAction(title: "Take Photo", style: .default) { action in
self.takePhoto()
}
let action2 = UIAlertAction(title: "Import Photo", style: .default) { action in
self.importPhoto(position: itemChosen)
}
let action3 = UIAlertAction(title: "Cancel", style: .destructive)
alert.addAction(action1)
alert.addAction(action2)
alert.addAction(action3)
present(alert, animated: true,completion: nil)
}
Ignore the takePhoto() function because it isn't being used yet, but right below that I am calling the importPhoto() function that takes in the position of the item you selected:
func importPhoto(position: IndexPath) {
print("User chose to import photo")
let picker = UIImagePickerController()
picker.allowsEditing = true
picker.delegate = self
present(picker, animated: true) {
print("cell tapped \(position)")
self.cellChosen = position
}
}
It then sets a global variable to the item you selected (I know this isn't a good practice vs. passing the actual indexPath) that I use to pass into the imagePickerControllerDelegate function:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let image = info[.editedImage] as? UIImage else { return }
dismiss(animated: true)
if let cell = tableView(gearTableView, cellForRowAt: cellChosen) as? GearComponentTableViewCell {
cell.itemImage.image = image
self.updateUI()
print("item image set for the item at position: \(cellChosen.row)")
}else {
print("did not set item image")
}
}
This is where I am running into issues now. When that delegate function gets called it successfully prints the true-case in the if/else statement and the photo isn't actually being set and I'm not receiving any errors at the time it is set. The only error I receive is "Changing the translatesAutoresizingMaskIntoConstraints property of a UICollectionViewCell that is managed by a UICollectionView is not supported, and will result in incorrect self-sizing." when I tap the right bar-button item from the first photo in my post to add a new cell to the tableView. I require some info in the form of another Alert with textfields for some info about the item you are creating before the cell is actually created to I'm not sure if that error is actually relevant to what I am currently working on.
Any ideas as to how I can set the image of the imageView in my cell? I've already tried changing the Layout of my imageView from Inferred (constraints) to Auto-resizing mask and any new cells stopped appearing...
Here is a link to my repository/the viewController I was working in.
It's because you're not updating the image on the data source of the table view. You update when you dequeue the cell, but the you call updateUI that calls reloadData().
For instance, your method imagePickerController can work like this:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let image = info[.editedImage] as? UIImage else { return }
dismiss(animated: true)
let item = itemArray[cellChosen.row]
let userSubmittedItem = GearItem(itemName: item.name,
itemImage: image,
itemWeight1: item.weight1,
itemWeight2: item.weight2,
itemQuantity: item.quantity,
itemNotes: item.notes)
itemArray[cellChosen.row] = userSubmittedItem
self.updateUI()
}
Change your implementation of method imagePickerController(_ picker:didFinishPickingMediaWithInfo:) to
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let image = info[.editedImage] as? UIImage else { return }
picker.dismiss(animated: true)
itemArray[cellChosen.row].image = image
gearTableView.reloadRows(at: [cellChosen], with: .none)
}
When you need to update an image of a data source model. You update the model and call reload row.
About why your code doesn't work, set a breakpoint at func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell, and you will find your imageView's image is beening reset in above method.
I am using Swift 3, Xcode 8.2. I have a custom Table View Cell (called MyCustomCell) in which there is an image view and a button that, when clicked, opens the camera. The user can add more of these cells. I want the user to be able to click on a button, take a picture, and have that appear in the appropriate image view for that button.
My issue is that right now it is always appearing in the first image view but not the others.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let pickedImage : UIImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
let scancell = tableView.cellForRow(at: NSIndexPath(row: 0, section: 0) as IndexPath) as! MyCustomCell // it's this line that is wrong
scancell.imgView.contentMode = .scaleAspectFit
scancell.imgView.image = pickedImage
scancell.cameraButton.isHidden = true
};
self.dismiss(animated: true, completion: nil)
}
I'm having hard time understanding how the imagePickerController gets called and if I can pass in the custom cell that the user clicked on.
I want to do something like this:
tableView.cellForRow(at: tableView.indexPath(for: cell))
where cell is passed into the argument somehow but I'm not sure if the signature of the imagePickerController can be modified and if so, how exactly is it called?
Any help would be greatly appreciated. Thanks!
Add a tag to the button in cellForRow method and use that tag to get the cell.
var Celltag = 0
func tableView_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell((withIdentifier: "")) as! MyCustomCell
cell.customButton.tag = indexPath.row
cell.customButton.addTarget(self, action: #selector(ButtonClickMethod), for: .touchUpInside)
return cell
}
func ButtonClickMethod (sender:UIButton) {
Celltag = sender.tag
........
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let pickedImage : UIImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
let scancell = tableView.cellForRow(at: NSIndexPath(row: Celltag, section: 0) as IndexPath) as! MyCustomCell
scancell.imgView.contentMode = .scaleAspectFit
scancell.imgView.image = pickedImage
scancell.cameraButton.isHidden = true
};
self.dismiss(animated: true, completion: nil)
}
Hope this helps.
In your MyCustomCell class you should have this function for imagePicker where the user can select one photo and return the photo your user chose.Then, in the tableViewController data source method with cellForRow (where you also pass the indexPath of the cell your user clicked on) you should have something like
func tableView_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(...) as! MyCustomCell
let image = cell.pickImage
cell.image = image
return cell
}
I'm trying to build a recipe app with a recipe upload function. In the PostController, there will be a tableview of all the cooking steps, each in a tableview cell. in the cell there will be a textfield of description and a UIImageView for picture upload ( the picture chosen from pickerController will be displayed in this UIImageView for later upload). I'm trying to do
imageViewInCell.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleImageUpload)))
to call thehandleImageUpload() function that generates a UIImagePickerController. But by doing this, I met two problems.
I cannot get the index.row value of the cell by the selector in UITapGestureRecognizer , with out the index I cannot assign the chosen image back to the UIImageView of the cell.
Even if I got index.row in handleImageUpload, I still need the function below to assign selected image. How would this function accept my parameter and find the corresponding imageViewInCell?
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let selectedImage: UIImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
imageViewInCell.image = selectedImage
}
dismiss(animated: true, completion: nil)
}
you can set the indexPath.row as the tag of your imageview in cellForRowAtIndexPath like Below
cell.yourImageView.tag = indexPath.row
and then you can get this indepath backagain using below
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath) as! yourcellClass!
cell.yourImgeView.image = selectedImage
I'm assuming you want thehandleImageUpload() to be called only on tap on imageView instead of whole cell, hence you're using tapGesture.
Now to answer your question, assign tag to your imageView like this :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
//cell configuration
cell.imageView.tag = indexPath.row
return cell
}
And you can assign selected image to selected cell like this:
Now your handleImageUpload() is:
func handleImageUpload(_ sender: UITapGestureRecognizer){
selectedIndexPath = sender.tag
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let selectedImage: UIImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
let cell = self.tableView.cellForRowAtIndexPath(selectedIndexPath) as UITableViewCell
cell.imageViewInCell.image = selectedImage
}
dismiss(animated: true, completion: nil)
}
I'm pretty late to the party, but the code below should work.
I'd have the UITableViewCell do the work of dealing with the image. Make it a UIPickerControllerDelegate and have it hold onto a closure to present the picker when necessary (as it can't present something itself as it is not a view controller).
Something like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let photoCell = tableView.dequeueReusableCell(withIdentifier: "PhotosTableViewCell", for: indexPath) as! PhotosTableViewCell
photoCell.takePicture = {[weak self] in
let imagePicker = UIImagePickerController()
imagePicker.delegate = photoCell
imagePicker.sourceType = .camera
self?.present(imagePicker, animated: true, completion: nil)
}
Have your UITableviewCell implement:
var takePicture: (() -> Void)?
to hold the closure. Then call it from a button or something to make it trigger.
Use:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
to deal with the image.
From what I understand, I can not connect the button in my cell to my TableViewController using an outlet connection. I can however connect the button in my cell to my TableView using an action connection. This is the root of my huge problems.
In my cell I have a textview, an imageView with overlaid button, and a send button. THIS IS THE CELL
I assign an image to the imageView using imagePicker. imagePicker must be opened from the tableViewController (it can not be opened in the cell).
var MyImage = UIImage?()
var MyName = String()
var MyStatus = String()
// This is ImgButton that overlays the imageView
#IBAction func MyImageButtonAction(sender: AnyObject) {
// Select Image
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
imagePicker.allowsEditing = false
self.presentViewController(imagePicker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
// The image chosen by the user is now called MyImage
MyImage = image
self.dismissViewControllerAnimated(true, completion: nil)
}
Now when the send button is tapped, I make MyImage a PFFile for Parse.com and send it to the database. I don't need to give full details here. This part works fine.
The problem is that I don't know how to properly connect the cell to the tableViewController in a way that I can transfer values from one to the other. I want to get the textView.text from the cell, to the tableViewController so that from there I can send it to database with the image. The other problem is that, although the user can select an image and send it to database, they can't get the selected image into their imageView.
This is my attempt to connect the cell to the tableViewController:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyStatusTableViewCell", forIndexPath: indexPath) as! MyStatusTableViewCell
cell.MyName.text = MyName
return cell
}
I was hoping that his code meant that MyName is now the variable that contains the .text that the user input in the textView.text in the cell. I tried sending MyName to the database but it came up empty.
How do I get the textView.text from the tableViewCell into my database?
u can set tag of each element of UITableViewCell and then u can access it in cellForRowAtIndexPath like
if let textfield = cell.viewWithTag(21) as? UITextField {
textfield.text = MyName
}
You can create IBAction instead of IBOutlet and in button action you can convert sender to position and find index path like this
#IBAction func buttonClick(sender: AnyObject) {
let btnPos: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath: NSIndexPath = self.tableView.indexPathForRowAtPoint(btnPos)!
print("indexPath->\(indexPath.row)")
}
I found the problem which is inside your UITableView cellForRowAtIndexPath method. You have to load your custom cells like that below:-
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("MyStatusTableViewCell", forIndexPath: indexPath) as! MyStatusTableViewCell
if cell == nil{
//Here you have to load your custom cells like that below
let array = NSBundle.mainBundle().loadNibNamed("MyStatusTableViewCell", owner: nil, options: nil)
cell = array[0] as? MyStatusTableViewCell
}
cell.MyName.text = "MyName" //Now it will set your textview value
return cell
}
I am fetching user's Photo Asset and trying to create a custom UICollectionViewCell in my UICollectionView where the first index is a Camera Image and other cells are the Camera Images. But when I scroll, it's like the image at the index is recreated and I notice some images in my cell get the view I added at the index[0]
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//Deque an GridViewCell
let cell: GridViewCell = (collectionView.dequeueReusableCellWithReuseIdentifier(CellReuseIdentifier, forIndexPath: indexPath) as? GridViewCell)!
if indexPath.item != 0 {
let asset = assetsFetchResults[indexPath.item - 1] as! PHAsset
cell.representedAssetIdentifier = asset.localIdentifier
imageManager.requestImageForAsset(asset, targetSize: AssetGridThumbnailSize, contentMode: PHImageContentMode.AspectFill, options: nil) { (result: UIImage?, info: [NSObject : AnyObject]?) -> Void in
//print(result)
if cell.representedAssetIdentifier.isEqualToString(asset.localIdentifier) {
if let imageResult = result {
cell.imageView.image = imageResult
}
}
}
} else {
let cameraButton = UIButton(frame: CGRectMake(0,0,80,80))
cameraButton.setTitle("Camera", forState: .Normal)
cameraButton.frame = cell.imageView.bounds
cell.imageView.addSubview(cameraButton)
}
return cell
}
Below is an image for illustration
Reusable cells are reused by the collection view when they are scrolled outside the view to prevent a lot of memory use. When a certain cell gets scrolled outside of the view the collection view will mark them as reusable. Reusable cells will be reused by the collection view instead of creating new ones. This will significantly reduce memory use. Why would you keep 1000 cells in memory if only 20 of them fit on the screen at the same time.
because cells are reused, they will still contain the content of previous cell, this means that the cell still has the cameraButton as a subview. Same story with the images, you need to manually remove them at from the cell and make sure al old content gets replaced or removed.
When you perform methods that will download and set the image to the cell after a while, the cell could still contain the image of the previous cell. You should clear the image at the beginning of the cellForRowAtIndexPath method to remove the old image/subviews.
See example below:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! GridViewCell
if indexPath.row == 0 {
// Show the title becouse it's the first cell.
cell.titleLabel.hidden = false
// Title needs to be set in storyboard
// Default image for camera roll needs to be set in storyboard
} else {
// All the other cells.
cell.titleLabel.hidden = true
// Clear old content
cell.imageView.image = nil
// I don't exactly know what all this code does behind the screen, but I assume it's a method that downloads the image and add it to the imageView when it's done.
let asset = assetsFetchResults[indexPath.item - 1] as! PHAsset
cell.representedAssetIdentifier = asset.localIdentifier
imageManager.requestImageForAsset(asset, targetSize: AssetGridThumbnailSize, contentMode: PHImageContentMode.AspectFill, options: nil) { (result: UIImage?, info: [NSObject : AnyObject]?) -> Void in
//print(result)
if cell.representedAssetIdentifier.isEqualToString(asset.localIdentifier) {
if let imageResult = result {
cell.imageView.image = imageResult
}
}
}
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// Cell that needs to open the camera roll when tapped)
if indexPath.row == 0 {
// Do something that opens the camera roll
}
}
I didn't test this code, but it should be something similar to this.