0

I have a UITableView with few rows. When I LongPress on a row then a menu will pop-up and I will be able to open the camera to take a picture. I've created an array of Albums which contains an array of Photos. I want for each row to save that captured pictures into my array of Photos. When I try to append the photo to my array is crashing with error Index out of range on this line: albumPhotos[lastIndexPath.row].imagesPath?.append(nameOfPhoto)

Here is a small DEMO which reflect my actual issue: https://github.com/tygruletz/AddPhotosToAlbums

Here is my code:

class AlbumPhotos {

    var imagesPath: [String]

    init(imagesPath: [String]) {

        self.imagesPath = imagesPath
    }
}

protocol AlbumCellDelegate {

    func longTapGestureOnCell(_ selectedCell: AlbumCell)
}

class AlbumCell: UITableViewCell {

    // Interface Links
    @IBOutlet weak var albumNameLabel: UILabel!
    @IBOutlet weak var photosImageView: CustomImageView!

    // Properties
    var delegate: AlbumCellDelegate?

    override func awakeFromNib() {
        super.awakeFromNib()

        let longTapGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longTapPressed(sender:)))
        addGestureRecognizer(longTapGesture)
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

    }

    // Detect when the user press Long Tap on any cell
    @objc func longTapPressed(sender: UITapGestureRecognizer) {
        delegate?.longTapGestureOnCell(self)
    }
}


extension AlbumVC: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return receivedAlbumsType.count
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

        return 100
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "albumCell", for: indexPath) as! AlbumCell

        cell.delegate = self // tell the delegate to report everything to this VC
        cell.albumNameLabel.text = receivedAlbumsType[indexPath.row].name.capitalized


        if albumPhotos.isEmpty {
            cell.photosImageView.isUserInteractionEnabled = false
        }
        else {

            let thumbnailImage = albumPhotos[indexPath.row].imagesPath.last ?? String()
            cell.photosImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapOnPhotosImageView)))
            cell.photosImageView.indexPath = indexPath
            cell.photosImageView.isUserInteractionEnabled = true
            cell.photosImageView.image = UIImage(named: thumbnailImage)
            print("For section \(indexPath.section) - row \(String(describing: indexPath.row)) the album photos are: \(String(describing: albumPhotos[indexPath.row].imagesPath))")
        }

        return cell
    }

    @objc func tapOnPhotosImageView(_ sender: UITapGestureRecognizer){

        guard let img = sender.view as? CustomImageView, let indexPath = img.indexPath else {return}
        selectedIndexPath = indexPath // Get the index of the clicked album of images
        print("Index for selected image: \(selectedIndexPath ?? IndexPath())")

        if albumPhotos.isEmpty {

            print("No photos in the album.")
        }
        else{
            print("There are photos in the album.")
            print(albumPhotos.map {$0.imagesPath})
        }
    }
}

class AlbumVC: UIViewController {

    // Interface Links
    @IBOutlet weak var albumsTableView: UITableView!

    // Properties
    var receivedAlbumsType: [AlbumType] = []
    var imagePicker = UIImagePickerController()
    var cellImageView = UIImageView()
    var selectedIndexPath: IndexPath!
    var lastIndexPath: IndexPath!
    var albumPhotos: [AlbumPhotos] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        albumsTableView.reloadData()
    }

    func setupViews(){
        albumsTableView.tableFooterView = UIView()
        albumsTableView.reloadData()
    }
}

extension AlbumVC: UINavigationControllerDelegate, UIImagePickerControllerDelegate {

    //Dismiss the Camera and display the selected image into the UIImageView
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]){

        imagePicker.dismiss(animated: true, completion: nil)
        guard let selectedImage = info[.originalImage] as? UIImage else {
            print("Image not found!")
            return
        }

        let randomNumber = arc4random()
        let nameOfPhoto = "photo_\(randomNumber).jpeg"

        print("Resolution of captured image before resize:  \(selectedImage.size)")

        print("Index for lastIndexPath.row: \(lastIndexPath.row)")

        albumPhotos[lastIndexPath.row].imagesPath?.append(nameOfPhoto)

        print("last images from albumPhotos: \(albumPhotos[lastIndexPath.row].imagesPath?[lastIndexPath.row] ?? String())")

        albumsTableView.reloadData()
    }
}

Thanks for reading this !

