Swift UICollectionViewCell UIlabel issue - swift

I am writing a calendar, and each day is a cell, each cell has a Rounded UILabel in contentView, but I don't know why is there the little black border on each cell
Calendar image
In 3d View 3d preview
class CalendarCell: UICollectionViewCell {
static var identifier: String = "DayCell"
let dayLabel: UILabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.setUpUI()
self.contentView.addSubview(dayLabel)
}
private func setUpUI() {
dayLabel.text = nil
dayLabel.sizeToFit()
dayLabel.backgroundColor = .white
//dayLabel.layer.borderWidth = 0.5
dayLabel.textColor = .black
dayLabel.textAlignment = .center
dayLabel.clipsToBounds = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
dayLabel.frame = self.contentView.frame
dayLabel.layer.cornerRadius = dayLabel.frame.width / 2
}
override func prepareForReuse() {
super.prepareForReuse()
setUpUI()
}

I'm not sure what's causing the problem but I'm pretty sure you can fix it and achieve the same behavior by changing your code to this:
let collectionViewCellWidth: CGFLoat = 150 // or whatever you want. You'd define this in the file with your custom flow layout or wherever your give the cell size to the collectionView.
class CalendarCell: UICollectionViewCell {
static let identifier = "DayCell" // type inference doesn't need the annotations on these two
let dayLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setUpUI()
}
private func setUpUI() {
contentView.layer.cornerRadius = collectionViewCellWidth / 2
contentView.clipsToBounds = true
contentView.backgroundColor = .white // or orange, whatever
dayLabel.text = nil
dayLabel.backgroundColor = .white
//dayLabel.layer.borderWidth = 0.5
dayLabel.textColor = .black
dayLabel.textAlignment = .center
dayLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(dayLabel)
NSLayoutConstraint.activate([
dayLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
dayLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//override func layoutSubviews() {
// dayLabel.frame = self.contentView.frame
// dayLabel.layer.cornerRadius = dayLabel.frame.width / 2
//}
// also as your code currently is, you don't do anything in your setup function that needs to be redone when a cell is dequeued for reuse. Unless you were setting some unique information for a cell like its color or text. Just FYI
override func prepareForReuse() {
super.prepareForReuse()
setUpUI()
}
}

Related

How to create a custom UILabel in Swift with a specific color

I am trying to create a custom UILabel where the text color would be red.
Here's what I tried and none of this works:
class CustomLabel: UILabel {
override func awakeFromNib() {
UILabel.appearance().textColor = UIColor.blue
textColor = UIColor.blue
}
}
You're missing another case, a Label can be created with and without nib. Try this:
class MyCustomLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
// This will call `awakeFromNib` in your code
setup()
}
private func setup() {
self.textColor = .red
}
}

Unable to resize cells with custom UICollectionViewFlowLayout

