Separator line appear early than cell title and image goes beyond of UIImageView - swift

I programmatically created UITableView and Cell. All work good, but I have a couple questions about constraints and auto layout:
Constraints with layoutMarginsGuide:
In screens where width 375, lower separator and cell starts from one point. On big size screens, separator appears earlier than the cell title(you can see this in attached screenshot). How I can fix this?
The image of the red rectangle with round corners in the attached screenshot goes beyond of UIImageView. I added constraints to UIImageView, but this not help me. How I can fix this?
Constraints without layoutMarginsGuide:
I want that stackView to cover the whole cell. How can I add constraints without using layoutMarginsGuide and avoid console warnings?
How can I add height constraints? I want that my stackView or title was 100 pt.
Screenshot, cell icon, and code you can found below.
Thanks for your answer.
Rectangle with rounded corners
Screenshot:
Code:
import Foundation
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// MARK: - Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
let tableViewstyle = UITableView.Style.insetGrouped
let tableView = UITableView(frame: CGRect(), style: tableViewstyle)
tableView.delegate = self
tableView.dataSource = self
tableView.register(Cell1.self, forCellReuseIdentifier: String(Cell1.description()))
tableView.tableFooterView = UIView()
tableView.translatesAutoresizingMaskIntoConstraints = false
self.view.backgroundColor = .systemBackground
self.view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.widthAnchor.constraint(equalTo: self.view.widthAnchor),
tableView.heightAnchor.constraint(equalTo: self.view.heightAnchor)
])
}
// MARK: - numberOfSections
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// MARK: - numberOfRowsInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 7
}
// MARK: - cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(Cell1.description())) as! Cell1
cell.title.text = "Cell \(indexPath.row)"
cell.icon2.image = UIImage(named: "Icon")
return cell
}
}
// MARK: - Cell
class Cell1: UITableViewCell {
// MARK: - Properties
let title = UILabel()
let icon2 = UIImageView()
let stackView = UIStackView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Off translatesAutoresizingMaskIntoConstraints
title.translatesAutoresizingMaskIntoConstraints = false
icon2.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
// Set constraints
icon2.addConstraint(NSLayoutConstraint(item: icon2, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30))
icon2.addConstraint(NSLayoutConstraint(item: icon2, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30))
// StackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
// TitleAndDetailStackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
// Hugging
title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
// Resistance
title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
icon2.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
title.textAlignment = .left
title.numberOfLines = 0
// Highlight stackView and set colors
stackView.addBackground(color: .green)
title.textColor = .label
icon2.backgroundColor = .cyan
// Add title and detail
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(icon2)
icon2.contentMode = .center
// Add to subview
self.contentView.addSubview(stackView)
// Get layoutMarginsGuide
let layoutMarginsGuide = contentView.layoutMarginsGuide
// Set constraints
NSLayoutConstraint.activate([
// Constrain all 4 sides of the stack view to the content view's layoutMarginsGuide
stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 0.0),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
// MARK: - AddBackground
extension UIStackView {
func addBackground(color: UIColor) {
let subView = UIView(frame: bounds)
subView.backgroundColor = color
subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
insertSubview(subView, at: 0)
}
}

To achieve this we need to change constraints to contentView. and set leadingAnchor.constraint constant equal to 16.0.
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),
])
Change icon2.contentMode = .center to icon2.contentMode = .scaleAspectFit
StackView that cover the whole cell:
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),
])
To change cell height we can simply add height constraint to any object inside the cell:
title.addConstraint(NSLayoutConstraint(item: title, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100))

Related

How to make a dynamic change in the height of the ScrollView on which the TableView is added? Swift

