简体   繁体   中英

[Programmatically]Push a viewController when selecting tableView row embedded in a collectionView cell

When a user click on a tableView cell i want to push a view controller showing a detailed view of the user's task. I tried using a pop-up view because i don't know how to push a viewController from a collectionView cell but i can't connect the didselect to the MyTasksDetailController layout view. Here's my code:

MyTasksCollectionCell

class MyTasksCollectionCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource {

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
                 tableView.deselectRow(at: indexPath, animated: true)

    let detailView = MyTasksDetailController()
    UIApplication.shared.keyWindow?.addSubview(detailView)

// I want the detailViewController to show these two (descriptionTitleLabel + titleLabel)  
//          print("\(task.descriptionTitleLabel)") 
//          print("\(task.titleLabel)") 

}
extension UIApplication {
    
    var keyWindow: UIWindow? {
        // Get connected scenes
        return UIApplication.shared.connectedScenes
            // Keep only active scenes, onscreen and visible to the user
            .filter { $0.activationState == .foregroundActive }
            // Keep only the first `UIWindowScene`
            .first(where: { $0 is UIWindowScene })
            // Get its associated windows
            .flatMap({ $0 as? UIWindowScene })?.windows
            // Finally, keep only the key window
            .first(where: \.isKeyWindow)
    }
}

MyTasksDetailController

class MyTasksDetailController: UIView  {
    
    var setTitleLabel: String? {
        didSet {
            titleLabel.text = setTitleLabel ?? ""
        }
    }
    var setdescriptionTitleLabel: String? {
        didSet {
            descriptionTitleLabel.text = setdescriptionTitleLabel ?? ""
        }
    }
     let titleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont.systemFont(ofSize: 28, weight: .bold)
        label.textAlignment = .center
        return label
    }()
    
     let descriptionTitleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
        label.textAlignment = .center
        label.numberOfLines = 3
        return label
    }()
    
     let container: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.clipsToBounds = true
        v.backgroundColor = .white
        v.backgroundColor =
            // 1
            UIColor { traitCollection in
              // 2
              switch traitCollection.userInterfaceStyle {
              case .dark:
                // 3
                 v.layer.borderColor = UIColor.label.cgColor
                return UIColor.systemBackground
                
              default:
                // 4
                 v.layer.borderColor = UIColor.black.cgColor
                return UIColor.systemBackground
              }
            }
        return v
    }()
    
     lazy var stack: UIStackView = {
        let stack = UIStackView(arrangedSubviews: [titleLabel, descriptionTitleLabel])
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.axis = .vertical
        return stack
    }()
    @objc func animateOut() {
        UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
            self.container.transform = CGAffineTransform(translationX: self.frame.height, y: 0)
            self.alpha = 0
        }) { (complete) in
            if complete {
                self.removeFromSuperview()
            }
        }
    }
    @objc func animateIn() {
        self.container.transform = CGAffineTransform(translationX: self.frame.height, y: 0)
        self.alpha = 1
        UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
            self.container.transform = .identity
            self.alpha = 1
        })
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
         self.frame = UIScreen.main.bounds
        self.addSubview(container)

        self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(animateOut)))
        container.topAnchor.constraint(equalTo:  self.topAnchor, constant: 0).isActive = true
        container.bottomAnchor.constraint(equalTo:  self.bottomAnchor, constant: 0).isActive = true
        container.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true

         
        container.addSubview(stack)
        stack.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
        stack.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
        stack.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true
        stack.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 0.5).isActive = true
        animateIn()
     }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

First, if you want to present your MyTasksDetailController , it needs to be a UIViewController , not a UIView .

You can present it like this (from your main view controller):

// instantiate MyTasksDetailController
let vc = MyTasksDetailController()
// set the title and description string properties
vc.titleString = "Some title"
vc.descriptionString = "Some description"
// present it
self.present(vc, animated: true, completion: nil)

To get the selected table row from the collection view cell, you can use a closure .

In your collection view cell class, define the closure with:

var myClosure: ((MyTasksCollectionCell, Int) -> ())?

when you select a row in the table view:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // when a row is selected, use the
    //  Closure to inform the controller
    myClosure?(self, indexPath.row)
}

and your cellForItemAt func looks like this:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myTasksCollectionCell", for: indexPath) as! MyTasksCollectionCell
    
    cell.label.text = myData[indexPath.item].title
    cell.tableData = myData[indexPath.item].tasks
    
    // set closure
    cell.myClosure = { [weak self] theCell, theRow in
        guard let self = self,
              let theIndexPath = self.collectionView.indexPath(for: theCell)
        else {
            return
        }
        // instantiate MyTasksDetailController
        let vc = MyTasksDetailController()
        // set the title and description string properties
        vc.titleString = self.myData[theIndexPath.item].title
        vc.descriptionString = self.myData[theIndexPath.item].tasks[theRow]
        // present it
        self.present(vc, animated: true, completion: nil)
    }
    return cell
}

Here is a complete example you can run and play with...

Modified version of your Detail controller

class MyTasksDetailController: UIViewController  {
    
    var titleString: String = ""
    var descriptionString: String = ""
    