7
  • Where have you added values to albumPhotos array? Commented May 17, 2019 at 13:20
  • See edit. albumPhotos[lastIndexPath.row].imagesPath?.append(nameOfPhoto) Commented May 17, 2019 at 13:20
  • In this like you apeend string to imagesPath array. Where do you append value to albumPhotos array which is type of [AlbumPhotos] Commented May 17, 2019 at 13:21
  • Yes. I need to append the name of the photos into that array. But I can't manage to do that. Is crashing when I try to append. Commented May 17, 2019 at 13:22
  • Why do you have 10 rows? What do you want to achieve? Do you have image of desired output? Commented May 17, 2019 at 13:24

3 Answers 3

1

I have changed below files in your code. So, please replace these files and check your app and share your feedback.

AlbumVC.swift

import UIKit

class AlbumVC: UIViewController {

    // Interface Links
    @IBOutlet weak var albumsTableView: UITableView!

    // Properties
    var imagePicker = UIImagePickerController()
    var cellImageView = UIImageView()
    var albumPhotos = [AlbumPhotos]()
    var selectedIndexPath: IndexPath!
    var lastIndexPath: IndexPath!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        albumPhotos = DataModel.instance.getAlbumPhotosData(numberOfData: 10)
    }

    func setupViews(){
        albumsTableView.tableFooterView = UIView()
        albumsTableView.reloadData()
    }
}


extension AlbumVC: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return albumPhotos.count
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

        return 100
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "albumCell", for: indexPath) as! AlbumCell

        cell.delegate = self // tell the delegate to report everything to this VC

        if albumPhotos.isEmpty {
            cell.photosImageView.isUserInteractionEnabled = false
        }
        else {

            let thumbnailImage = albumPhotos[indexPath.row].imagesPath.last ?? String()
            cell.photosImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapOnPhotosImageView)))

            cell.photosImageView.indexPath = indexPath
            cell.photosImageView.isUserInteractionEnabled = true
            cell.photosImageView.image = UIImage(named: thumbnailImage)
            print("For section \(indexPath.section) - row \(String(describing: indexPath.row)) the album photos are: \(String(describing: albumPhotos[indexPath.row].imagesPath))")
        }

        return cell
    }

    @objc func tapOnPhotosImageView(_ sender: UITapGestureRecognizer){

        guard let img = sender.view as? CustomImageView, let indexPath = img.indexPath else {return}
        selectedIndexPath = indexPath // Get the index of the clicked album of images
        print("Index for selected image: \(selectedIndexPath ?? IndexPath())")

        if albumPhotos.isEmpty {

            print("No photos in the album.")
        }
        else{
            print("There are photos in the album.")
            print(albumPhotos.map {$0.imagesPath})
        }
    }
}

extension AlbumVC: UINavigationControllerDelegate, UIImagePickerControllerDelegate {

    //Dismiss the Camera and display the selected image into the UIImageView
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]){

        imagePicker.dismiss(animated: true, completion: nil)
        guard let selectedImage = info[.originalImage] as? UIImage else {
            print("Image not found!")
            return
        }

        let randomNumber = arc4random()
        let nameOfPhoto = "photo_\(randomNumber).jpeg"

        print("Resolution of captured image before resize:  \(selectedImage.size)")

        print("Index for lastIndexPath.row: \(lastIndexPath.row)")

        albumPhotos[lastIndexPath.row].imagesPath.append(nameOfPhoto)

        print("last images from albumPhotos: \(albumPhotos[lastIndexPath.row].imagesPath )")

        albumsTableView.reloadData()
    }
}

extension AlbumVC: AlbumCellDelegate {

    // Delegate function to detect on which cell was pressed a LongTapGesture
    func longTapGestureOnCell(_ selectedCell: AlbumCell) {
        showOptionsOnCellTapped(albumsTableView.indexPath(for: selectedCell)!)
    }

    func showOptionsOnCellTapped(_ indexPath: IndexPath) {
        let addPhoto = UIAlertAction(title: "📷 Add Photo", style: .default) { action in
            self.lastIndexPath = indexPath
            print("Add Photo - LastIndexPath: \(self.lastIndexPath ?? IndexPath())")

            self.showCamera(imagePicker: self.imagePicker)
        }

        let actionSheet = configureActionSheet()
        actionSheet.addAction(addPhoto)
        self.present(actionSheet, animated: true, completion: nil)
    }

    // Open camera
    func showCamera(imagePicker: UIImagePickerController) {

        imagePicker.delegate = self
        imagePicker.sourceType = .camera
        present(imagePicker, animated: true, completion: nil)
    }

    // Configure an UIActionSheet to work for iPhone and iPad. If the user use an iPad then the ActionSheet will be displayed in the middle of the screen.
    func configureActionSheet() -> UIAlertController {
        let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
        let cancel = UIAlertAction(title: "Cance;", style: .cancel, handler: nil)
        actionSheet.addAction(cancel)

        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad ){
            actionSheet.popoverPresentationController?.sourceView = self.view
            actionSheet.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
            actionSheet.popoverPresentationController?.permittedArrowDirections = []
        }
        return actionSheet
    }  
}