I'm currently trying to implement custom divider views in between each cell of my collection view. I found this answer online that adds the custom view in the inter-line spacing (link).
private let separatorDecorationView = "separator"
final class CustomFlowLayout: UICollectionViewFlowLayout {
override init() {
super.init()
register(SeparatorView.self,
forDecorationViewOfKind: separatorDecorationView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect) ?? []
let lineWidth = self.minimumLineSpacing
var decorationAttributes: [UICollectionViewLayoutAttributes] = []
// skip first cell
for layoutAttribute in layoutAttributes where layoutAttribute.indexPath.item > 0 {
let separatorAttribute = UICollectionViewLayoutAttributes(forDecorationViewOfKind: separatorDecorationView,
with: layoutAttribute.indexPath)
let cellFrame = layoutAttribute.frame
separatorAttribute.frame = CGRect(x: cellFrame.origin.x,
y: cellFrame.origin.y - lineWidth,
width: cellFrame.size.width,
height: lineWidth)
separatorAttribute.zIndex = Int.max
decorationAttributes.append(separatorAttribute)
}
return layoutAttributes + decorationAttributes
}
}
private final class SeparatorView: UICollectionReusableView {
private let imageView: UIImageView = {
let iv = UIImageView(image: UIImage(named: "cell-divider"))
iv.contentMode = .scaleAspectFit
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(imageView)
imageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
self.frame = layoutAttributes.frame
}
}
This solution actually works, and I'm able to see the dividers. The problem arises, however, when the user clicks on one of the cells. The behavior I want is for the cell to expand to show more details when the cell is clicked. The way I'm implementing this is by keeping track of which indexPaths are selected, and returning a larger size if they are selected in sizeForItemAt. In didSelectItemAt, I reload the collection view. This approach works when I'm using the normal UICollectionViewFlowLayout, but when I try using my custom flow layout (above), I get the following crash:
no UICollectionViewLayoutAttributes instance for -layoutAttributesForDecorationViewOfKind: separator at path <NSIndexPath: 0xf75c5b66b8a0a8ab> {length = 2, path = 0 - 6}
I tried looking up solutions and found these two stack overflows here and here but none of the answers I tried seemed to work.
I tried:
Invalidating the layout when I reload the collection view.
Implementing a cache that I return from when I override layoutAttributesForItem in my custom layout.
Any help would be greatly appreciated at this point!
It seems that I had to overwrite two methods in my custom layout:
override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let layoutAttributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind,
with: indexPath)
return layoutAttributes;
}
override func initialLayoutAttributesForAppearingDecorationElement(ofKind elementKind: String, at decorationIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = layoutAttributesForDecorationView(ofKind: elementKind,
at: decorationIndexPath)
return attributes
}
I also needed to keep the layout invalidation call when I reloaded the data.
collectionView.reloadData()
let context = collectionViewLayout.invalidationContext(forBoundsChange: bounds)
context.contentOffsetAdjustment = CGPoint.zero
collectionView.collectionViewLayout.invalidateLayout(with: context)
layoutSubviews()

Safe Area Layout Guide Not Working On UITableView's BackgroundView

The issue I'm having is that the label is anchored to the bottom edge of the screen when it should be anchored to the safe area layout guide. which will bring the label above the iPhone line.
Here's the code...
class CustomTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: .zero)
tableView.backgroundView = CustomBackgroundView()
}
}
.
class CustomBackgroundView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupSubviews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupSubviews() {
let label = UILabel()
label.text = "Hello, World!"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor).isActive = true
}
}
You are seeing this behavior because every UIView has its own SafeAreaLayoutGuide. Per default the SafeAreaLayoutGuide of a generic UIView subclass does not include the Safe Areas that you are looking for. You have to use the SafeAreaLayoutGuide of your table view.
You could do something like this:
class CustomBackgroundView: UIView {
var safetyAreaBottomAnchor: NSLayoutYAxisAnchor? {
didSet {
guard let safetyAreaBottomAnchor = safetyAreaBottomAnchor else { return }
label.bottomAnchor.constraint(equalTo: safetyAreaBottomAnchor).isActive = true
}
}
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupSubviews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupSubviews() {
label.text = "Hello, World!"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
}
}
And then in your UITableViewController do this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let customBackgroundView = CustomBackgroundView()
tableView.tableFooterView = UIView(frame: .zero)
tableView.backgroundView = customBackgroundView
customBackgroundView.safetyAreaBottomAnchor = tableView.safeAreaLayoutGuide.bottomAnchor
}
For anyone arriving from Google after all these years, to get this to work I had to return the safeAreaLayoutGuide of the superview from my custom view. See below:
class CustomBackgroundView: UIView {
override var safeAreaLayoutGuide: UILayoutGuide {
guard let superview = superview else { return UILayoutGuide() }
return superview.safeAreaLayoutGuide
}
}
This made all the constraints work as expected. Make sure you don't use the safeAreaLayoutGuide before adding the background view
func setupBackgroundViews()
{
tableView.backgroundView = customBackgroundView
anotherView.topAnchor.constraint(equalTo: customBackgroundView.safeAreaLayoutGuide.topAnchor).isActive = true
}

Struggling to show data in a label between custom classes and the view controller

This is in my viewController
import UIKit
import SnapKit
class ViewController: UIViewController {
var numberInCircleView: NumberInCircleView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setupViews()
}
func setupViews() {
self.numberInCircleView = NumberInCircleView(frame: .zero)
self.view.addSubview(self.numberInCircleView)
self.numberInCircleView.snp.makeConstraints { (make) in
make.leading.equalTo(20)
make.bottom.equalTo(-40)
make.width.equalTo(60)
make.height.equalTo(60)
}
self.numberInCircleView.numberLabel = 5
}
}
The following is in my custom class.
import Foundation
import UIKit
class NumberInCircleView: UIView {
var numberLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
self.setupViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupViews()
}
func setupViews() {
self.numberLabel = UILabel(frame: .zero)
self.addSubview(self.numberLabel)
self.numberLabel.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
self.layer.cornerRadius = 30
self.backgroundColor = UIColor.red
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.black.cgColor
self.numberLabel.text = ""
self.numberLabel.textAlignment = .center
self.numberLabel.textColor = UIColor.white
self.numberLabel.adjustsFontSizeToFitWidth = true
self.numberLabel.minimumScaleFactor = 0.5
}
This line in my ViewController
self.numberInCircleView.numberLabel = 5
Should set the label as 5. I'm not sure how I call between views to do this? Could you please advise? Some online tutorials that offer me more info on this would be much appreciated too.
It seems like you want to set the value 5 to the label but here you are missing the proper syntax, the numberLabel is a UILabel instance and has the property text of type String if you want to set the text to the label you have to assign some string like this:
self.numberInCircleView.numberLabel.text = "5"
I'm not sure how I call between views to do this
You can call any method on any object till you have a reference to it.
self.numberInCircleView.numberLabel = 5 this is compile time error , as you can't assign 5 to type of "UILabel"
Replace
self.numberInCircleView.numberLabel = 5
with
self.numberInCircleView.numberLabel.text = "5"

Programmatically access members of a UIView subclass in a UIViewController subclass

I want to put all subview properties and my subview setup code in a UIView subclass and load that into my UIViewController subclass using loadView(). Then access the UIView subclass members without casting the view property of UIViewController all the time.
This is my UIView subclass AwesomeClass
class AwesomeView: UIView {
lazy var testView:UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
self.addSubview(view)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
testView.frame = CGRect(x: 10
, y: 10
, width: self.bounds.size.width - 20
, height: 100)
}
}
And my UIViewController subclass AwesomeViewController
class AwesomeViewController: UIViewController {
override func loadView() {
let view = AwesomeView()
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I could do something like:
var subclassedView:AwesomeView {
get {
return self.view as! AwesomeView
}
}
and
subclassedView.testView.backgroundColor = UIColor.blue
But is there a way to call testView directly with self.view in the AwesomeViewController?
Edit:
What I am looking for is Covariant return type in swift.
You can you something like this:
Instantiate an instance of AwesomeViewController in AwesomeView
class AwesomeView: UIView {
var exampleColorVariable:UIColor?
//here you instantiate your view controller
var awesomeViewController = AwesomeViewController()
lazy var testView:UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
self.addSubview(view)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
testView.frame = CGRect(x: 10
, y: 10
, width: self.bounds.size.width - 20
, height: 100)
}
}
then you can access any method in AwesomeView changing a little bit your code
class AwesomeViewController: UIViewController {
lazy var awesomeView: AwesomeView = {
let view = AwesomeView()
view.awesomeViewController = self
return view
}()
func setupView() {
view.addSubview(awesomeView)
// your constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupView()
// NOW YOU CAN ACCESS ANY METHOD IN YOUR VIEW awesomeView.yourFunction()
// or you access that variable
awesomeView.exampleColorVariable = .red // you can now omit UIColor in swift3
}
}