How do you add constraints to a label in a UICollectionView header? - swift

I have a label that I cant add constraints to because it is in a Collection Reusable View, which doesn't have a viewDidLoad method. Is it possible to constrain the header so it is always 15 px from the left?
I tried adding constraints normally, but again, the Reusable View doesnt have a view for some reason. The other solutions I looked at all use a view of sorts.
override func layoutSubviews() {
super.layoutSubviews()
headerTitle.translatesAutoresizingMaskIntoConstraints = false
headerTitle.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 15).isActive = true
headerTitle.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
headerTitle.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
headerTitle.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
headerTitle.frame = bounds
}
This code throws the error Cannot find 'view' in scope which im assuming is a Reusable View thing rather than a non existent view. Here is the rest of my code:
class HeaderCollectionReusableView: UICollectionReusableView {
static let identifier = "homeheader"
private let headerTitle = UILabel()
private let newDataSet = UIButton()
override init(frame: CGRect) {
super.init(frame:frame)
headerTitle.text = "AppTitle"
headerTitle.font = UIFont.systemFont(ofSize: 32.0, weight: .bold)
headerTitle.textAlignment = .left
headerTitle.numberOfLines = 0
addSubview(headerTitle)
}
required init?(coder: NSCoder) {
fatalError()
}
override func layoutSubviews() {
super.layoutSubviews()
headerTitle.frame = bounds //<-- want to replace this line with the constraints
}
}
I'm hoping the solution works with buttons too. This is my desired output:
desired output from constraints

You are trying to put constraints on a label but you don't have a parent view of that label. Constraints need some view to hold onto in order to be functional.

Related

How to hide/show Xib view on button click in swift

