iosswiftpass-data

How do I pass data to another ViewController without presenting said ViewController?


I am building an app that fetches pictures from a nasa API and then shows it a CollectionView as well as in a UITableView. My app is driven by a UITabBarController which has an array of three view controllers. HomeViewController((): This is where the app defaults to when it initially launches, this viewcontroller shows the latest picture retrieved from the API, i.e today's date. I have a button here that when clicked, should send the latest photo being displayed to the FavoriteViewController(), but without presenting it that viewcontroller. And so far it only works if I also present this VC, and if I don't, the data isn't being sent.

BrowseViewController(): this is the second view controller in the UITabBar's array of view controllers and this is where I use collectionViews to show all of the images in a grid layout. I have no issues here.

FavoritesViewController(): This is the last view controller in the array and it's of type UITableViewController. What I want to do here is display the photo's title, the date it was taken and the photo itself. The only problem is I am unable to pass the photo from the HomeViewController()

I tried using a closure aka a completion handler, I tried posting a NotificationsCenter, and to no avail. I have been told to use a singleton approach but I am trying to avoid that as I am sure there's a cleaner way to do this. Thanks By the way, I am not using storyboards at all.

This is the Photos object


import UIKit


struct Photos: Codable {
    let date: String
    let explanation: String
    let title: String
    let url: URL?
    let media_type: String
}

extension Photos: Equatable {
    static func == (lhs: Photos, rhs: Photos) -> Bool {
        return lhs.url == rhs.url
    }
}


enum PhotoError: Error {
    case imageCreationError
    case missingImageURL
}

This is the HomeViewController()


import UIKit
import JGProgressHUD
class HomeViewController: UIViewController {
 
  
    var photo: Photos!
    var photos = [Photos]()
    var store: PhotoStore!
    
    let scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()
    
   private let contentView: UIView = {
        let contentView = UIView()
        contentView.translatesAutoresizingMaskIntoConstraints = false
        return contentView
    }()
    
    
    
    var imageView: UIImageView = {
        let imageview = UIImageView()
        imageview.translatesAutoresizingMaskIntoConstraints = false
        imageview.contentMode = .scaleAspectFill
        imageview.layer.masksToBounds = true
        return imageview
    }()

    private var titleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.numberOfLines = 0
        label.font = .systemFont(ofSize: 24, weight: .semibold)
        label.backgroundColor = .tertiarySystemFill
        return label
    }()
    
    private var descriptionTextView: UITextView = {
        let textview = UITextView()
        textview.translatesAutoresizingMaskIntoConstraints = false
        textview.font = .systemFont(ofSize: 21)
        textview.textAlignment = .justified
        textview.textColor = .white
        textview.isEditable = false
        textview.backgroundColor = .black
        return textview
    }()
   
   
    var favsButton: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setImage(UIImage(systemName: "heart"), for: .normal)
   
        return button
    }()
    
    var dateButton: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setImage(UIImage(systemName: "star"), for: .normal)
        button.backgroundColor = .systemGreen
        return button
    }()
    
    
    override func loadView() {
        super.loadView()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
       
        
        view.backgroundColor = .systemBackground
        navigationController?.navigationBar.prefersLargeTitles = true
        addSubviews()
        showHUD()
        fetchPhotoFromAPI()
        constraints()
        
        //MARK: - navigation buttons
        navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "calendar"),
                                                           style: .plain,
                                                           target: self,
                                                           action: #selector(dateButtonTapped(_:)))
        
     
        
        navigationItem.leftBarButtonItem?.tintColor = .systemRed
        navigationItem.rightBarButtonItem?.tintColor = .systemRed

      
    }
    
    func fetchPhotoFromAPI() {
        store.fetchPhotosFromNasa {[weak self] photoResult in
            switch photoResult {
            case let .success(photos):
                self?.photos = photos
                if let lastPhoto = photos.last {
                    print(lastPhoto.date)
                    self?.updateImageView(for: lastPhoto)
                    self?.photo = lastPhoto
                    
                    
                }
            case let .failure(error):
                print("error fetching interesting photos \(error)")
            }
            
        }
    }

    
    func updateImageView(for photo: Photos) {
     
        //first convert the string date to Date() object 
        guard let convertedDate = DateFormatters.shared.inputDateFormatter.date(from: photo.date) else {
            return
        }
        let formattedDate = DateFormatters.shared.outPutDateFormatter.string(from: convertedDate)
        store.fetchImage(for: photo){[weak self] (imageResult) in
            switch imageResult {
            case let .success(image):
                self?.imageView.image = image
                self?.navigationItem.title = formattedDate
                
                self?.titleLabel.text = photo.title
                self?.descriptionTextView.text = photo.explanation
                
            case let .failure(error):
                print("Error downloading image: \(error)")
            }
            
        }
    }
    
    func showHUD() {
        let hud = JGProgressHUD()
        hud.textLabel.text = "Loading"
        hud.show(in: view)
        hud.dismiss(afterDelay: 3.2)
    }
    
    @objc func dateButtonTapped(_ sender: UIBarButtonItem) {
        //configure and present data picker
      
    }
    

    
    @objc func favsButtonTapped(_ sender: UIButton) {
                     //The notifications approach
//        NotificationCenter.default.post(name: Notification.Name("photo"), object: photo)
        
        //also tried this approach
        let vc = FavoritesViewController()
        vc.store = self.store
        vc.photo = self.photo
        //but without presenting it like you normally would

    }
}