and added this file to create black data for your app DataModel.Swift

class DataModel {

    static let instance = DataModel()

    private init() {

    }

    func getAlbumPhotosData(numberOfData: Int) -> [AlbumPhotos] {

        var albumPhotos = [AlbumPhotos]()

        for _ in 1...numberOfData {
            let objAlbum = AlbumPhotos(imagesPath: [])
            albumPhotos.append(objAlbum)
        }

        return albumPhotos
    }
}

and also change below file AlbumPhotos.swift as well

import Foundation

class AlbumPhotos {

    var imagesPath: [String]

    init(imagesPath: [String]) {

        self.imagesPath = imagesPath
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

Hi Vagh. I implemented your solution but is also crashing my app when I insert the second Row and I try to attach a photo to the second Row. Here I attached 2 print screens with my app and also where is crashing: imgur.com/a/f8jztdO
Hi, @Flo I have edited my code. So, please replace that and let me know if anything goes wrong.
Hi, @Vagh I modified the model and is not optional anymore but the crash is exactly in the same place. When I add 3 rows and I try to add a photo on the second or third row is crashing. If I add a photo on the first row is not crashing. Here is a printscreen: imgur.com/a/rqebYrB Thank you !
Hi, @Flo I have changed "AlbumVC.swift" as well did you replace that ??
Yes of course. This was the first thing what I've done it.
|
0
  albumPhotos[lastIndexPath.row].imagesPath?.append(nameOfPhoto)

At that time, the albumPhotos is empty so you can't get AlbumPhotos object form that position of an array.

Either you need to add an object of AlbumPhotos at each cell for row at index or you can take the dictionary by putting index as key.

3 Comments

Hi @Sarthak. I updated the Demo Project to reflect exactly my issue. If you can take a look and to tell me what I'm doing wrong then will be amazing. Here is the updated link, also you can find the link in the post above: github.com/tygruletz/AddPhotosToAlbums
ok, I will check. Just initialize imagesPath array for every cell item and insert in albumPhotos. Problem is that, you are appending image into array imagesPath of particular object of albumPhotos. but at that time object is not available at index. I will update you asap.
@Flo I have added one more answer. Please check.
0
class AlbumPhotos {

    var imagesPath: [UIImage]

    init(imagesPath: [UIImage]) {

        self.imagesPath = imagesPath
    }
}


extension AlbumVC: AlbumTypeVCDelegate {

    func receivedAlbumType(name: String) {
        albumPhotos.append(AlbumPhotos(imagesPath: [UIImage]()))
        receivedAlbumsType.append(AlbumType(name: name))
    }
}

extension AlbumVC: UINavigationControllerDelegate, UIImagePickerControllerDelegate {

    //Dismiss the Camera and display the selected image into the UIImageView
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]){

        imagePicker.dismiss(animated: true, completion: nil)
        guard let selectedImage = info[.originalImage] as? UIImage else {
            print("Image not found!")
            return
        }

        let randomNumber = arc4random()
        let nameOfPhoto = "photo_\(randomNumber).jpeg"

        print("Resolution of captured image before resize:  \(selectedImage.size)")

        print("Index for lastIndexPath.row: \(lastIndexPath.row)")

        albumPhotos[lastIndexPath.row].imagesPath.append(selectedImage)

//        print("last images from albumPhotos: \(albumPhotos[lastIndexPath.row].imagesPath.last ?? String())")

        albumsTableView.reloadData()
    }
}


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "albumCell", for: indexPath) as! AlbumCell

        cell.delegate = self // tell the delegate to report everything to this VC
        cell.albumNameLabel.text = receivedAlbumsType[indexPath.row].name.capitalized


        if albumPhotos.isEmpty {
            cell.photosImageView.isUserInteractionEnabled = false
        }
        else {

            let thumbnailImage = albumPhotos[indexPath.row].imagesPath.last ?? UIImage()
            cell.photosImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapOnPhotosImageView)))
            cell.photosImageView.indexPath = indexPath
            cell.photosImageView.isUserInteractionEnabled = true
            cell.photosImageView.image = thumbnailImage//UIImage(named: thumbnailImage)
            print("For section \(indexPath.section) - row \(String(describing: indexPath.row)) the album photos are: \(String(describing: albumPhotos[indexPath.row].imagesPath))")
        }

        return cell
    }

Please try to change the above code in your project.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.