Friends, I need help, I spent 2 days on solving this issue. I need to make my ScrollView dynamically change its height based on the amount of text I add to the tableView. The problem is that if I don't strictly set the scroll.contentSize.height height, then the ability to scroll the screen is disabled.
private let scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.contentSize.height = 4000
scroll.backgroundColor = UIColor.white
scroll.translatesAutoresizingMaskIntoConstraints = false
return scroll
}()
func setConstraints() {
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
NSLayoutConstraint.activate([
headLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
headLabel.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 45)
])
NSLayoutConstraint.activate([
ellipseBabyImage.topAnchor.constraint(equalTo: stackViewName.bottomAnchor, constant: 75),
ellipseBabyImage.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
ellipseBabyImage.heightAnchor.constraint(equalToConstant: 131.3),
ellipseBabyImage.widthAnchor.constraint(equalToConstant: 145)
])
NSLayoutConstraint.activate([
goButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
goButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40),
goButton.leadingAnchor.constraint(equalTo: view.leadingAnchor,constant: 20),
goButton.trailingAnchor.constraint(equalTo: view.trailingAnchor,constant: -20),
goButton.heightAnchor.constraint(equalToConstant: 48)
])
NSLayoutConstraint.activate([
characterLabel.topAnchor.constraint(equalTo: ellipseBabyImage.bottomAnchor, constant: 68.35),
characterLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor)
])
NSLayoutConstraint.activate([
stackViewName.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
stackViewName.topAnchor.constraint(equalTo: headLabel.topAnchor,constant: 44),
])
NSLayoutConstraint.activate([
leftBlockStack.topAnchor.constraint(equalTo: fullNameLabel.topAnchor, constant: 60),
leftBlockStack.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 28),
leftBlockStack.rightAnchor.constraint(equalTo: ellipseBabyImage.leftAnchor,constant: -10)
])
NSLayoutConstraint.activate([
rightBlockStack.topAnchor.constraint(equalTo: fullNameLabel.topAnchor, constant: 60),
rightBlockStack.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -28),
rightBlockStack.leftAnchor.constraint(equalTo: ellipseBabyImage.rightAnchor,constant: 10)
])
NSLayoutConstraint.activate([
arrayStackCharacteristicOne.topAnchor.constraint(equalTo: characterLabel.bottomAnchor,constant: 16),
arrayStackCharacteristicOne.centerXAnchor.constraint(equalTo: view.centerXAnchor),
arrayStackCharacteristicOne.leftAnchor.constraint(equalTo: scrollView.leftAnchor,constant: 22),
healthProgressView.heightAnchor.constraint(equalToConstant: 5),
healthProgressView.widthAnchor.constraint(equalToConstant: 90),
willpowerProgressView.heightAnchor.constraint(equalToConstant: 5),
willpowerProgressView.widthAnchor.constraint(equalToConstant: 90),
luckProgressView.heightAnchor.constraint(equalToConstant: 5),
luckProgressView.widthAnchor.constraint(equalToConstant: 90),
])
NSLayoutConstraint.activate([
arrayStackCharacteristicTwo.topAnchor.constraint(equalTo: arrayStackCharacteristicOne.bottomAnchor,constant: 16),
arrayStackCharacteristicTwo.centerXAnchor.constraint(equalTo: view.centerXAnchor),
arrayStackCharacteristicTwo.leftAnchor.constraint(equalTo: scrollView.leftAnchor,constant: 22),
talentProgressView.heightAnchor.constraint(equalToConstant: 5),
talentProgressView.widthAnchor.constraint(equalToConstant: 90),
successProgressView.heightAnchor.constraint(equalToConstant: 5),
successProgressView.widthAnchor.constraint(equalToConstant: 90),
creationProgressView.heightAnchor.constraint(equalToConstant: 5),
creationProgressView.widthAnchor.constraint(equalToConstant: 90),
])
NSLayoutConstraint.activate([
descriptionView.topAnchor.constraint(equalTo: arrayStackCharacteristicTwo.topAnchor, constant: 50),
descriptionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
descriptionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
descriptionView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant: 0),
])
class DescriptionView: UIView {
private let characteristicModel = CharacteristicModel()
private let descriptionIdentifier = "CELL"
private let tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .grouped)
tableView.backgroundColor = .none
tableView.bounces = false
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
tableView.isScrollEnabled = false
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setDelegates()
setupViews()
setupConstraints()
tableView.sizeToFit()
tableView.register(DescriptionTableViewCell.self, forCellReuseIdentifier: "CELL")
}
required init?(coder: NSCoder) {
fatalError()
}
private func setDelegates() {
tableView.delegate = self
tableView.dataSource = self
}
private func setupViews() {
addSubview(tableView)
translatesAutoresizingMaskIntoConstraints = false
}
}
//MARK: - UI TableViewDataSource
extension DescriptionView: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
characteristicModel.title.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CELL",for: indexPath) as! DescriptionTableViewCell
let descriptionTitle = characteristicModel.title[indexPath.row]
let descriptionText = characteristicModel.description[indexPath.row]
var content = cell.defaultContentConfiguration()
content.textProperties.color = UIColor(red: 0.729, green: 0.443, blue: 0.443, alpha: 1)
content.textProperties.alignment = .center
content.textProperties.font = UIFont(name: "Lato-SemiBold", size: 22) ?? .systemFont(ofSize: 22)
content.text = descriptionTitle
content.secondaryText = descriptionText
content.secondaryTextProperties.font = UIFont(name: "Lato-SemiBold", size: 16) ?? .systemFont(ofSize: 16)
content.secondaryTextProperties.alignment = .justified
cell.contentConfiguration = content
return cell
}
}
extension DescriptionView {
private func setupConstraints() {
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: topAnchor,constant: 5),
tableView.leadingAnchor.constraint(equalTo: leadingAnchor,constant: 5),
tableView.trailingAnchor.constraint(equalTo: trailingAnchor,constant: -5),
tableView.bottomAnchor.constraint(equalTo: bottomAnchor,constant: -5)
])
}
}
I tried to use different approaches that I found on the Internet, but unfortunately I could not solve it. Either scroll is disabled for me, or the scroll width does not change automatically.
The screenshot shows that I use a tableView for the text that will be automatically added from the API, everything else is uiScrollView. tableView should have scroll disabled and scrollView should scroll to the end of the text
The application should look like this.
Set your scrollview and tableview height to 0 and try adding this to your viewcontroller. you can update the height of the scrollview through viewDidLayoutSubviews.
override func viewDidLayoutSubviews() {
DispatchQueue.main.async {
self.scrollView.contentSize.height = self.descriptionView.tableView.contentSize.height + otherViews.frame.height
}
}
you can also add an observer to the tableview whenever it updates its contentsize
override func viewDidLoad() {
descriptionView.tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let obj = object as? UITableView {
if obj == descriptionView.tableView && keyPath == "contentSize" {
if let newSize = change?[.newKey] as? CGSize {
descriptionView.tableViewHeightConstraint.constant = newSize.height
view.layoutIfNeeded()
}
}
}
}

