简体   繁体   中英

How to send clicked item of Table View to other view controller when datasource and delegate are separate from View Controller

I am a beginner to iOS coming from the android background and just learned about table view (for me it's an Android ListView). I am trying to separate data source & delegate from view controller. I found some tutorials on how to do so but stuck at figuring out how to send the clicked item to another view controller. The code is below:

class PictureTableViewController: UIViewController {


    @IBOutlet weak var pictureTableView: UITableView!

    private let picsDataSource: PicturesDataSource

    required init?(coder aDecoder: NSCoder) {
        self.picsDataSource = PicturesDataSource()
        super.init(coder: aDecoder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        pictureTableView.dataSource = picsDataSource
        pictureTableView.reloadData()
        pictureTableView.delegate = picsDataSource
    }
}

class PicturesDataSource: NSObject, UITableViewDataSource, UITableViewDelegate{

    private var pictureModels = [PictureModel]()

    override init(){
        let picModelsDataController = PictureModelsDataController()
        pictureModels = picModelsDataController.pictureModels
    }


    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return pictureModels.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PictureCell.self)) as! PictureCell

        let picModel = pictureModels[indexPath.row]
        cell.pictureName = picModel.pictureName
        cell.imageItem = picModel.imageItem

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {


        //1 - try loading the "Detail" view controller and typecasting it to be DetailViewController
        if let detailViewController = storyboard.instantiateViewController(withIdentifier: "PictureDetailView") as? PictureDetailViewController {

            //2 - success! set its selecteImage property
            detailViewController.selectedImgName = pictureModels[indexPath.row].pictureName

            //3 - now push it onto the navigation controller
            navigationController?.pushViewController(detailViewController, animated: true)
        }
    }

}     

Error in: func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){ } . since "storyboard" & "navigationController" are not available in PicturesDataSource class, how can I send clicked item(picture name) to the DetailsViewController

There are StackOverflow answers about separating data source and delegate but did not solve my problem.

Using: Xcode 8.3 beta 6

You can include a reference to main view controller at your table view events handler. Below is a playground code I derived from your example:

import UIKit

// MARK: - Model

struct Picture {
  let title: String
  let image: UIImage
}

struct PictureModelsDataSource {
  let pictures = [
    Picture(title: "exampleTitle", image: UIImage(named: "exampleImage")!),
    Picture(title: "exampleTitle", image: UIImage(named: "exampleImage")!)
  ]
}

// MARK - View

class PictureCell: UITableViewCell {
  @IBOutlet weak var pictureTitleLabel: UILabel!
  @IBOutlet weak var pictureImage: UIImageView!
}

// MARK: - Controller

class PictureTableViewController: UIViewController {

  // MARK: - Properties

  @IBOutlet weak var pictureTableView: UITableView!

  private var pictureListController: PictureListController?

  // MARK: - View lifecycle

  override func viewDidLoad() {
    super.viewDidLoad()
    pictureListController = PictureListController()
    pictureListController?.viewController = self
    pictureTableView.dataSource = pictureListController
    pictureTableView.delegate = pictureListController
    pictureTableView.reloadData()
  }
}

class PictureDetailViewController: UIViewController {
  var selectedPictureTitle: String?
}

class PictureListController: NSObject, UITableViewDataSource, UITableViewDelegate {

  // MARK: - Properties

  weak var viewController: PictureTableViewController?

  private let pictures: [Picture] = {
    let pictureModelsDataSource = PictureModelsDataSource()
    return pictureModelsDataSource.pictures
  }()

  // MARK: - View setup

  func numberOfSections(in tableView: UITableView) -> Int {
    return 1
  }

  // MARK: - Event handling

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return pictures.count
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PictureCell.self)) as? PictureCell else {
      return UITableViewCell()
    }

    let picture = pictures[indexPath.row]
    cell.pictureTitleLabel.text = picture.title
    cell.pictureImage.image = picture.image

    return cell
  }

  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let pictureTitle = pictures[indexPath.row].title
    let storyboard = UIStoryboard(name: "exampleStoryboard", bundle: nil)
    if let pictureDetailViewController = storyboard.instantiateViewController(withIdentifier: "PictureDetailView") as? PictureDetailViewController {
      pictureDetailViewController.selectedPictureTitle = pictureTitle
      viewController?.navigationController?.pushViewController(pictureDetailViewController, animated: true)
    }
  }
}

See StoryBoard object can be obtained by using this

let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)

Now your second question is about how to get navigation controller. It means how to get currentViewController in your case. This can be get by below code

 func getCurrentViewController() -> UIViewController? {

 if let rootController = UIApplication.shared.keyWindow?.rootViewController {
    var currentController: UIViewController! = rootController
       while( currentController.presentedViewController != nil ) {
                currentController = currentController.presentedViewController
            }
            return currentController
        }
        return nil
    }

Now your didSelectRowAt code will look like this

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
 let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)

 if let detailViewController = storyboard.instantiateViewController(withIdentifier: "PictureDetailView") as? PictureDetailViewController 
   detailViewController.selectedImgName = pictureModels[indexPath.row].pictureName
    self.getCurrentViewController()!.pushViewController(detailViewController, animated: true)  
}

You are trying to adhere to MVC, but you are confusing what your actual data source is.

PicturesDataSource is not your data source . It's the code that tells your table how to set itself up.

PictureModelsDataController() is the source from which you get the data that actually populates that table.


All of your posted code should be in the same class:

class PictureTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

Change these lines:

pictureTableView.dataSource = picsDataSource
pictureTableView.delegate = picsDataSource

to

pictureTableView.dataSource = self  // Note use of self because this is now the dataSource, not another class
pictureTableView.delegate = self  // Note use of self because this is now the delegate, not another class

and remove:

private let picsDataSource: PicturesDataSource

required init?(coder aDecoder: NSCoder) {
    self.picsDataSource = PicturesDataSource()
    super.init(coder: aDecoder)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM