INITIAL GOAL:
Have a view with a list of cells positioned vertically displaying some information. As soon as the user clicks on a cell to show a new view with more information.
THE ROAD SO FAR (curry on my wayward son!):
The struct:
struct Detail: Decodable {
let name: String?
let surname: String?
let address: String?
let description: String?
}
I use the following data for testing (after the testing is done I will get this data from an API call). I placed it inside ViewController:
let details: [Detail] = [Detail(name: "Chris", surname: "Doe", address: "Neverland 31", description: "This is a description about Chris Doe"), Detail(name: "Tony", surname: "Cross", address: "Galaxy Road 1", description: "This is a description about Tony Cross")]
To create the cells using the information above and the method:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
And also:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! Cell
As the method requires us to return a UICollectionViewCell, I first send the associated information to Cell by doing the following:
cell.details = details[indexPath.item]
return cell
Inside the Cell I created the following property using didSet to help me retrieve the information:
var details: Detail? {
didSet {
guard let details = details else { return }
guard let name = details.name else { return }
....
....
}
As you can understand using the information coming from ViewController I dynamically constructed each cell.
All were good at this point.
Then I tried to show a detailed view when clicking on a cell. To do this I followed the same practice inside the method:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailView = DetailView()
detailView.details = details[indexPath.item]
let detailViewController = DetailViewController()
detailViewController.modalTransitionStyle = .coverVertical
self.present(detailViewController, animated: true, completion: nil)
}
Again, in the DetailView I use the same approach to get the data associated with the selected item. This way I can have access to the data of the cell the user selects, as shown below:
import UIKit
class DetailView: UIView {
var dismissDetailViewAction: (() -> Void)?
var details: Detail? {
didSet {
// get details
guard let details = details else { return }
guard let name = details.name else { return }
guard let surname = details.surname else { return }
guard let address = details.address else { return }
guard let description = details.description else { return }
// print description and it shows in the console but not in the view
print(description)
let attributedTextDescription = NSMutableAttributedString(string: description, attributes: [NSAttributedStringKey.font: UIFont.FontBook.AvertaRegular.of(size: 20), NSAttributedStringKey.foregroundColor: UIColor.white])
briefDescription.attributedText = attributedTextDescription
briefDescription.textAlignment = .center
briefDescription.textColor = .white
briefDescription.numberOfLines = 0
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not yet been implemented")
}
fileprivate func setupView() {
setupDescriptionText()
setupCloseButton()
}
let briefDescription: UITextView = {
let text = UITextView()
text.textColor = .red
return text
}()
let closeButton: UIButton = {
let button = UIButton(title: "Close", font: UIFont.FontBook.AvertaRegular.of(size: 18), textColor: .white, cornerRadius: 5)
button.backgroundColor = .black
button.addTarget(self, action: #selector(closeDetailView), for: .touchUpInside)
return button
}()
fileprivate func setupDescriptionText() {
self.addSubview(briefDescription)
briefDescription.translatesAutoresizingMaskIntoConstraints = false
briefDescription.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5).isActive = true
briefDescription.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -5).isActive = true
briefDescription.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
briefDescription.heightAnchor.constraint(equalToConstant: 300).isActive = true
}
fileprivate func setupCloseButton() {
self.addSubview(closeButton)
closeButton.translatesAutoresizingMaskIntoConstraints = false
closeButton.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
closeButton.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
closeButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 40).isActive = true
closeButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -40).isActive = true
closeButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
}
@objc func closeDetailView() {
dismissDetailViewAction?()
}
}
So, what I actually do is to design the static part of the view outside didSet, and what is dynamic part inside didSet. This works with the cells of collectionView.
I use the DetailViewController to display the DetailView and dismiss itself when the user clicks on the "Close" button.
import UIKit
class DetailViewController: UIViewController {
// reference DetailView view
var detailView: DetailView!
override func viewDidLoad() {
super.viewDidLoad()
// setup view elements
setupView()
}
fileprivate func setupView() {
let mainView = DetailView(frame: self.view.frame)
self.detailView = mainView
self.view.addSubview(detailView)
self.homeDetailView.dismissDetailViewAction = dismissDetailView
// pin view
self.detailView.translatesAutoresizingMaskIntoConstraints = false
self.detailView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.detailView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.detailView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
self.detailView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
}
fileprivate func dismissDetailView() {
// dismiss current (DetailViewController) controller
self.dismiss(animated: true, completion: nil)
}
}
The reason I did this is that I like to keep my ViewControllers as clean as possible (Massive View Controller, not my thing).
THE PROBLEM
The whole thing is built without any problem, but when I click on a cell to go to the DetailView no information is displayed.
THE WEIRD PART
Inside the DetailView --> didSet, when I use print(name), it works just fine (you see the correct name inside console). But when I try to use that value inside the view it will not be displayed.
And I know that my DetailView is just fine since if I use hardcoded values in it, it works (you see the correct result).
Any advise why this is not working properly?
PS: I am building the whole thing programmatically. No storyboards involved.
Thanks in advance and sorry for the lost post.
As was mentioned, your detailView
is not referenced inside detailViewController
. Instead, you create another instance of DetailView
inside DetailViewController
but this one has no Detail
in it.
The console message was called from inside your detailView
, but inside detailViewController
is another instance that did not call this message, because its Detail
is set to nil by default.
To be short, to fix that you should simply do the following changes:
import UIKit
class DetailViewController: UIViewController {
var detail: Detail!
private lazy var detailView: DetailView = {
let mainView = DetailView(frame: self.view.frame)
mainView.details = detail
return mainView
}
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
fileprivate func setupView() {
self.view.addSubview(detailView)
self.homeDetailView.dismissDetailViewAction = dismissDetailView
// pin view
self.detailView.translatesAutoresizingMaskIntoConstraints = false
self.detailView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.detailView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.detailView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
self.detailView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
}
fileprivate func dismissDetailView() {
// dismiss current (DetailViewController) controller
self.dismiss(animated: true, completion: nil)
}
}
And inside your collectionView(...)
func:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailViewController = DetailViewController()
detailViewController.detail = details[indexPath.item]
detailViewController.modalTransitionStyle = .coverVertical
self.present(detailViewController, animated: true, completion: nil)
}