//MARK: - configure views and constraints
extension HomeViewController {
    
    func constraints() {
        let heightConstraint = contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
        heightConstraint.priority = UILayoutPriority(250)
        
        NSLayoutConstraint.activate([
            
            
            scrollView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            
            
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            heightConstraint,
            
            //
            imageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
            imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            imageView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.3),
            
            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -40),
            titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 10),
            titleLabel.heightAnchor.constraint(equalToConstant: 70),
            
            dateButton.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 5),
            dateButton.topAnchor.constraint(equalTo: titleLabel.topAnchor, constant: 10),
            dateButton.widthAnchor.constraint(equalToConstant: 35),
            dateButton.heightAnchor.constraint(equalToConstant: 45),
            
            descriptionTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            descriptionTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            descriptionTextView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
            descriptionTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
            
        ])
    }
    
    func addSubviews() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        contentView.addSubview(imageView)
        contentView.addSubview(titleLabel)
        contentView.addSubview(descriptionTextView)
        contentView.addSubview(dateButton)
        dateButton.addTarget(self, action:  #selector(favsButtonTapped(_:)), for: .touchUpInside)
        
        
    }
    
   
}

and this is the FavoriteVC

import UIKit
import JGProgressHUD

class FavoritesViewController: UIViewController, UITableViewDelegate {
    var observer: NSObjectProtocol?

    var store: PhotoStore! {
        didSet {
            tableDatSource.store = store
           
        }
    }
    
    var photo: Photos! {
        didSet {
            tableDatSource.photos.append(photo)
            tableDatSource.photo = photo
        }
    }
    
   
    var tableView: UITableView?
    
    let tableDatSource = TableDataSource()
    
    let tableview: UITableView = {
        let table = UITableView(frame: .zero, style: .grouped)
//        table.translatesAutoresizingMaskIntoConstraints = false
        
        table.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier)
        return table
    }()
    
    //MARK: - lifecycle
    override func loadView() {
        super.loadView()
       setupTableView()
        tableview.reloadData()
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Favorites"
        view.backgroundColor = .systemBackground
        
   observer = NotificationCenter.default.addObserver(forName: Notification.Name("photo"),
                                                          object: nil, queue: .main,
                                                          using: {[weak self] notification in
            
            guard let photo = notification.object as? Photos else{return}
            self?.photo = photo
                   })
       print("Received photo and it's \(photo)")

       

    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
       
    }
   
    //MARK: - Table
    func setupTableView() {
        view.addSubview(tableview)
        tableview.dataSource = tableDatSource
        tableview.delegate = self
        tableview.translatesAutoresizingMaskIntoConstraints = false
        self.tableView = tableview
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        self.tableView?.frame = view.bounds
    }
    
   
 
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 95.0
    }
   
   
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
       
    }

I tried using three different approaches to pass along the photo. 1: I tried posting a notifications in the sender viewcontroller() and an observer in the receiver. 2: a Completion handler. 3: I also tried to instantiate the receiver viewconteroller() inside the function in which the tap event takes place.

All didn't work.


Solution

  • Your mistake is here:

        let vc = FavoritesViewController()
    

    When you say FavoritesViewController() you are telling the class to create a new instance of the FavoritesViewController. This is wrong. There is already an instance of the FavoritesViewController; it is the third child of the tab bar controller. And that is the instance you want to talk to right now.

    So what you need to do is:

    1. Build into FavoritesViewController a method whereby other code can hand it a newly selected favorite. This code will then respond by displaying the favorite or whatever it is you want to do.
    2. In HomeViewController, when the user selects a new favorite, you get a reference to the existing FavoritesViewController (which is easy to do because, as you already know, it is the third child in the tab view controller's array) and call that method. It is trivial for FavoritesViewController to get that reference, because a view controller automatically has a reference to its containing tab bar controller, and the tab bar controller hands you the array of its children.

    There is absolutely no need for the FavoritesViewController to be displayed in order to update its view's interface with the new favorite. The user will see it if and when the user switches to that third tab.