I have created xib view(AdvertisementView) programatically and here i have added Delegate method for button
import UIKit
protocol SampleButtonViewDelegate: AnyObject {
func sampleButtonTapped()
}
class AdvertisementView: UIView {
// MARK: - MySubViews
//some other view...
lazy var closeButton: UIButton = {
let buttonClose = UIButton(type: .roundedRect)
buttonClose.translatesAutoresizingMaskIntoConstraints = false
buttonClose.backgroundColor = .red
buttonClose.setTitle("X", for: .normal)
buttonClose.addAction {
print("this is close button")
self.delegate?.sampleButtonTapped()
}
return buttonClose
}()
// MARK: - Properties
var website = ""
weak var delegate: SampleButtonViewDelegate?
// MARK: - Initializers
override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = true
addCloseButton()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
clipsToBounds = true
addCloseButton()
}
private func addCloseButton() {
addSubview(closeButton)
NSLayoutConstraint.activate([
closeButton.topAnchor.constraint(equalTo: titleLabel.topAnchor),
closeButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 15),
closeButton.widthAnchor.constraint(equalToConstant: 35),
closeButton.heightAnchor.constraint(equalToConstant: 25)
])
}
}
in global.swift file i have added AdvertisementView to containerview--- this is global file
let adView = AdvertisementView()
func addAdvertisementView(containerView: UIView) {
// let adView = AdvertisementView()
containerView.isHidden = false
containerView.clipsToBounds = true
containerView.addSubview(adView)
adView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
adView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5),
adView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -5),
adView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 5),
adView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -5)
])
}
and in parent class i have added view in storyboard and given its IBOutlet as adContainerView: with this code i can able to close adContainerView when i click close button
after closing if i move from messagelist view controller and come back then again i want to show whole adContainerView with adView but i am getting only adContainerView but on top adView design is not coming why?
how to show adContainerView with adView`... please guide me
class MessageList: UIViewController, SampleButtonViewDelegate {
#IBOutlet weak var adContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
addAdvertisementView(containerView: adContainerView)
adView.delegate = self
}
func sampleButtonTapped() {
adContainerView.isHidden = true
}
}
When you move to messagelist view controller, the previous view was not deinit. It just only hide so all the property you set still remain there. Apple calls it View Controller Life Cycle.
In here is adContainerView still being hidden by the func sampleButtonTapped() just like you describe
So if you need to unhide it when you get back from another view. You show check it in viewWillAppear because the view will appear again not from initialize
Code will be like this - You can add more condition to check if you need to unhide it
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
if (adContainerView.isHidden) {
adContainerView.isHidden = false
}
}

Swift: Make the background image of UITableViewController embedded in Navigation Controller fill the entire screen

I managed to create translucent and rounded UITableViewCells in a UITableViewController that is embedded inside a Navigation Controller with this line of code in viewDidLoad():
tableView.backgroundView = UIImageView(image: UIImage(named: "nightTokyo"))
But I want the background image to fill the entire phone screen. I changed the code (and only this line of code) to:
navigationController?.view = UIImageView(image: UIImage(named: "nightTokyo"))
Now the background image fills up the entire phone screen, but my table and even the iPhone's time and battery indicator icons are missing.
What I want is for the background image to fill the entire screen, but the tableView, its cells, the iPhone time, battery level icon, etc. to remain displayed.
navigationController?.setNavigationBarHidden(true, animated: true)
Here is what I did which worked for me using Swift 5, XCode 12.
Step 1 (Optional) - Create a custom UINavigationController class
class CustomNavigationController: UINavigationController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationBar.isTranslucent = true
}
Replace your UINavigationController with this UINavigationController subclass. I mark this as optional as this is based on preference, if you do not set this, your navigation bar will be opaque and you cannot see what's beneath it.
Setting the navigationBar.isTranslucent = true allows you to see the background beneath it which is what I like. A subclass is also optional but you might need to make other updates to your nav bar so I always like to make this a subclass.
Step 2 - Set up your background view constraints
class CustomViewController: UIViewController {
// your background view
let bgImageView: UIImageView = {
let bgImageView = UIImageView()
bgImageView.image = UIImage(named: "gradient_background")
bgImageView.contentMode = .scaleAspectFill
return bgImageView
}()
// Get the height of the nav bar and the status bar so you
// know how far up your background needs to go
var topBarHeight: CGFloat {
var top = self.navigationController?.navigationBar.frame.height ?? 0.0
if #available(iOS 13.0, *) {
top += UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
} else {
top += UIApplication.shared.statusBarFrame.height
}
return top
}
var isLayoutConfigured = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
title = "Site Visit"
// you only want to do this once
if !isLayoutConfigured() {
isLayoutConfigured = true
configBackground()
}
}
private func configBackground() {
view.addSubview(bgImageView)
configureBackgroundConstraints()
}
// Set up your constraints, main one here is the top constraint
private func configureBackgroundConstraints() {
bgImageView.translatesAutoresizingMaskIntoConstraints = false
bgImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor,
constant: -topBarHeight).isActive = true
bgImageView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor,
constant: 0).isActive = true
bgImageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: 0).isActive = true
bgImageView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor,
constant: 0).isActive = true
view.layoutIfNeeded()
}
Before setting constraints:
After setting above constraints:

Custom UITableViewCell Constraints

I'm creating a chat using a UITable with custom cells for the chat bubbles. Like WhatsApp, I want some bubbles to be on the left, and some to be on the right.
I'm using dequeueReusableCell(withIdentifier identifier: String) -> UITableViewCell?, which calls the cell's init(style:reuseIdentifier:) method so setup my cell with:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
cellBackground = UIView()
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
setupConstraints()
}
func setupConstraints() {
NSLayoutConstraint.activate([
cellBackground.topAnchor.constraint(equalTo: contentView.topAnchor),
cellBackground.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
cellBackground.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
cellBackground.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.5)
])
}
but this isn't satisfactory. The cell is always on the left, due to the leadingAnchor constraint - when the cell is on the right it needs to be a trailingAnchor constraint.
If I use a setup function for the cell, should setupConstraints be called there, or should the init setup the constraints it knows about at that time? Alternatively, should these constraints all be set up in layoutSubviews()?
Add 2 instance variables as a references to the leading and trailing constraints inside the cell custom class
var leadCon,traCon:NSLayoutConstraint!
Then inside init also but out of the activate
leadCon = cellBackground.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
leadCon.isActive = true
And
traCon = cellBackground.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
traCon.isActive = true
According to what you need add a func
func toLeft(_ value:Bool) {
if value {
leadCon.isActive = true
traCon.isActive = false
}
else {
leadCon.isActive = false
traCon.isActive = true
}
}
And call it after dequeue line in cellForRowAt

Table View layout not updating on device orientation change

So I have created a tableView and have made it's frame identical to the view's frame, so therefore it should be the same size of the phone screen. However when I change the device orientation to landscape in the simulator the table view keeps the same dimensions from portrait mode.
Here is my tableView code:
func setTableView() {
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.frame = view.frame
tableView.backgroundColor = UIColor.lightGray
tableView.delegate = self
tableView.dataSource = self
tableView.separatorColor = UIColor.lightGray
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
Here is the viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
setTableView()
}
Here is the method where I detect orientation change:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
print("Landscape")
} else {
print("Portrait")
}
}
Here is what I get in the simulator. In landscape mode the table view is only half of the width of the view while it should always fill the whole screen.
Usually if you want the frame of a view to determine its anchors, you don't set tableView.translatesAutoresizingMaskIntoConstraints = false. Setting that flag to false will force it to rely on anchors as opposed to its own view frame to set its constraints.
You could try setting it to true, or you could try just constraining the table to the view like so:
self.view.addSubview(tableView)
tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
This will constrain it to the view frame. If you do it this way, you don't need to worry about setting tableView.frame = view.frame. Personally, I prefer this method over relying on view's frames.

ContentView for tableViewCell not auto-resizing correctly?

I have added 2 labels to my cell and setup these constraints with snapkit, issue is I cant get the cell to expand correctly, it stays at its default height:
titleLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(contentView.snp.top)
make.bottom.equalTo(descriptionLabel.snp.top)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
}
descriptionLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(titleLabel.snp.bottom)
make.bottom.equalTo(contentView.snp.bottom)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
}
I mapped the four edges as you can see, however I know height isnt implied by these, how can I apply a height when the content is by nature, dynamic, and could be various heights...
setup for the labels looks like this:
lazy var titleLabel: UILabel = {
let titleLabel = UILabel()
titleLabel.textColor = .green
titleLabel.textAlignment = .center
contentView.addSubview(titleLabel)
return titleLabel
}()
lazy var descriptionLabel: UILabel = {
let descriptionLabel = UILabel()
descriptionLabel.textColor = .dark
descriptionLabel.textAlignment = .center
descriptionLabel.numberOfLines = 0
contentView.addSubview(descriptionLabel)
return descriptionLabel
}()
Give the table view an estimatedRowHeight, and set its rowHeight to UITableViewAutomaticDimension. Now the cells will be self-sizing. Well, if a label is pinned by all four sides to the content view, and if the cell is self-sizing, then that's all you have to do: the label will automatically change its height to accommodate its text, and the cell will automatically change size to accommodate the label.
First of all I think that you should add subviews to contentView in subclassed UITableViewCell class initializer method.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(titleLabel)
self.contentView.addSubview(descriptionLabel)
}
Secondly, make sure that in your viewDidLoad method (probably in your ViewController) these two lines are added:
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableView.automaticDimension
Of course, you should change estimatedRowHeight to accommodate your needs.
One more thing worth mentioning - you can create these constraints easier (using power of SnapKit):
titleLabel.snp.makeConstraints { (make) -> Void in
make.top.left.right.equalTo(contentView)
}
descriptionLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(titleLabel.snp.bottom)
make.bottom.left.right.equalTo(contentView)
}