didSet 属性 值只出现在 print()
didSet property value only appears in print()
初始目标:
创建一个包含垂直放置的单元格列表的视图,显示一些信息。一旦用户单击单元格以显示包含更多信息的新视图。
路漫漫其修远兮(讨好我任性的儿子!):
- 我创建了 2 个视图控制器:ViewController(继承 UICollectionViewController、UICollectionViewDelegateFlowLayout)和 DetailViewController(子类化 UIViewController)。
- 我创建了一个 Cell 供 ViewController 用于生成集合视图和一个 DetailView 供 DetailViewController 使用
- 我创建了一个名为 Detail 的结构作为自定义数据类型,它使用属性(例如姓名、姓氏、地址等)提供数据存储
结构:
struct Detail: Decodable {
let name: String?
let surname: String?
let address: String?
let description: String?
}
我使用以下数据进行测试(测试完成后,我将从 API 调用中获取此数据)。我把它放在里面 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")]
使用上述信息和方法创建单元格:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
还有:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! Cell
由于该方法要求我们return一个UICollectionViewCell,我首先通过执行以下操作将关联信息发送到Cell:
cell.details = details[indexPath.item]
return cell
在 Cell 中,我使用 didSet 创建了以下 属性 来帮助我检索信息:
var details: Detail? {
didSet {
guard let details = details else { return }
guard let name = details.name else { return }
....
....
}
使用来自 ViewController 的信息您可以理解,我动态构建了每个单元格。
此时一切都很好。
然后我尝试在单击单元格时显示详细视图。为此,我在方法中遵循了相同的做法:
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)
}
同样,在 DetailView 中,我使用相同的方法获取与所选项目关联的数据。这样我就可以访问用户选择的单元格的数据,如下所示:
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?()
}
}
所以,我其实做的是在didSet之外设计view的静态部分,didSet里面的动态部分是什么。这适用于 collectionView 的单元格。
我使用 DetailViewController 来显示 DetailView 并在用户点击 "Close" 按钮。
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)
}
}
我这样做的原因是我喜欢让我的 ViewController 尽可能干净(Massive View Controller,不是我的事)。
问题
整个构建没有任何问题,但是当我单击一个单元格转到 DetailView 时,没有显示任何信息。
奇怪的部分
在 DetailView --> didSet 中,当我使用 print(name) 时,它工作正常(您在控制台中看到正确的名称)。但是当我尝试在视图中使用该值时,它不会显示。
而且我知道我的 DetailView 很好,因为如果我在其中使用硬编码值,它就可以工作(您会看到正确的结果)。
任何建议为什么这不能正常工作?
PS:我正在以编程方式构建整个东西。不涉及故事板。
提前致谢,对丢失的物品深表歉意post。
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)
}
你在这里创建一个 DetailView
,将它传递给你的 details
... 然后什么也不做。
通常 DetailView
是 DetailViewController
的 属性,您会将 details
传递给视图控制器,视图控制器会显示它。
这里发生的事情是您正在创建、配置和丢弃 DetailView
,而您可能应该使用 DetailViewController
拥有或应该拥有的那个。
如前所述,detailViewController
中未引用您的 detailView
。相反,您在 DetailViewController
中创建了另一个 DetailView
的实例,但是这个实例中没有 Detail
。
控制台消息是从您的 detailView
内部调用的,但 detailViewController
内部是另一个未调用此消息的实例,因为它的 Detail
默认设置为 nil。
简而言之,要解决这个问题,您只需进行以下更改:
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)
}
}
在你的 collectionView(...)
函数中:
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)
}
初始目标:
创建一个包含垂直放置的单元格列表的视图,显示一些信息。一旦用户单击单元格以显示包含更多信息的新视图。
路漫漫其修远兮(讨好我任性的儿子!):
- 我创建了 2 个视图控制器:ViewController(继承 UICollectionViewController、UICollectionViewDelegateFlowLayout)和 DetailViewController(子类化 UIViewController)。
- 我创建了一个 Cell 供 ViewController 用于生成集合视图和一个 DetailView 供 DetailViewController 使用
- 我创建了一个名为 Detail 的结构作为自定义数据类型,它使用属性(例如姓名、姓氏、地址等)提供数据存储
结构:
struct Detail: Decodable {
let name: String?
let surname: String?
let address: String?
let description: String?
}
我使用以下数据进行测试(测试完成后,我将从 API 调用中获取此数据)。我把它放在里面 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")]
使用上述信息和方法创建单元格:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
还有:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! Cell
由于该方法要求我们return一个UICollectionViewCell,我首先通过执行以下操作将关联信息发送到Cell:
cell.details = details[indexPath.item]
return cell
在 Cell 中,我使用 didSet 创建了以下 属性 来帮助我检索信息:
var details: Detail? {
didSet {
guard let details = details else { return }
guard let name = details.name else { return }
....
....
}
使用来自 ViewController 的信息您可以理解,我动态构建了每个单元格。
此时一切都很好。
然后我尝试在单击单元格时显示详细视图。为此,我在方法中遵循了相同的做法:
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)
}
同样,在 DetailView 中,我使用相同的方法获取与所选项目关联的数据。这样我就可以访问用户选择的单元格的数据,如下所示:
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?()
}
}
所以,我其实做的是在didSet之外设计view的静态部分,didSet里面的动态部分是什么。这适用于 collectionView 的单元格。
我使用 DetailViewController 来显示 DetailView 并在用户点击 "Close" 按钮。
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)
}
}
我这样做的原因是我喜欢让我的 ViewController 尽可能干净(Massive View Controller,不是我的事)。
问题
整个构建没有任何问题,但是当我单击一个单元格转到 DetailView 时,没有显示任何信息。
奇怪的部分
在 DetailView --> didSet 中,当我使用 print(name) 时,它工作正常(您在控制台中看到正确的名称)。但是当我尝试在视图中使用该值时,它不会显示。
而且我知道我的 DetailView 很好,因为如果我在其中使用硬编码值,它就可以工作(您会看到正确的结果)。
任何建议为什么这不能正常工作?
PS:我正在以编程方式构建整个东西。不涉及故事板。
提前致谢,对丢失的物品深表歉意post。
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)
}
你在这里创建一个 DetailView
,将它传递给你的 details
... 然后什么也不做。
通常 DetailView
是 DetailViewController
的 属性,您会将 details
传递给视图控制器,视图控制器会显示它。
这里发生的事情是您正在创建、配置和丢弃 DetailView
,而您可能应该使用 DetailViewController
拥有或应该拥有的那个。
如前所述,detailViewController
中未引用您的 detailView
。相反,您在 DetailViewController
中创建了另一个 DetailView
的实例,但是这个实例中没有 Detail
。
控制台消息是从您的 detailView
内部调用的,但 detailViewController
内部是另一个未调用此消息的实例,因为它的 Detail
默认设置为 nil。
简而言之,要解决这个问题,您只需进行以下更改:
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)
}
}
在你的 collectionView(...)
函数中:
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)
}