Problem
I'm trying to fully understand building a Swift app entirely programmatically but I'm getting hung up on layout anchors. I have a tableview and if a row is selected it will push a new viewcontroller into the view.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let currentCell = tableView.cellForRow(at: indexPath)! as! CoinCell
if let coin = currentCell.coin {
let newViewController = CoinViewController(coin)
self.navigationController?.pushViewController(newViewController, animated: true)
}
tableView.deselectRow(at: indexPath, animated: true)
}
Below is my code for the viewcontroller that is being pushed. I'm able to see that the nameLabel has text while debugging but I can't seem to have the labels actually show in the view.
var coin: Coin? {
didSet {
nameLabel.text = coin?.name
}
}
init(_ coin: Coin) {
super.init(nibName: nil, bundle: nil)
self.coin = coin
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(nameLabel)
view.addSubview(testLabel)
setupView()
}
let nameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let testLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "TEST"
return label
}()
func setupView() {
nameLabel.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
nameLabel.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
nameLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true
nameLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
I'm still a beginner with this so I'm not sure of the best way to actually debug an issue like this. Thanks for the help.
Since you are precisely making sure that your controller receive data (or non optional data) via the initializer, you should replace the property observer (didSet) to just a simple property.
let coin: Coin
init(_ coin: Coin) {
self.coin = coin
super.init(nibName: nil, bundle: nil)
}
// now in viewDidLoad() you can set the text of your label i.e namelabel.text = self.coin.something
You try to set the text nameLabel in the property observer:
var coin: Coin? {
didSet {
nameLabel.text = coin?.name
}
}
But didSet will not be called from the initializer, therfore the label will remain empty.
In iOS (Cocoa Touch), you should fill your views after/within viewDidLoad (or viewDidAppear) or - in your case - in setupView:
func setupView() {
nameLabel.text = coin?.name
// ...
}
Related
I created table view cell programmatically and set the constrains as well.It is able to display the image , firstname , lastname property into cell but when I added new label with values , it not displaying the values .
Here is the cell code .
import UIKit
class PeopleCell: UITableViewCell {
static let identifier = "PeopleCell"
let containerView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true // this will make sure its children do not go out of the boundary
return view
}()
let profileImageView:UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill // image will never be strecthed vertially or horizontally
img.translatesAutoresizingMaskIntoConstraints = false // enable autolayout
img.layer.cornerRadius = 35
img.clipsToBounds = true
return img
}()
let firstnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let lastnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textColor = .white
label.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
label.layer.cornerRadius = 5
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let jobTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(profileImageView)
containerView.addSubview(firstnameTitleLabel)
containerView.addSubview(lastnameTitleLabel)
containerView.addSubview(jobTitleLabel)
self.contentView.addSubview(containerView)
profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:70).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:70).isActive = true
containerView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
containerView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
firstnameTitleLabel.topAnchor.constraint(equalTo:self.containerView.topAnchor).isActive = true
firstnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
firstnameTitleLabel.trailingAnchor.constraint(equalTo:self.containerView.trailingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
jobTitleLabel.topAnchor.constraint(equalTo:self.lastnameTitleLabel.bottomAnchor).isActive = true
jobTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
jobTitleLabel.topAnchor.constraint(equalTo:self.lastnameTitleLabel.bottomAnchor).isActive = true
jobTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(firstName: String, lastName: String,jobtitle: String ) {
firstnameTitleLabel.text = "Firstname :\(firstName)"
lastnameTitleLabel.text = "Lastname : \(lastName)"
jobTitleLabel.text = "Occupation : \(jobtitle)"
}
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
}
}
}
Here is the view controller code .
import UIKit
import Combine
class PeopleViewController: UIViewController {
var coordinator: PeopleBaseCoordinator?
init(coordinator: PeopleBaseCoordinator) {
super.init(nibName: nil, bundle: nil)
self.coordinator = coordinator
title = "People"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let viewModel = ViewModel()
private var subscribers = Set<AnyCancellable>()
private var activityIndicator = UIActivityIndicatorView(style: .medium)
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false
tableview.dataSource = self
tableview.prefetchDataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(PeopleCell.self, forCellReuseIdentifier: PeopleCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
setUpUI()
setUpBinding()
self.activityIndicator.stopAnimating()
// Do any additional setup after loading the view.
}
private func setUpUI() {
view.backgroundColor = .white
title = "People List "
view.addSubview(activityIndicator)
view.addSubview(tableView)
// self.tableView.rowHeight = 100.00
tableView.rowHeight = UITableView.automaticDimension
tableView.rowHeight = 100
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func setUpBinding() {
viewModel
.$peoples
.receive(on : RunLoop.main)
.sink { [weak self ] _ in
self?.tableView.reloadData()
}
.store(in: &subscribers)
viewModel.getPeople()
}
}
extension PeopleViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.peoples.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: PeopleCell.identifier, for: indexPath) as? PeopleCell
else { return UITableViewCell() }
let row = indexPath.row
let people = viewModel.peoples[row]
cell.configureCell(firstName: people.firstName, lastName: people.lastName,jobtitle: people.jobtitle)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
}
extension PeopleViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getPeople()
}
}
Here is the screenshot . As it not showing the job title property into cell .
I think your problem is the height of your containerView
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
I think 40 is to low to show the jobTitleLabel
I am new to swift .I want to display the records with image view in table view cell . I have defined the property with leadingAnchor , trailingAnchor, widthAnchor, heightAnchor with content view . But when I run the app it overlapping the view .
Here is the code in cell .
import UIKit
class PeopleCell: UITableViewCell {
static let identifier = "PeopleCell"
let containerView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true // this will make sure its children do not go out of the boundary
return view
}()
let profileImageView:UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill // image will never be strecthed vertially or horizontally
img.translatesAutoresizingMaskIntoConstraints = false // enable autolayout
img.layer.cornerRadius = 35
img.clipsToBounds = true
return img
}()
let firstnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let lastnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textColor = .white
label.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
label.layer.cornerRadius = 5
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(profileImageView)
containerView.addSubview(firstnameTitleLabel)
containerView.addSubview(lastnameTitleLabel)
self.contentView.addSubview(containerView)
profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:70).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:70).isActive = true
containerView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
containerView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
firstnameTitleLabel.topAnchor.constraint(equalTo:self.containerView.topAnchor).isActive = true
firstnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
firstnameTitleLabel.trailingAnchor.constraint(equalTo:self.containerView.trailingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(firstName: String, lastName: String) {
firstnameTitleLabel.text = "Firstname :\(firstName)"
lastnameTitleLabel.text = "Lastname : \(lastName)"
}
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
}
}
}
Here is the view controller code .
import UIKit
import Combine
class PeopleViewController: UIViewController {
var coordinator: PeopleBaseCoordinator?
init(coordinator: PeopleBaseCoordinator) {
super.init(nibName: nil, bundle: nil)
self.coordinator = coordinator
title = "People"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let viewModel = ViewModel()
private var subscribers = Set<AnyCancellable>()
var activityIndicator = UIActivityIndicatorView(style: .medium)
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false
tableview.dataSource = self
tableview.prefetchDataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(PeopleCell.self, forCellReuseIdentifier: PeopleCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
setUpUI()
setUpBinding()
self.activityIndicator.stopAnimating()
// Do any additional setup after loading the view.
}
private func setUpUI() {
view.backgroundColor = .white
title = "People List "
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func setUpBinding() {
viewModel
.$peoples
.receive(on : RunLoop.main)
.sink { [weak self ] _ in
self?.tableView.reloadData()
}
.store(in: &subscribers)
viewModel.getPeople()
}
}
extension PeopleViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.peoples.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: PeopleCell.identifier, for: indexPath) as? PeopleCell
else { return UITableViewCell() }
let row = indexPath.row
let people = viewModel.peoples[row]
cell.configureCell(firstName: people.firstName, lastName: people.lastName)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
extension PeopleViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getPeople()
}
}
Here is the result .
This is rather tricky because it seems your constraints are fine, assuming that your tableview height is 100, but the screenshot tableview cells seem a little shorter than 100. Let's assume the cell height is 100 correct.
I suggest you try configuring the imageView (and other views) in override func layoutSubViews(), which is a function that renders whenever the contentView's bound change. It should also be noted that better practice is where the imageSize is relative to the cell/contentView's frame instead of hardcoded values.
So it should look like
import UIKit
class PeopleCell: UITableViewCell {
let profileImageView:UIImageView = {
let img = UIImageView()
return img
}()
override func layoutSubviews() {
super.layoutSubviews()
profileImageView.translatesAutoresizingMaskIntoConstraints = false profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:self.frame.width * 0.7).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:self.frame.width * 0.7).isActive = true
//You may want to try with other type of contentMode such as aspectFit, etc
profileImageView.contentMode = .scaleAspectFill
profileImageView.layer.cornerRadius = self.frame.width / 2
profileImageView.clipsToBounds = true
}
//If above doesn't work, you may want to look into the imageConfiguration function you made and ensure that contentMode is applied properly.
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
self?.profileImageView.contentMode = .scaleAspectFill
}
}
If all of the above code doesn't work, try to find the profileImageView size values by using breakpoints or ViewHierarchy within Xcode. To check the height of image or cell itself, they should be sufficient for you to find clues to resolve the issue.
All the best.
I have a ViewController with a CollectionView inside and a CollectionViewCell with a TableView inside. When the user click on a TableViewCell i want to present a ViewController showing a detailed view of the user's task but i get this error "Missing argument for parameter 'coder' in call" at this line let vc = MyTasksDetailController().
Here's my code :
ProfileController
final class ProfileController: UIViewController {
private var collectionView: UICollectionView?
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 1
layout.minimumInteritemSpacing = 1
layout.sectionInset = UIEdgeInsets(top: 0, left: 1, bottom: 0, right: 1)
let size = (view.width - 4)/3
layout.itemSize = CGSize(width: size, height: size)
collectionView = UICollectionView(frame: .zero,
collectionViewLayout: layout)
// Headers
collectionView?.register(ProfileInfoHeader.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: ProfileInfoHeader.identifier)
collectionView?.register(MyTasksCollectionCell.self,
forCellWithReuseIdentifier: MyTasksCollectionCell.identifier)
collectionView?.delegate = self
collectionView?.dataSource = self
guard let collectionView = collectionView else {
return
}
view.addSubview(collectionView)
}
extension ProfileController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyTasksCollectionCell.identifier,
for: indexPath)as! MyTasksCollectionCell
return cell
}
MyTaskCollectionCell
class MyTasksCollectionCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let task = {() -> Add in
switch (displayedTask) {
case .current:
// First segment tapped
return self.tasks[indexPath.row]
case past:
// Second segment tapped
return self.pastTasks[indexPath.row]
}
}()
let vc = MyTasksDetailController() //ERROR HERE: Missing argument for parameter 'coder' in call : Insert 'coder: <#NSCoder#>'
self.present(vc, animated: true, completion: nil)
}
MyTaskTableCell
class MyPostsTableCell: UITableViewCell {
var setdescriptionTitleLabel: String? {
didSet {
descriptionTitleLabel.text = setdescriptionTitleLabel ?? ""
}
}
var setdateLabel: String? {
didSet {
dateLabel.text = setdateLabel ?? ""
}
}
var sethourLabel: String? {
didSet {
hourLabel.text = sethourLabel ?? ""
}
}
var setDateIcon: UIImage? {
didSet {
dateIcon.image = UIImage()
}
}
var setHourIcon: UIImage? {
didSet {
hourIcon.image = UIImage()
}
}
MyTasksDetailController
class MyTasksDetailController: UIViewController {
internal var task: Add? {
didSet {
if let task = task {
setDescriptionLabel = task.description
setDescriptionTitleLabel = task.descriptionTitle
}
}
}
var setDescriptionLabel: String? {
didSet {
descriptionLabel.text = setDescriptionLabel ?? ""
}
}
var setdescriptionTitleLabel: String? {
didSet {
descriptionTitleLabel.text = setdescriptionTitleLabel ?? ""
}
}
let descriptionLabel: 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.layer.cornerRadius = 24
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 stackContainer: UIStackView = {
let stackContainer = UIStackView(arrangedSubviews: [stackDesLabel, stackDesTitLabel])
stackContainer.translatesAutoresizingMaskIntoConstraints = false
stackContainer.axis = .vertical
stackContainer.distribution = UIStackView.Distribution.fillEqually
return stackContainer
}()
lazy var stackDesLabel: UIStackView = {
let stackDesLabel = UIStackView(arrangedSubviews: [descriptionLabel])
stackDesLabel.translatesAutoresizingMaskIntoConstraints = false
stackDesLabel.axis = .vertical
stackDesLabel.distribution = UIStackView.Distribution.fillProportionally
return stackDesLabel
}()
lazy var stackDesTitLabel: UIStackView = {
let stackDesTitLabel = UIStackView(arrangedSubviews: [descriptionTitleLabel])
stackDesTitLabel.translatesAutoresizingMaskIntoConstraints = false
stackDesTitLabel.axis = .horizontal
stackDesTitLabel.distribution = UIStackView.Distribution.fillEqually
return stackDesTitLabel
}()
override func viewDidLoad() {
view.addSubview(stackDesLabel)
view.addSubview(stackDesTitLabel)
stackContainer.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
stackContainer.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
stackContainer.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true
stackContainer.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true
stackContainer.heightAnchor.constraint(equalTo: container.heightAnchor).isActive = true
stackContainer.widthAnchor.constraint(equalTo: container.widthAnchor).isActive = true
stackDesTitLabel.topAnchor.constraint(equalTo: stackContainer.topAnchor, constant: 50).isActive = true
stackDesTitLabel.widthAnchor.constraint(equalTo: stackContainer.widthAnchor).isActive = true
stackDesTitLabel.centerXAnchor.constraint(equalTo: stackContainer.centerXAnchor).isActive = true
stackDesLabel.topAnchor.constraint(equalTo: stackDesTitLabel.bottomAnchor, constant: 50).isActive = true
stackDesLabel.leadingAnchor.constraint(equalTo: stackContainer.leadingAnchor, constant: 5).isActive = true
stackDesLabel.widthAnchor.constraint(equalTo: stackContainer.widthAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Add(Data struct)
struct Add {
static var details: Add = Add()
var descriptionTitle: String = ""
var description: String = ""
var id: String?
var date: String = ""
var hour: String = ""
func getDict() -> [String: Any] {
let dict = [
"descriptionTitle": self.descriptionTitle,
"description": self.description,
"date": self.date,
"hour": self.hour,
] as [String : Any]
return dict
}
}
As I suspected, the issue is in MyTasksDetailController with your implementation of
required init?(coder aDecoder: NSCoder)
If you just want a quick solution, you can do one of 2 things based on what makes the most sense to your application:
Remove the required init?(coder aDecoder: NSCoder)
Add your own initializer like this:
init() {
// Keep nil if you are not initializing from XIB
super.init(nibName: nil, bundle: nil)
}
Explanation
What you tried to achieve breaks the rules of initialization inheritance I believe which is why you get that error.
I am looking at all rules from here.
When you do this: class MyTasksDetailController: UIViewController - you are automatically inheriting the initializers of from UIViewController if you follow these rules:
Automatic Initializer Inheritance
Rule 1
If your subclass doesn’t define any designated initializers, it
automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its
superclass designated initializers—either by inheriting them as per
rule 1, or by providing a custom implementation as part of its
definition—then it automatically inherits all of the superclass
convenience initializers.
These rules apply even if your subclass adds
further convenience initializers.
So now when you added:
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
It breaks rule 1, you are adding your own designated initializer and so you lost all other initializers you inherited previous so you get this error:
So now you have 2 options as described earlier.
1. Remove the required init?(coder aDecoder: NSCoder)
This version of init is needed if you create view controllers in storyboard as it is called automatically called in that situation. If you are not planning on doing that you can remove it. If you are planning on using it in storyboard, you need to complete it:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
2. Add your own designated initializer
By adding required init?(coder aDecoder: NSCoder), you lost the default initializer you were inheriting before and so now you need to make your own:
init() {
// Initialize your local vars
// Call designated initializer from the super class
// Keep nil if you are not initializing from XIB
super.init(nibName: nil, bundle: nil)
}
Either of these should fix your problem:
Update with final thoughts
The above should fix your errors with initialization
However, you will have an error on the next line self.present(vc, animated: true, completion: nil)
In short, present (documentation reference) can only be called by view controllers.
In your code MyTasksCollectionCell is a UICollectionViewCell so you need to notify the view controller that your collection view is in that a cell was tapped and the view controller needs to handle presenting the new view.
You can do this in two ways:
Delegates - give responsibility to your view controller to handle cell tap and present another view controller
Observers - notify your view controller to handle cell tap and present another view controller
Both of these will
These are just quick links to give you an idea but I suggest googling bit more on them to see which is the best for your situation
Does anyone know why the code complains when I run the simulator of the application in xCode v12?
I get the error message on the line:
cell.likeControl.isFavored = request.isFavored <== Thread 1: EXC_BAD_ACCESS (code=2, address=0x7fff86e8d750)
The code will run fine when I comment out the line.
SOURCE CODE
RequestTableViewController.swift
import UIKit
class RequestTableViewController: UITableViewController {
//MARK: Properties
var requests = [Request]()
override func viewDidLoad() {
super.viewDidLoad()
loadSampledData()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
(...)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "RequestTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? RequestTableViewCell else {
fatalError("The dequeued cell is not an instance of RequestTableViewCell.")
}
// Fetches the appropriate request for the data source layout.
let request = requests[indexPath.row]
cell.wordLabel.text = request.word
cell.countLabel.text = String(request.count)
cell.likeControl.isFavored = request.isFavored
return cell
}
//MARK: Private methods
private func loadSampledData() {
guard let request1 = Request(word: "Ghetto", count: 5, isFavored: false) else {
fatalError("Unable to instantiate request1")
}
guard let request2 = Request(word: "Skyskraber", count: 0, isFavored: false) else {
fatalError("Unable to instantiate request2")
}
guard let request3 = Request(word: "Hjem", count: 1, isFavored: true) else {
fatalError("Unable to instantiate request3")
}
print(requests)
requests += [request1, request2, request3]
}
}
RequestTableViewCell.swift
import UIKit
class RequestTableViewCell: UITableViewCell {
#IBOutlet weak var wordLabel: UILabel!
#IBOutlet weak var countLabel: UILabel!
#IBOutlet weak var thumbsControl: ThumbsControl!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
LikeControl.swift
import UIKit
#IBDesignable class LikeControl: UIStackView {
//MARK: Properties
private var thumbsImage = UIButton()
var isFavored = false
//MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupButton()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setupButton()
}
//MARK: Button action
#objc func likeButtonTapped(button: UIButton) {
isFavored.toggle()
print("Button updated")
}
//MARK: Private Methods
private func setupButton() {
let bundle = Bundle(for: type(of: self))
let emptyUp = UIImage(named: "thumbs-up-regular", in: bundle, compatibleWith: self.traitCollection)
let filledUp = UIImage(named: "thumbs-up-solid", in: bundle, compatibleWith: self.traitCollection)
let highlightedUp = emptyUp?.withTintColor(.blue)
let emptyDown = UIImage(named: "thumbs-down-regular", in: bundle, compatibleWith: self.traitCollection)
let button = UIButton()
button.setImage(emptyUp, for: .normal)
button.setImage(filledUp, for: .selected)
button.setImage(highlightedUp, for: .highlighted)
button.setImage(emptyDown, for: [.highlighted, .selected])
// Setup the button action
button.addTarget(self, action: #selector(LikeControl.likeButtonTapped(button:)), for: .touchUpInside)
print("Button created")
}
}
Request.swift
import Foundation
class Request {
//MARK: Properties
var word: String
var count: Int
var isFavored: Bool
//MARK: Initialization
init?(word: String, count: Int, isFavored: Bool) {
guard !word.isEmpty else {
return nil
}
guard count >= 0 else {
return nil
}
guard !isFavored || (isFavored && count > 0) else {
return nil
}
self.word = word
self.count = count
self.isFavored = isFavored
}
}
i'm build a simple app that allows you to reserve information equipment or a seat in a classroom. In my table view i insert an image and some text. How can i add a text field and a button in the bottom that send me a mail with the summary of the booking?
This is the code that i build until now.
TableViewController
import UIKit
struct CellData {
let image : UIImage?
let message : String?
}
class TableViewController: UITableViewController {
var data = [CellData] ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
data = [CellData.init(image: imageLiteral(resourceName: "printer"), message: "Stampante 3D"),CellData.init(image: imageLiteral(resourceName: "printer"), message: "Stampante 3D"),CellData.init(image: imageLiteral(resourceName: "printer"), message: "Stampante 3D")]
self.tableView.register(CustomCell.self, forCellReuseIdentifier: "custom")
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 200
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "custom") as! CustomCell
cell.mainImage = data[indexPath.row].image
cell.message = data[indexPath.row].message
cell.layoutSubviews()
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
}
Custom Cell
import Foundation
import UIKit
class CustomCell: UITableViewCell {
var message : String?
var mainImage : UIImage?
var messageView : UITextView = {
var textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isScrollEnabled = false
return textView
}()
var mainImageView : UIImageView = {
var imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(mainImageView)
self.addSubview(messageView)
mainImageView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
mainImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
mainImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
mainImageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
mainImageView.heightAnchor.constraint(equalToConstant: 100).isActive = true
messageView.leftAnchor.constraint(equalTo: self.mainImageView.rightAnchor).isActive = true
messageView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
messageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
messageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
}
override func layoutSubviews() {
super.layoutSubviews()
if let message = message {
messageView.text = message
}
if let image = mainImage{
mainImageView.image = image
} }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Thanks!
I think you are referring to create a chat like window, if this is correct, one way to resolve this is add handlers for the keyboard events, in order to move the top views. In this case you can start with the following ones:
First you need to add some observers to the Notification center to listen when the keyboard is shown or when is hidden.
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
Then, you need to create the functions to be triggered when the events occurs. As you can see in the following code, the view frame of the view is modified accordingly to the keyboardSize.
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y -= keyboardSize.height
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y += keyboardSize.height
}
}
So, just for clarification, you need to create a second view below the table, in which you will add the textfield and the send button.