Swift: How to update data in Container View without storyboard

274 Views Asked by At

My project is created programmatically without using storyboard. And it is like Apple Music's miniPlayer, when clicking a row in tableView, will update the data of miniPlayer(which is in containerView).

I see some examples with storyboard and segue like below code: call child viewController's method in parent viewController to update data by using protocol & delegate.

But I don't use storyboard, so what is the alternative code to prepare()?

 protocol ContentDelegate {
    func updateContent(id: Int)
 }

 class ParentViewController: UIViewController {
    var delegate: ContentDelegate?

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        if (segue.identifier == "containerController") {
            let containerVC = segue.destination  as!   ChildContainerViewController

            self.delegate = containerVC
        }        
    }
}

class ChildContainerViewController: UIViewController, ContentDelegate {
   func updateContent(id: Int) {
     // your code
   }
}

My Code: add container view in the root view controller(UITabViewController).

class ViewController: UITabBarController {
    
    // mini player
    var miniPlayer: MiniPlayerViewController?
    
    // container view
    var containerView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // set tabBar and other stuff
        ...
        configureContainer()   
    }
    
    func configureContainer() {
        
        // add container
        containerView = UIView()
        containerView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(containerView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            containerView.bottomAnchor.constraint(equalTo: tabBar.topAnchor),
            containerView.heightAnchor.constraint(equalToConstant: 64.0)
        ])
        
        // add child view controller view to container
        miniPlayer = MiniPlayerViewController()
        guard let miniPlayer = miniPlayer else { return }
        addChild(miniPlayer)
        miniPlayer.view.translatesAutoresizingMaskIntoConstraints = false
        containerView.addSubview(miniPlayer.view)
        
        
        // Create and activate the constraints for the child’s view.
        guard let miniPlayerView = miniPlayer.view else { return }
        NSLayoutConstraint.activate([
            miniPlayerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            miniPlayerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            miniPlayerView.topAnchor.constraint(equalTo: containerView.topAnchor),
            miniPlayerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
        ])
        
        miniPlayer.didMove(toParent: self)
    }
}

I want to trigger the update when clicking the row in parentView.

protocol ContentDelegate {
    func configure(songs: [Song]?, at index: Int)
}

class SongsListViewController: UIViewController {
    private var tableView: UITableView!
    var delegate: ContentDelegate?
    
    // MARK: - data source
    var songs = [Song]()
    . . .

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let index = indexPath.row
        let vc = MiniPlayerViewController()
        self.delegate = vc
        self.delegate?.configure(songs: songs, at: index)
//        present(vc, animated: true)
    }

The update method in child view.

extension MiniPlayerViewController {
    
    func configure(songs: [Song]?, at index: Int) {
        if let songs = songs {
            let song = songs[index]
            songTitle.text = song.title
            thumbImage.image = song.artwork?.image
        } else {
            // placeholder fake info
            songTitle.text = "你在终点等我"
            thumbImage.image = UIImage(named: "Wang Fei")
        }
    }
}
1

There are 1 best solutions below

1
DonMag On BEST ANSWER

There is more than one approach to this...


First approach - no custom delegate:

Use the subclassed UITabBarController as an "intermediary". Give it a func such as:

func configure(songs: [Song]?, at index: Int) -> Void {
    miniPlayer.configure(songs: songs, at: index)
}

then, in your "Select Song" view controller (one of the tabs):

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    guard let tbc = self.tabBarController as? CustomTabBarController else {
        return
    }
    let index = indexPath.row
    tbc.configure(songs: songs, at: index)
}

Second approach - using a custom delegate:

protocol ContentDelegate {
    func configure(songs: [Song]?, at index: Int)
}

Make sure your "mini player" controller conforms to the delegate:

class MiniPlayerViewController: UIViewController, ContentDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        // add UI elements, any other setup code
    }
    
}

extension MiniPlayerViewController {
    
    func configure(songs: [Song]?, at index: Int) {
        if let songs = songs {
            let song = songs[index % songs.count]
            songTitle.text = song.title
            thumbImage.image = song.artwork
        } else {
            // placeholder fake info
            songTitle.text = "你在终点等我"
            thumbImage.image = UIImage(named: "Wang Fei")
        }
    }

}

Give your "Select Song" view controller (and any other of the tab controllers) a delegate var:

class SelectSongViewController: UIViewController {

    var delegate: ContentDelegate?

    // everything else
}

then, in your subclassed UITabBarController:

override func viewDidLoad() {
    super.viewDidLoad()

    configureContainer()

    if let vc = viewControllers?.first as? SelectSongViewController {
        vc.delegate = miniPlayer
    }
    
}

now your "Select Song" view controller can call the delegate func:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    guard let tbc = self.tabBarController as? CustomTabBarController else {
        return
    }
    let index = indexPath.row
    delegate?.configure(songs: songs, at: index)
}