Expand UITableViewCell (custom) on tap - autosize cell - some issues

In my project (UIKit, programmatic UI) I have a UITableView with sections. The cells use a custom class. On load all cells just show 3 lines of info (2 labels). On tap, all contents will be displayed. Therefor I've setup my custom cell class to have two containers, one for the 3 line preview and one for the full contents. These containers are added/removed from the cell's content view when needed when the user taps the cell by calling a method (toggleFullView) on the custom cell class. This method is called from the view controller in didSelectRowAt:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let annotation = annotationsController.getAnnotationFor(indexPath)
//Expandable cell
guard let cell = tableView.cellForRow(at: indexPath) as? AnnotationCell else { return }
cell.toggleFullView()
tableView.reloadRows(at: [indexPath], with: .none)
// tableView.reloadData()
}
Basically it works, but there are some issues:
I have to double tap the cell for it to expand and again to make it collapse again. The first tap will perform the row animation of tableView.reloadRows(at: [indexPath], with: .none) and the second tap will perform the expanding. If I substitute reloadRows with tableView.reloadData() the expanding and collapsing will happen after a single tap! But that is disabling any animations obviously, it just snaps into place. How Do I get it to work with one tap?
When the cell expands, some other random cells are also expanded. I guess this has something to do with reusable cells, but I have not been able to remedy this. See the attached Video (https://www.youtube.com/watch?v=rOkuqMnArEU).
I want to be the expanded cell to collapse once I tap another cell to expand, how do I perceive that?
My custom cell class:
import UIKit
class AnnotationCell: UITableViewCell, SelfConfiguringAnnotationCell {
//MARK: - Properties
private let titleLabelPreview = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let titleLabelDetails = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let detailsLabelShort = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 2)
private let detailsLabelLong = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 0)
private let mapImageLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let lastEditedLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let checkmarkImageView = UIImageView()
private var checkmarkView = UIView()
private var previewDetailsView = UIStackView()
private var fullDetailsView = UIStackView()
private var showFullDetails = false
//MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureContents()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
let padding: CGFloat = 5
if contentView.subviews.contains(previewDetailsView) {
//Constrain the preview view
NSLayoutConstraint.activate([
previewDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
previewDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
previewDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
previewDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding)
])
} else {
//Constrain the full view
NSLayoutConstraint.activate([
fullDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
fullDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
fullDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
fullDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding)
])
}
}
//MARK: - Actions
///Expand and collapse the cell
func toggleFullView() {
showFullDetails.toggle()
if showFullDetails {
//show the full version
if contentView.subviews.contains(previewDetailsView) {
previewDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(fullDetailsView) {
contentView.addSubview(fullDetailsView)
}
} else {
//show the preview version
if contentView.subviews.contains(fullDetailsView) {
fullDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(previewDetailsView) {
contentView.addSubview(previewDetailsView)
}
}
UIView.animate(withDuration: 1.2) {
self.layoutIfNeeded()
}
}
//MARK: - Layout
private func configureContents() {
backgroundColor = .clear
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
selectionStyle = .none
detailsLabelShort.adjustsFontSizeToFitWidth = false
detailsLabelLong.adjustsFontSizeToFitWidth = false
checkmarkView.translatesAutoresizingMaskIntoConstraints = false
checkmarkView.addSubview(checkmarkImageView)
checkmarkImageView.tintColor = .systemOrange
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView = UIStackView(arrangedSubviews: [titleLabelPreview, detailsLabelShort])
previewDetailsView.axis = .vertical
previewDetailsView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView.addBackground(.blue)
fullDetailsView = UIStackView(arrangedSubviews: [titleLabelDetails, detailsLabelLong, mapImageLabel, lastEditedLabel])
fullDetailsView.axis = .vertical
fullDetailsView.translatesAutoresizingMaskIntoConstraints = false
fullDetailsView.addBackground(.green)
//By default only add the preview View
contentView.addSubviews(checkmarkView, previewDetailsView)
let padding: CGFloat = 5
NSLayoutConstraint.activate([
//Constrain the checkmark image view to the top left with a fixed height and width
checkmarkImageView.widthAnchor.constraint(equalToConstant: 24),
checkmarkImageView.heightAnchor.constraint(equalTo: checkmarkImageView.widthAnchor),
checkmarkImageView.centerYAnchor.constraint(equalTo: checkmarkView.centerYAnchor),
checkmarkImageView.centerXAnchor.constraint(equalTo: checkmarkView.centerXAnchor),
checkmarkView.widthAnchor.constraint(equalToConstant: 30),
checkmarkView.heightAnchor.constraint(equalTo: checkmarkView.widthAnchor),
checkmarkView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
checkmarkView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: padding)
])
self.layoutIfNeeded()
}
//MARK: - Configure cell with data
func configure(with annotation: AnnotationsController.Annotation) {
titleLabelPreview.text = annotation.title
titleLabelDetails.text = annotation.title
detailsLabelShort.text = annotation.details
detailsLabelLong.text = annotation.details
checkmarkImageView.image = annotation.complete ? ProjectImages.Annotation.checkmark : nil
lastEditedLabel.text = annotation.lastEdited.customMediumToString
mapImageLabel.text = annotation.mapImage?.title ?? "No map image attached"
}
}
Ok, got it fixed, a fully expanding tableview cell. Key things are invalidating the layout in the custom cell class and calling beginUpdates() and endUpdates() on the tableView!
In my viewController:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Expandable cell
guard let cell = tableView.cellForRow(at: indexPath) as? AnnotationCell else { return }
cell.toggleFullView()
tableView.beginUpdates()
tableView.endUpdates()
}
and my custom cell class with the toggleFullView() method:
class AnnotationCell: UITableViewCell, SelfConfiguringAnnotationCell {
//MARK: - Properties
private let titleLabelPreview = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let titleLabelDetails = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let detailsLabelShort = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 2)
private let detailsLabelLong = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 0)
private let mapImageLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let lastEditedLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let checkmarkImageView = UIImageView()
private var checkmarkView = UIView()
private var previewDetailsView = UIStackView()
private var fullDetailsView = UIStackView()
let padding: CGFloat = 5
var showFullDetails = false
//MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureContents()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Actions
///Expand and collapse the cell
func toggleFullView() {
//Show the full contents
print("ShowFullDetails = \(showFullDetails.description.uppercased())")
if showFullDetails {
print("Show full contents")
if contentView.subviews.contains(previewDetailsView) {
previewDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(fullDetailsView) {
contentView.addSubview(fullDetailsView)
}
NSLayoutConstraint.activate([
fullDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
fullDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
fullDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
fullDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding)
])
//Show preview contents
} else {
print("Show preview contents")
if contentView.subviews.contains(fullDetailsView) {
fullDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(previewDetailsView) {
contentView.addSubview(previewDetailsView)
}
NSLayoutConstraint.activate([
previewDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
previewDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
previewDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
previewDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
showFullDetails.toggle()
//Invalidate current layout &
self.setNeedsLayout()
}
override func prepareForReuse() {
//Make sure reused cells start in the preview mode!
// showFullDetails = false
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
NSLayoutConstraint.activate([
//Constrain the checkmark image view to the top left with a fixed height and width
checkmarkImageView.widthAnchor.constraint(equalToConstant: 24),
checkmarkImageView.heightAnchor.constraint(equalTo: checkmarkImageView.widthAnchor),
checkmarkImageView.centerYAnchor.constraint(equalTo: checkmarkView.centerYAnchor),
checkmarkImageView.centerXAnchor.constraint(equalTo: checkmarkView.centerXAnchor),
checkmarkView.widthAnchor.constraint(equalToConstant: 30),
checkmarkView.heightAnchor.constraint(equalTo: checkmarkView.widthAnchor),
checkmarkView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
checkmarkView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: padding)
])
}
//MARK: - Layout
private func configureContents() {
//Setup Views
backgroundColor = .clear
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
selectionStyle = .none
detailsLabelShort.adjustsFontSizeToFitWidth = false
detailsLabelLong.adjustsFontSizeToFitWidth = false
checkmarkView.translatesAutoresizingMaskIntoConstraints = false
checkmarkView.addSubview(checkmarkImageView)
checkmarkImageView.tintColor = .systemOrange
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView = UIStackView(arrangedSubviews: [titleLabelPreview, detailsLabelShort])
previewDetailsView.axis = .vertical
previewDetailsView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView.addBackground(.blue)
fullDetailsView = UIStackView(arrangedSubviews: [titleLabelDetails, detailsLabelLong, mapImageLabel, lastEditedLabel])
fullDetailsView.axis = .vertical
fullDetailsView.translatesAutoresizingMaskIntoConstraints = false
fullDetailsView.addBackground(.green)
//By default only show the preview View
contentView.addSubviews(checkmarkView)
//Setup preview/DetailView
toggleFullView()
}
//MARK: - Configure cell with data
func configure(with annotation: AnnotationsController.Annotation) {
titleLabelPreview.text = annotation.title
titleLabelDetails.text = annotation.title
detailsLabelShort.text = annotation.details
detailsLabelLong.text = annotation.details
checkmarkImageView.image = annotation.complete ? ProjectImages.Annotation.checkmark : nil
lastEditedLabel.text = annotation.lastEdited.customMediumToString
mapImageLabel.text = annotation.mapImage?.title ?? "No map image attached"
}
}
HTH!

Create custom UITableViewCell with using stack view and auto layout

I have 2 questions about constraints and auto layout:
How programmatically create a cell-like in the picture below? I didn't understand how to programmatically add auto-layout and constraints for my cell.
How to assign default Apple layout margins to the cell? (for example, left inset in default cell equal 20 pt for big screens and 16 pt for small).
My current code:
class cellWithTitleAndDetail: UITableViewCell {
// MARK: - Properties
let title = UILabel()
let detail = UILabel()
let stackView = UIStackView()
// MARK: - Override init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
title.translatesAutoresizingMaskIntoConstraints = false
detail.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fillProportionally
// Set color
title.textColor = .white
detail.textColor = .white
// Highlight StackView
stackView.addBackground(color: .blue)
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(detail)
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.isLayoutMarginsRelativeArrangement = true
self.contentView.addSubview(stackView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Result:
Update:
Below added my new code and code from DonMag's answer.
New question: "LayoutMarginsGuide" works perfectly on iPhones with screen width that equal to 375 pt(image 375-1). But on big size screens separator appears earlier than the cell(image 414-2). How I can fix this?
New code:
// MARK: - Override init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Off translatesAutoresizingMaskIntoConstraints
title.translatesAutoresizingMaskIntoConstraints = false
detail.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
// Setup stackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
// Hugging
title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
detail.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal)
// Resistance
title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
detail.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
// Set textAlignment
title.textAlignment = .left
detail.textAlignment = .right
// Set numberOfLines
title.numberOfLines = 0
// Highlight stackView and set colors
stackView.addBackground(color: .blue)
title.textColor = .white
detail.textColor = .white
// Add title and detail
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(detail)
// Add to subview
self.contentView.addSubview(stackView)
// Get layoutMarginsGuide
let layoutMarginsGuide = contentView.layoutMarginsGuide
// Set constraints
NSLayoutConstraint.activate([
// constrain all 4 sides of the stack view to the content view's layoutMarginsGuide
stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 0.0),
])
}
You can use the Content View's layoutMarginsGuide:
// only if you want different margins than the content view's margins
//stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
//stackView.isLayoutMarginsRelativeArrangement = true
self.contentView.addSubview(stackView)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
// constrain all 4 sides of the stack view to the
// content view's layoutMarginsGuide
stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
As a side note, it's not clear what you really want (and this would be a separate question if this doesn't provide the layout you want)...
Do you want your cells to look like "columns"
Do you want the "detail" to be right-aligned?
Might the detail label be multi-line?
Edit
Using your updated code - the only change I made is giving the labels a background color since you didn't show your stackView.addBackground(color: .blue) code:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Off translatesAutoresizingMaskIntoConstraints
title.translatesAutoresizingMaskIntoConstraints = false
detail.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
// Setup stackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
// Hugging
title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
detail.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal)
// Resistance
title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
detail.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
// Set textAlignment
title.textAlignment = .left
detail.textAlignment = .right
// Set numberOfLines
title.numberOfLines = 0
// Highlight stackView and set colors
//stackView.addBackground(color: .blue)
title.backgroundColor = .blue
detail.backgroundColor = .red
title.textColor = .white
detail.textColor = .white
// Add title and detail
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(detail)
// Add to subview
self.contentView.addSubview(stackView)
// Get layoutMarginsGuide
let layoutMarginsGuide = contentView.layoutMarginsGuide
// Set constraints
NSLayoutConstraint.activate([
// constrain all 4 sides of the stack view to the content view's layoutMarginsGuide
stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 0.0),
])
}
This is what I get:
Edit 2
Table view cell separators can change based on device, iOS version, table view style, etc.
For the most reliable consistency, set your own.
Here's an example...
we set the cell's stackView constraints relative to the contentView not to the content view's layout margins guide.
we set the table view's separatorInset so the left inset matches the stack view's leading anchor.
we also need to set each cell's .separatorInset equal to our table view's custom .separatorInset
Here's the full code:
class MyTestTableViewController: UITableViewController {
let testTitles: [String] = [
"Yesterday all my troubles seemed so far away, Now it looks as though they're here to stay.",
"She packed my bags last night pre-flight, Zero hour nine AM.",
"When you're weary, feeling small, When tears are in your eyes, I will dry them all."
]
let testDetails: [String] = [
"The Beatles",
"Elton John",
"Simon & Garfunkel",
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(MyCellWithTitleAndDetail.self, forCellReuseIdentifier: "myCell")
// our custom separatorInset
// left matches cell's stackView leading anchor
tableView.separatorInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyCellWithTitleAndDetail
// set cell separatorInset equal to tableView separatorInset
cell.separatorInset = tableView.separatorInset
cell.title.text = testTitles[indexPath.row]
cell.detail.text = testDetails[indexPath.row]
return cell
}
}
class MyCellWithTitleAndDetail: UITableViewCell {
// MARK: - Properties
let title = UILabel()
let detail = UILabel()
let stackView = UIStackView()
// MARK: - Override init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Off translatesAutoresizingMaskIntoConstraints
title.translatesAutoresizingMaskIntoConstraints = false
detail.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
// Setup stackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fillEqually
// if we want the labels to be 50% of the width,
// set stackView.distribution = .fillEqually
// then we don't need any Content Hugging or Compression Resistance priority changes
// // Hugging
// title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
// detail.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal)
//
// // Resistance
// title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
// detail.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
// Set textAlignment
title.textAlignment = .left
detail.textAlignment = .right
// Set numberOfLines
title.numberOfLines = 0
// Highlight stackView and set colors
title.backgroundColor = .blue
detail.backgroundColor = .red
//stackView.addBackground(color: .blue)
title.textColor = .white
detail.textColor = .white
// Add title and detail
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(detail)
// Add to subview
self.contentView.addSubview(stackView)
// Set constraints
NSLayoutConstraint.activate([
// constrain all 4 sides of the stack view to the content view
// with your own "margins"
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15.0),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15.0),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12.0),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
and the results:

