Swift: 如何在没有故事板的情况下更新容器视图中的数据

Swift: How to update data in Container View without storyboard

我的项目是在不使用故事板的情况下以编程方式创建的。又像Apple Music的miniPlayer,在tableView中点击一行,会更新miniPlayer(在containerView中)的数据。

我看到了一些故事板和 segue 的示例,如下面的代码:在父 viewController 中调用子 viewController 的方法以使用协议和委托更新数据。

但我不使用情节提要,那么 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
   }
}

我的代码:在根视图控制器(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)
    }
}

我想在点击 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)
    }

子视图中的更新方法。

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")
        }
    }
}

解决这个问题的方法不止一种...


第一种方法 - 无自定义委托:

使用子类 UITabBarController 作为“中介”。给它一个函数,例如:

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

然后,在您的“Select 歌曲”视图控制器(选项卡之一)中:

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)
}

第二种方法 - 使用自定义委托:

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

确保您的“迷你播放器”控制器符合委托:

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")
        }
    }

}

给你的“Select Song”视图控制器(以及任何其他选项卡控制器)一个委托变量:

class SelectSongViewController: UIViewController {

    var delegate: ContentDelegate?

    // everything else
}

然后,在你的子类中 UITabBarController:

override func viewDidLoad() {
    super.viewDidLoad()

    configureContainer()

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

现在您的“Select 歌曲”视图控制器可以调用委托函数:

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)
}