    let titleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont.systemFont(ofSize: 28, weight: .bold)
        label.textAlignment = .center
        label.backgroundColor = .green
        return label
    }()
    
    let descriptionTitleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
        label.textAlignment = .center
        label.numberOfLines = 3
        label.backgroundColor = .cyan
        return label
    }()
    
    let container: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.clipsToBounds = true
        v.backgroundColor = .white
        v.backgroundColor =
        // 1
        UIColor { traitCollection in
            // 2
            switch traitCollection.userInterfaceStyle {
            case .dark:
                // 3
                v.layer.borderColor = UIColor.label.cgColor
                return UIColor.systemBackground
                
            default:
                // 4
                v.layer.borderColor = UIColor.black.cgColor
                return UIColor.systemBackground
            }
        }
        return v
    }()
    
    lazy var stack: UIStackView = {
        let stack = UIStackView(arrangedSubviews: [titleLabel, descriptionTitleLabel])
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.axis = .vertical
        return stack
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(container)
        
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismissMe)))
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            container.topAnchor.constraint(equalTo: g.topAnchor, constant: 0),
            container.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0),
            container.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0),
            container.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0),
        ])
        
        container.addSubview(stack)
        NSLayoutConstraint.activate([
            stack.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0),
            stack.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 0),
            stack.centerYAnchor.constraint(equalTo: container.centerYAnchor, constant: 0),
            stack.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 0.5),
        ])

        titleLabel.text = titleString
        descriptionTitleLabel.text = descriptionString
    }
    
    @objc func dismissMe() {
        dismiss(animated: true, completion: nil)
    }
    
}

simple Data struct

struct MyTask {
    var title: String = ""
    var tasks: [String] = []
}

"main" view controller (holds the collection view):

class MyTasksViewController: UIViewController {
    
    var myData: [MyTask] = []
    
    let colors: [UIColor] = [
        .systemRed, .systemGreen, .systemBlue,
        .systemPink, .systemYellow, .systemTeal,
    ]
    
    var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let tableStrings: [[String]] = [
            ["red", "green", "blue", "cyan", "magenta", "yellow"],
            ["one", "two", "three"],
            ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
            ["Bob", "Joe", "Steve", "Mary"],
            ["Car", "Boat", "Train", "Airplane", "Bicycle"],
        ]
        
        // fill myData array with some data
        for i in 0..<tableStrings.count {
            let mt: MyTask = MyTask(title: "Cell Title \(i)", tasks: tableStrings[i])
            myData.append(mt)
        }
        
        let cvl = UICollectionViewFlowLayout()
        cvl.itemSize = CGSize(width: 200, height: 240)
        cvl.minimumLineSpacing = 10
        cvl.minimumInteritemSpacing = 0
        cvl.scrollDirection = .horizontal
        
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: cvl)
        
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(collectionView)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            collectionView.heightAnchor.constraint(equalToConstant: 240.0),
            
        ])
        
        collectionView.dataSource = self
        collectionView.delegate = self
        
        collectionView.register(MyTasksCollectionCell.self, forCellWithReuseIdentifier: "myTasksCollectionCell")
        
    }
    
}

extension MyTasksViewController: UICollectionViewDataSource, UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return myData.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myTasksCollectionCell", for: indexPath) as! MyTasksCollectionCell
        cell.contentView.backgroundColor = colors[indexPath.item % colors.count]
        
        cell.label.text = myData[indexPath.item].title
        cell.tableData = myData[indexPath.item].tasks
        
        // set closure
        cell.myClosure = { [weak self] theCell, theRow in
            guard let self = self,
                  let theIndexPath = self.collectionView.indexPath(for: theCell)
            else {
                return
            }
            // instantiate MyTasksDetailController
            let vc = MyTasksDetailController()
            // set the title and description string properties
            vc.titleString = self.myData[theIndexPath.item].title
            vc.descriptionString = self.myData[theIndexPath.item].tasks[theRow]
            // present it
            self.present(vc, animated: true, completion: nil)
        }
        return cell
    }
}

Simple Table view Cell

// simple table cell with a centered-text label
class MyTasksTableCell: UITableViewCell {
    let label: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.textAlignment = .center
        return v
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        contentView.addSubview(label)
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            label.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            label.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
        ])
    }
}

Collection View Cell with "top label" and tableView

// collection view cell with
//  label at the top and
//  table view below the label
class MyTasksCollectionCell: UICollectionViewCell {
    
    // closure to tell the controller that a tableView row was selected
    var myClosure: ((MyTasksCollectionCell, Int) -> ())?
    
    var tableData: [String] = [] {
        didSet {
            tableView.reloadData()
        }
    }
    
    let label: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        v.textAlignment = .center
        return v
    }()
    
    let tableView: UITableView = {
        let v = UITableView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        contentView.addSubview(label)
        contentView.addSubview(tableView)
        
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            label.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            label.heightAnchor.constraint(equalToConstant: 30.0),
            
            tableView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 0.0),
            tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
        ])
        
        tableView.separatorInset = .zero
        tableView.register(MyTasksTableCell.self, forCellReuseIdentifier: "myTasksTableCell")
        tableView.dataSource = self
        tableView.delegate = self
    }
}
extension MyTasksCollectionCell: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: "myTasksTableCell", for: indexPath) as! MyTasksTableCell
        c.label.text = tableData[indexPath.row]
        return c
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // when a row is selected, use the
        //  Closure to inform the controller
        myClosure?(self, indexPath.row)
    }
}

And here's what it looks like, with collection view scrolled to the right a few cells:

在此处输入图像描述

and what we get when we select a row:

在此处输入图像描述

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