The last cell in UITableView always has the same height as the tallest cell in the table?

I have a UITableView that displays comments. The last cell always has the same height as the tallest cell in the UITableView and I do not know why. With reference to the image below, it can be seen that the height of the last custom object (LabelWithPadding) that holds the comment is the same height as the tallest custom object in the same table. Please can someone advise?
class CommentOnPostTableView: UITableView, UITableViewDataSource, UITableViewDelegate{
var items: [Comment]?
var post: ResearchPost?
let cellIDB = "commentCellId"
init(frame: CGRect, style: UITableView.Style, sourceData: [Comment], postContent: ResearchPost) {
super.init(frame: frame, style: style)
items = sourceData
post = postContent
self.dataSource = self
self.delegate = self
self.register(CommentTableViewCell.self, forCellReuseIdentifier: cellIDB)
//self.separatorStyle = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
/**Delegate functions*/
extension CommentOnPostTableView{
/***Number of cells*/
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let dataSource = items{
return dataSource.count
}else{
return 0
}
}
/***Create Cell*/
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let dataSource = items else{return UITableViewCell.init()}
let cell = tableView.dequeueReusableCell(withIdentifier: cellIDB, for: indexPath) as! CommentTableViewCell
cell.dataObject = dataSource[indexPath.row]
return cell
}
class CommentTableViewCell: UITableViewCell {
var commentLabel: LabelWithPadding!
var imgView: UIImageView = {
let view = UIImageView()
view.backgroundColor = UIColor.black
view.contentMode = .scaleAspectFit
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var nameLabel: UILabel = {
let label = UILabel()
label.font = .boldSystemFont(ofSize: 11)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var dataObject: Comment?{
didSet{
if let data = dataObject{
guard
let image = data.imageOfCommentor,
let name = data.name,
let comment = data.comment else{return}
imgView.image = image
nameLabel.text = name
let padding = UIEdgeInsets.init(top: 3, left: 5, bottom: 3, right: 5)
commentLabel = LabelWithPadding.init(frame: .zero, with: padding)
commentLabel.translatesAutoresizingMaskIntoConstraints = false
commentLabel.label.text = comment
setupViews()
}
}
}
....
// Auto layout constraints
private func setupViews(){
let c = contentView.safeAreaLayoutGuide
contentView.addSubview(imgView)
contentView.addSubview(nameLabel)
contentView.addSubview(commentLabel)
imgView.topAnchor.constraint(equalTo: c.topAnchor, constant: borderSpace).isActive = true
imgView.leadingAnchor.constraint(equalTo: c.leadingAnchor, constant: borderSpace).isActive = true
imgView.widthAnchor.constraint(equalTo: c.widthAnchor, multiplier: 0.08).isActive = true
imgView.heightAnchor.constraint(equalTo: c.widthAnchor, multiplier: 0.08).isActive = true
nameLabel.topAnchor.constraint(equalTo: c.topAnchor, constant: borderSpace).isActive = true
nameLabel.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 10).isActive = true
nameLabel.trailingAnchor.constraint(equalTo: c.trailingAnchor, constant: -borderSpace).isActive = true
commentLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: viewSpace).isActive = true
commentLabel.widthAnchor.constraint(equalTo: nameLabel.widthAnchor).isActive = true
commentLabel.trailingAnchor.constraint(equalTo: c.trailingAnchor, constant: -borderSpace).isActive = true
commentLabel.bottomAnchor.constraint(equalTo: c.bottomAnchor).isActive = true
commentLabel.label.font = UIFont.systemFont(ofSize: 12)
commentLabel.label.numberOfLines = 0
commentLabel.label.lineBreakMode = .byWordWrapping
commentLabel.layer.cornerRadius = 7
commentLabel.layer.masksToBounds = true
commentLabel.backgroundColor = UIColor.appGrayExtraLightGray
}
}
Custom Class:
class LabelWithPadding: UIView {
var padding: UIEdgeInsets!
let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
init(frame: CGRect, with padding: UIEdgeInsets) {
super.init(frame: frame)
self.padding = padding
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView(){
self.addSubview(label)
label.topAnchor.constraint(equalTo: self.topAnchor, constant: padding.top).isActive = true
label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -padding.bottom).isActive = true
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: padding.left).isActive = true
label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -padding.bottom).isActive = true
}
}
First of all, I couldn't find where you update constraints.
When you use tableView.dequeueReusableCell, you may get the old cell, so you should update old constraints (or remove old constratints and add new ones). You may read it in docs.
You should add variable to store old constraints:
var oldConstraints: [NSLayoutConstraint] = []
And should deactivate old constraints and add new constraints in setupView():
NSLayoutConstraint.deactivate(oldConstraints)
let constraints = [
imgView.topAnchor.constraint(equalTo: c.topAnchor, constant: borderSpace),
imgView.leadingAnchor.constraint(equalTo: c.leadingAnchor, constant: borderSpace),
imgView.widthAnchor.constraint(equalTo: c.widthAnchor, multiplier: 0.08),
imgView.heightAnchor.constraint(equalTo: c.widthAnchor, multiplier: 0.08),
nameLabel.topAnchor.constraint(equalTo: c.topAnchor, constant: borderSpace),
nameLabel.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 10),
nameLabel.trailingAnchor.constraint(equalTo: c.trailingAnchor, constant: -borderSpace),
commentLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: viewSpace),
commentLabel.widthAnchor.constraint(equalTo: nameLabel.widthAnchor),
commentLabel.trailingAnchor.constraint(equalTo: c.trailingAnchor, constant: -borderSpace),
commentLabel.bottomAnchor.constraint(equalTo: c.bottomAnchor)
]
NSLayoutConstraint.activate(constraints)
oldConstraints = constraints
Also you shouldn't add subviews every time you update dataObject in CommentTableViewCell, because they could have already been added.
In conclusion, you should add subview to contentView just once and update the constraints every time you update dataObject. I gave you an example how to solve your issue.
You could also use prepareForReuse() to prepare the cell for reuse:
If a UITableViewCell object is reusable—that is, it has a reuse
identifier—this method is invoked just before the object is returned
from the UITableView method dequeueReusableCellWithIdentifier:.

CollectionView delegate error

I have made a collection view programmatically but when set collectionView.delegate = self and collectionView.dataSource = self I get a nil while unwrapping an optional. I don't know what I did wrong here.
class MainFeedViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate, UIViewControllerTransitioningDelegate, UIGestureRecognizerDelegate, MyCollectionCell {
let transition = CircularAnimation()
var collectionView: UICollectionView!
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapEdit(recognizer:)))
func MyCollectionCell() {
let vc = DescriptionViewController()
self.present(vc, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.isHidden = true
collectionView.delegate = self
collectionView.dataSource = self
view.backgroundColor = .white
UIApplication.shared.isStatusBarHidden = false
UIApplication.shared.statusBarStyle = .default
view.addSubview(Settings)
view.addSubview(topBar)
view.addSubview(separatorView)
view.addSubview(separatorView2)
Settings.translatesAutoresizingMaskIntoConstraints = false
Settings.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15).isActive = true
Settings.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
Settings.widthAnchor.constraint(equalToConstant: 50).isActive = true
Settings.heightAnchor.constraint(equalToConstant: 50).isActive = true
separatorView.translatesAutoresizingMaskIntoConstraints = false
separatorView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40).isActive = true
separatorView.heightAnchor.constraint(equalToConstant: 1).isActive = true
view.addConstraint(NSLayoutConstraint(item: separatorView, attribute: .left, relatedBy: .equal, toItem: Settings, attribute: .right, multiplier: 1, constant: 15))
view.addConstraint(NSLayoutConstraint(item: separatorView, attribute: .right, relatedBy: .equal, toItem: topBar, attribute: .right, multiplier: 1, constant: 0))
separatorView2.translatesAutoresizingMaskIntoConstraints = false
separatorView2.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40).isActive = true
separatorView2.heightAnchor.constraint(equalToConstant: 1).isActive = true
view.addConstraint(NSLayoutConstraint(item: separatorView2, attribute: .right, relatedBy: .equal, toItem: Settings, attribute: .left, multiplier: 1, constant: -15))
view.addConstraint(NSLayoutConstraint(item: separatorView2, attribute: .left, relatedBy: .equal, toItem: topBar, attribute: .left, multiplier: 1, constant: 0))
topBar.translatesAutoresizingMaskIntoConstraints = false
topBar.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
topBar.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
topBar.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.addConstraint(NSLayoutConstraint(item: topBar, attribute: .bottom, relatedBy: .equal, toItem: separatorView, attribute: .top, multiplier: 1, constant: 0))
view.insertSubview(topBar, belowSubview: Settings)
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let height = (view.frame.width - 16 - 16) * 9/16
layout.sectionInset = UIEdgeInsets(top: 80, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: view.frame.width, height: height + 16 + 80)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(80, 0, 0, 0)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(Cell.self, forCellWithReuseIdentifier: "cellId")
collectionView.backgroundColor = UIColor.clear
collectionView.addGestureRecognizer(tapGesture)
tapGesture.delegate = self
self.view.addSubview(collectionView)
view.insertSubview(collectionView, belowSubview: topBar)
}
let Settings : UIButton = {
let btn = UIButton()
btn.setTitle("Clotho", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.layer.cornerRadius = 25
btn.backgroundColor = UIColor.rgb(displayP3Red: 255, green: 165, blue: 0)
btn.titleLabel?.font = UIFont(name: "Pacifico-Regular", size: 16)
btn.addTarget(self, action:#selector(settingsTab), for: .touchUpInside)
return btn
}()
let topBar: UIView = {
let bar = UIView()
bar.backgroundColor = .white
return bar
}()
let separatorView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.rgb(displayP3Red: 211, green: 211, blue: 211)
return view
}()
let separatorView2: UIView = {
let view2 = UIView()
view2.backgroundColor = UIColor.rgb(displayP3Red: 211, green: 211, blue: 211)
return view2
}()
#objc func tapEdit(recognizer: UITapGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.ended {
let tapLocation = recognizer.location(in: self.collectionView)
if let tapIndexPath = self.collectionView.indexPathForItem(at: tapLocation) {
if (self.collectionView.cellForItem(at: tapIndexPath) as? Cell) != nil {
//do what you want to cell here
}
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! Cell
cell.Delegate = self
return cell
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .present
transition.startingPoint = Settings.center
transition.circleColor = Settings.backgroundColor!
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .dismiss
transition.startingPoint = Settings.center
transition.circleColor = Settings.backgroundColor!
return transition
}
#objc func settingsTab(){
let secondVC = SettingsViewController()
secondVC.transitioningDelegate = self
secondVC.modalPresentationStyle = UIModalPresentationStyle.custom
self.present(secondVC, animated: true, completion: nil)
}
}
I set a var Delegate: MyCollectionCell? in my cell with a protocol
import UIKit
protocol MyCollectionCell {
func MyCollectionCell()
}
class Cell: UICollectionViewCell {
var Delegate: MyCollectionCell?
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
let TapGesture = UITapGestureRecognizer(target: self, action: #selector(Cell.tapEdit(sender:)))
addGestureRecognizer(TapGesture)
TapGesture.delegate = MainFeedViewController()
}
//other setup code...
#objc func tapEdit(sender: UITapGestureRecognizer) {
Delegate?.MyCollectionCell()
}
You haven't actually created the collection view anywhere.
This line:
var collectionView: UICollectionView!
creates a variable ready to hold the collection view (and the ! character indicates you should populate the variable in the viewDidLoad method) but you don't actually create the instance of the UICollectionView and assign it to that variable.
So when you try to set the delegate and data source the collection view variable is still nil and hence you get an error.
You need to actually create the instance of a UICollectionView which is also going to involve creating an instance of a UICollectionViewLayout (or a subclass of it like UICollectionViewFlowLayout).
At the most basic level you should do something like this:
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 100, height: 100)
collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 500, height: 500), collectionViewLayout: layout)
although of course you should adjust the frame and the parameters of the layout to suit your usage requirements.