I am creating a screen where the user can search for films and the results load in a collection View, everything loads perfectly but in the simulator when I clicked "command, shift, A" to change to light mode to make sure the colours would adapt correctly the Cells randomly changed size to full screen instead of what I have set.
This also happens when I leave the application for the home page and then click back into the app. I am creating everything programmatically so would need answer this way please.
Below is the code from my custom cell:
import UIKit
class FavouritesCell: UICollectionViewCell {
static let identifier = "FavouritesCell"
let movieTitle: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.lineBreakMode = .byWordWrapping
label.textColor = .secondaryLabel
//label.text = "Title"
label.adjustsFontSizeToFitWidth = true
label.textAlignment = .center
label.font = label.font.withSize(12)
label.minimumScaleFactor = 0.75
return label
}()
let image : UIImageView = {
let image = UIImageView()
image.clipsToBounds = true
image.translatesAutoresizingMaskIntoConstraints = false
//image.backgroundColor = .yellow
image.layer.cornerRadius = 10.0
return image
}()
let cancelItem : UILabel = {
let label = UILabel()
label.layer.borderWidth = 2.0
label.layer.borderColor = UIColor.systemGray.cgColor
label.text = "X"
label.textAlignment = .center
label.layer.cornerRadius = 15
//label.isHidden = true
return label
}()
override init(frame: CGRect) {
super.init(frame: .zero)
contentView.addSubview(image)
contentView.addSubview(movieTitle)
contentView.addSubview(cancelItem)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
image.frame = CGRect(x: 0, y: 25, width: contentView.width, height: contentView.height - 30 )
movieTitle.frame = CGRect(x: 0, y: 0, width: contentView.width, height: 25)
cancelItem.frame = CGRect(x: image.right - 25, y: image.top , width: 25, height: 25)
}
}
Here is the code from the view controller in relation to the collectionView:
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 2
layout.minimumInteritemSpacing = 2
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(FavouritesCell.self, forCellWithReuseIdentifier: FavouritesCell.identifier)
collectionView.clipsToBounds = true
collectionView.backgroundColor = UIColor.systemBackground
return collectionView
}()
NSLayoutConstraint.activate([
searchText.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
searchText.heightAnchor.constraint(equalToConstant: 50),
searchText.leadingAnchor.constraint(equalTo: view.leadingAnchor),
searchText.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.topAnchor.constraint(equalTo: searchText.bottomAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (view.width / 3) - 4, height: (view.width / 2) - 2 )
}
image of CollectionView working normally
After the screen has been reloaded
Try to give your imageView a fixed height and fixed width and try it again, It will work
Related
my CollectionView's items do not scroll, when I scroll down the first items stay at their place, without any change, you can see what I mean on the image: collectionview
Here is the code of the collectionView:
func setUpCollectionView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 15, bottom: 20, right: 15)
layout.itemSize = CGSize(width: 100, height: 100)
myCollectionView = UICollectionView(frame: self.contentView.frame, collectionViewLayout: layout)
myCollectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
myCollectionView?.backgroundColor = UIColor.clear
myCollectionView?.allowsSelection = true
myCollectionView?.dataSource = self
myCollectionView?.delegate = self
contentView.addSubview(myCollectionView ?? UICollectionView())
myCollectionView?.register(SpendingCell.self, forCellWithReuseIdentifier: "Spending")
myCollectionView?.translatesAutoresizingMaskIntoConstraints = false
myCollectionView?.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
myCollectionView?.widthAnchor.constraint(equalTo: contentView.widthAnchor).isActive = true
myCollectionView?.heightAnchor.constraint(equalToConstant: 350).isActive = true
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Spending", for: indexPath) as! SpendingCell
cell.setUpSpending(cost: categories[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 23
}
and the collectionView cell:
class SpendingCell: UICollectionViewCell {
let stackView = UIStackView()
let SpendingButton = UIImageView()
let nameLabel = UILabel()
let sumLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(stackView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setUpSpending(cost: Spending) {
stackView.alignment = .center
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.spacing = 5
stackView.addArrangedSubview(SpendingButton)
stackView.addArrangedSubview(nameLabel)
//Constraints
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
stackView.widthAnchor.constraint(equalToConstant: 100).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 100).isActive = true
SpendingButton.image = cost.image
SpendingButton.contentMode = .scaleAspectFit
SpendingButton.layer.cornerRadius = 40
SpendingButton.layer.masksToBounds = true
SpendingButton.translatesAutoresizingMaskIntoConstraints = false
SpendingButton.widthAnchor.constraint(equalToConstant: 80).isActive = true
SpendingButton.heightAnchor.constraint(equalToConstant: 80).isActive = true
nameLabel.text = cost.title
nameLabel.textAlignment = .center
nameLabel.textColor = .black
nameLabel.font = UIFont.boldSystemFont(ofSize: 10)
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.widthAnchor.constraint(equalToConstant: 100).isActive = true
nameLabel.heightAnchor.constraint(equalToConstant: 15).isActive = true
}
}
Could someone please help me how to change that? Thanks in advance!
func makeKeyboardCollectionView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 70, height: 40)
layout.scrollDirection = .vertical
keyboardCollectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
contentView.addSubview(keyboardCollectionView)
keyboardCollectionView.snp.makeConstraints({ make in
make.top.equalTo(vocabularyLabel.snp.bottom).inset(-5)
make.left.equalToSuperview().inset(40)
make.right.equalToSuperview().inset(50)
make.height.equalTo(210)
})
keyboardCollectionView.backgroundColor = .clear
keyboardCollectionView.showsVerticalScrollIndicator = false
keyboardCollectionView.register(KeyboardCollectionViewCell.self, forCellWithReuseIdentifier: KeyboardCollectionViewCell.identifier)
keyboardCollectionView.dataSource = self
keyboardCollectionView.delegate = self
}
For the ChatLogController of my messaging feature, I'm having trouble getting the message bubble to automatically adjust its size based on its textView content size. It's showing only one line from the textview as the rest of the text is truncated. Here's how I've defined my textView and the message bubbleContainer:
private let textView: UITextView = {
let tv = UITextView()
tv.backgroundColor = .clear
tv.font = .systemFont(ofSize: 16)
tv.isScrollEnabled = false
tv.isEditable = false
tv.textColor = .white
return tv
}()
private let bubbleContainer: UIView = {
let view = UIView()
view.backgroundColor = .systemPurple
return view
}()
And here are the constraints:
addSubview(bubbleContainer)
bubbleContainer.layer.cornerRadius = 12
bubbleContainer.anchor(top: topAnchor, bottom: bottomAnchor)
bubbleContainer.widthAnchor.constraint(lessThanOrEqualToConstant: 250).isActive = true
bubbleLeftAnchor = bubbleContainer.leftAnchor.constraint(equalTo: profileImageView.rightAnchor, constant: 12)
bubbleLeftAnchor.isActive = false
bubbleRightAnchor = bubbleContainer.rightAnchor.constraint(equalTo: rightAnchor, constant: -12)
bubbleRightAnchor.isActive = false
bubbleContainer.addSubview(textView)
textView.anchor(top: bubbleContainer.topAnchor, left: bubbleContainer.leftAnchor,
bottom: bubbleContainer.bottomAnchor, right: bubbleContainer.rightAnchor,
paddingTop: 4, paddingLeft: 12, paddingBottom: 4, paddingRight: 12)
The ChatLogController is a UICollectionViewController, and this is the way I've set up sizeForItemAt:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)
let estimatedSizeCell = MessageCell(frame: frame)
estimatedSizeCell.message = messages[indexPath.row]
estimatedSizeCell.layoutIfNeeded()
let targetSize = CGSize(width: view.frame.width, height: 1000)
let estimatedSize = estimatedSizeCell.systemLayoutSizeFitting(targetSize)
return .init(width: view.frame.width, height: estimatedSize.height)
}
The delegate is set to self but it seems as if the function is not even being called.
in the pic below, you can see how the messages are being truncated and the cell height is stuck to 50 pixels, where it should have been adjusted to display the rest of the message:
Inside cell take label with 0 number of lines which has following constraints:
Leading: Grater than 90 superview
All other with as usual
I followed a tutorial to build a weather app programmatically (without a storyboard) that displays the current city and temperature. I am modifying it to display a 5 day forecast instead of just the current temperature by adding a UITableView, but it is not showing up.
Here is my WeatherView code:
class WeatherView: UIView, UITableViewDelegate, UITableViewDataSource {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
setupViews()
setupConstraints()
}
private func setupViews() {
self.addSubview(mainStack)
conditionsImageStack.addArrangedSubview(conditionsImageView)
mainStack.addArrangedSubview(conditionsImageStack)
// forecastTable.register(UITableView.self, forCellReuseIdentifier: "forecast")
forecastTable.register(UITableViewCell.self, forCellReuseIdentifier: "forecast")
forecastTable.delegate = self
forecastTable.dataSource = self
mainStack.addArrangedSubview(centerContentStack)
// centerContentStack.addArrangedSubview(temperatureLabel)
centerContentStack.addArrangedSubview(forecastTable)
centerContentStack.addArrangedSubview(cityAndConditionsStack)
cityAndConditionsStack.addArrangedSubview(cityLabel)
cityAndConditionsStack.addArrangedSubview(conditionsLabel)
mainStack.addArrangedSubview(buttonsStack)
buttonsStack.addArrangedSubview(celsiusButton)
buttonsStack.addArrangedSubview(fahrenheitButton)
buttonsStack.addArrangedSubview(UIView(frame: .zero))
}
private func setupConstraints() {
mainStack.pinEdges(to: self)
}
let mainStack: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.spacing = 10
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = UIEdgeInsets(top: 10, left: 30, bottom: 30, right: 30)
return stackView
}()
let conditionsImageStack: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.alignment = .trailing
stackView.spacing = 10
return stackView
}()
let cityAndConditionsStack: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.spacing = 10
return stackView
}()
let centerContentStack: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.alignment = .center
stackView.spacing = 60
return stackView
}()
// TABLE CODE HERE
var animalArray : [String] = ["elephant", "pig", "goat"]
var forecastTable: UITableView = {
let tableView = UITableView()
let estimatedHeight = tableView.numberOfRows(inSection: 3) //You may need to modify as necessary
// let width = parentView.frame.size.width
let width = estimatedHeight
tableView.frame = CGRect(x: 0, y: 0, width: width, height: estimatedHeight)
return tableView
}()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = forecastTable.dequeueReusableCell(withIdentifier: "forecast")
cell?.textLabel!.text = "Success"
return cell!
}
let temperatureLabel: UILabel = {
let label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 144)
label.textColor = .white
label.textAlignment = .center
label.text = "18°"
return label
}()
let cityLabel: UILabel = {
let label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 36)
label.textColor = .white
label.textAlignment = .center
label.text = "Atlanta"
return label
}()
let conditionsLabel: UILabel = {
let label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 20)
label.textColor = .white
label.textAlignment = .center
label.text = "Sunny"
return label
}()
let conditionsImageView: UIImageView = {
let image = UIImage(named: "sun")
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleAspectFit
imageView.widthAnchor.constraint(equalToConstant: image!.size.width).isActive = true
imageView.heightAnchor.constraint(equalToConstant: image!.size.height).isActive = true
return imageView
}()
let celsiusButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("°C", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 73)
button.setTitleColor(.white, for: .normal)
return button
}()
let fahrenheitButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("°F", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 73)
button.setTitleColor(.white, for: .normal)
return button
}()
let buttonsStack: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .horizontal
stackView.distribution = .equalCentering
stackView.spacing = 10
return stackView
}()
}
Here is my ViewController code:
class WeatherViewController: UIViewController {
var mainView: WeatherView! { return self.view as! WeatherView }
let presenter: WeatherPresenter!
init(with presenter: WeatherPresenter){
self.presenter = presenter
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init coder not implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
updateBackground()
}
func updateBackground() {
self.mainView.updateGradient(presenter.backgroundColor)
}
override func loadView() {
self.view = WeatherView(frame: UIScreen.main.bounds)
}
}
Here is my UIView + Constraints code:
extension UIView {
func pinEdges(to view: UIView){
self.translatesAutoresizingMaskIntoConstraints = false
self.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
self.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
self.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
I have tried forcing it to display by setting the dimensions with tableView.frame = CGRect(x: 0, y: 0, width: width, height: estimatedHeight) but that did not work.
I am registering the table in the view class instead of of the view controller class, but I am not sure if this is the problem or how to modify it correctly.
You're doing a couple things wrong...
First, in your var forecastTable: UITableView = {...} declaration, you have a line:
let estimatedHeight = tableView.numberOfRows(inSection: 3)
But, at that point, the table view has no dataSource -- even if it did, your dataSource has only section. So the value returned is undefined. If Iprint(esttmatedHeight)I get9223372036854775807. So you are trying to set the frame of your table view to9223372036854775807 x 9223372036854775807`
Next, when you add a table view to a stack view, the stack view will try to arrange it based on the stack view's properties and the table view's intrinsic size. However, the table view has no intrinsic size at that point - you must use auto-layout properties.
So, remove the frame setting for your table view, and after you've added it to the stack view, use:
// I'm guessing you want a height based on number of rows * row height?
// so, something like:
let estimatedHeight = CGFloat(3 * 44)
forecastTable.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
forecastTable.widthAnchor.constraint(equalTo: centerContentStack.widthAnchor),
forecastTable.heightAnchor.constraint(equalToConstant: estimatedHeight),
])
That will make the table view the same width as the centerContentStack that holds it, and give it a height.
At this point, you should see your table.
You are adding mainStack to the superView but not specifying it's dimensions. You need to set constrains to mainStack similar this,
mainStack.translatesAutoresizingMaskIntoConstraints = false
mainStack.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
mainStack.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
mainStack.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
mainStack.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
Above code fills the entire super view with mainStack. You can modify this code according to your requirement.
I know how to center a single image in UINavigationBar but no idea how to do that with dynamic number of images. I have a chat app that supports group chats. The number of people in a group chat could be as little as 3 but there's no upper limit.
In the UINavigationBar, I have to set the title to show at least 4 or 5 overlayed images (but not more than that since it looks odd in the UINavigationBar) and a UILabel showing how many more users are in the same group chat (ie + 15 more). The title (all the images and the label) should be centered in the UINavigationBar. The images are being downloaded from the server.
When the user taps on the title (any of the images or the label in the UINavigationBar) it should trigger an action to show a full list of the users in a separate UIViewController
The number of the overlayed images is dynamic (based on each group chat) but I can't figure out how to do this. Here's what the image of what the end result should look like:
Has anyone done this before or have an idea how to accomplish this? Help is very much appreciated
UPDATE:
I've tried to accomplish this with UIStackView but I have multiple problems. Here's the code:
var navStackView : UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.backgroundColor = .red
stack.alignment = .fill
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
var images = ["1", "2", "3", "4"]
override func viewDidLoad() {
super.viewDidLoad()
let navController = navigationController!
navController.navigationBar.addSubview(navStackView)
// x, y, w, h
navStackView.leadingAnchor.constraint(equalTo: navController.navigationBar.leadingAnchor).isActive = true
navStackView.trailingAnchor.constraint(equalTo: navController.navigationBar.trailingAnchor).isActive = true
navStackView.topAnchor.constraint(equalTo: navController.navigationBar.topAnchor).isActive = true
navStackView.bottomAnchor.constraint(equalTo: navController.navigationBar.bottomAnchor).isActive = true
for image in images {
let imageView = UIImageView()
imageView.image = UIImage(named: image)
imageView.layer.cornerRadius = imageView.bounds.height / 2
imageView.clipsToBounds = true
imageView.layer.masksToBounds = true
imageView.contentMode = .scaleAspectFill
// imageView.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
navStackView.addArrangedSubview(imageView)
navStackView.layoutIfNeeded()
}
navigationItem.titleView = navStackView
}
Here's the result so far (I'm stuck though, not sure how to accomplish it):
I'm not sure about stackView. But for a simple implementation I've used collectionView. Check the below strategy. You should be able to modify accordingly per your requirement.
import UIKit
class OverlayCell: UICollectionViewCell {
func didplay(with number: String) {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 40.0, height: 40.0))
view.backgroundColor = UIColor.blue
view.layer.cornerRadius = 20.0
view.layer.masksToBounds = true
view.layer.borderColor = UIColor.white.cgColor
view.layer.borderWidth = 2.0
let label = UILabel(frame: CGRect(x: 2, y: 2, width: view.bounds.width - 4, height: view.bounds.height - 4))
label.textColor = .white
label.text = number
label.textAlignment = .center
view.addSubview(label)
contentView.addSubview(view)
contentView.transform = CGAffineTransform(scaleX: -1, y: 1)
}
}
class OverlayedView: UIView {
var mainView: UIView!
var imageCollection: UICollectionView!
//Static for now
let cellWidth: CGFloat = 40.0
let cellHeight: CGFloat = 40.0
var collectionWidth: CGFloat = 115.0
override init(frame: CGRect) {
super.init(frame: frame)
loadNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadNib()
}
private func loadNib() {
if let view = Bundle.main.loadNibNamed("OverlayedView", owner: self, options: nil)?.first as? UIView {
mainView = view
mainView.frame = self.bounds
self.backgroundColor = .black
addSubview(view)
}
}
var dataSource = ["4","3","2","1"]
func loadData() {
//dynamically calculate collectionWidth to be able to kepp it in center
collectionWidth = dataSource.count >= 4 ? CGFloat(dataSource.count) * cellWidth - CGFloat((dataSource.count - 1) * 15) : CGFloat(dataSource.count) * cellWidth - CGFloat((dataSource.count - 1) * 15) //CGFloat(dataSource.count * 15) here is the item spacing from delegate -15 inward so that we can get overlapping effect
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
imageCollection = UICollectionView(frame: CGRect(x: 0, y: 0, width: collectionWidth, height: self.bounds.height), collectionViewLayout: layout)
imageCollection.center = mainView.center
imageCollection.register(OverlayCell.self, forCellWithReuseIdentifier: "Cell")
//flip the collectionView so that it loads from right to left for overlapping effect
imageCollection.transform = CGAffineTransform(scaleX: -1, y: 1)
imageCollection.delegate = self
imageCollection.dataSource = self
mainView.addSubview(imageCollection)
}
}
extension OverlayedView: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if dataSource.count > 4 {
return 4
}
return dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! OverlayCell
cell.didplay(with: dataSource[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: 40.0 , height: 40.0)
return size
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return -15.0
}
}
Usage:
let navOverlay = OverlayedView(frame: CGRect(x: 0, y: 0, width: 250.0, height: 44.0))
navOverlay.loadData() . //pass your data to this method
navigationItem.titleView = navOverlay
I've figured it out finally. Not sure if this is the right way to accomplish it, but it's a way to accomplish it and it works great. The thing to notice - I have to calculate the navStackView width based on the number of images we have. More than 5-6 images gets too crouded, so, no more than 5 images.
The navStackView.spacing also is calculated based the width and the space you'd like between the images.
var navStackView : UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.alignment = .fill
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
var moreLabel: UILabel = {
let label = UILabel()
label.text = "+ 5 more"
label.textColor = .black
label.textAlignment = .left
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var images = ["1", "2", "3", "4", "3", "3"]
override func viewDidLoad() {
super.viewDidLoad()
let navController = navigationController!
navController.navigationBar.addSubview(navStackView)
// x, y, w, h
navStackView.widthAnchor.constraint(equalToConstant: 95).isActive = true
navStackView.centerYAnchor.constraint(equalTo: navController.navigationBar.centerYAnchor).isActive = true
navStackView.heightAnchor.constraint(equalToConstant: 35).isActive = true
navStackView.centerXAnchor.constraint(equalTo: navController.navigationBar.centerXAnchor).isActive = true
// image height = 35, image width = 35
// when subtracting spacing from NavStackView, we need to subtrack from the width as well for (items - 1)
switch images.count {
case 0:
print("0 images")
case 1:
changeNavStackWidth(constant: 60, spacing: 0)
moreLabel.isHidden = true
case 2:
changeNavStackWidth(constant: 80, spacing: 10)
moreLabel.isHidden = true
case 3:
changeNavStackWidth(constant: 95, spacing: -5)
moreLabel.isHidden = true
case 4:
changeNavStackWidth(constant: 110, spacing: -10)
moreLabel.isHidden = true
case 5:
changeNavStackWidth(constant: 95, spacing: -20)
moreLabel.isHidden = true
case 6...1000:
changeNavStackWidth(constant: 95, spacing: -20)
moreLabel.isHidden = false
default:
print("default")
}
for image in images {
let imageView = UIImageView()
imageView.image = UIImage(named: image)
imageView.layer.borderColor = UIColor.white.cgColor
imageView.layer.borderWidth = 1
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
navStackView.addArrangedSubview(imageView)
navStackView.layoutIfNeeded()
}
navController.navigationBar.addSubview(moreLabel)
// x, y ,w, h
moreLabel.leadingAnchor.constraint(equalTo: navStackView.trailingAnchor, constant: 50).isActive = true
moreLabel.topAnchor.constraint(equalTo: navStackView.topAnchor).isActive = true
moreLabel.bottomAnchor.constraint(equalTo: navStackView.bottomAnchor).isActive = true
navigationItem.titleView = navStackView
let stackTap = UITapGestureRecognizer()
stackTap.addTarget(self, action: #selector(stackTapped))
navStackView.isUserInteractionEnabled = true
navStackView.addGestureRecognizer(stackTap)
}
#objc func stackTapped() {
print("tapp")
}
func changeNavStackWidth(constant: CGFloat, spacing: CGFloat) {
navStackView.constraints.forEach { constraint in
if constraint.firstAttribute == .width {
constraint.constant = constant
}
}
navStackView.spacing = spacing
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
navStackView.subviews.forEach { $0.layer.cornerRadius = $0.frame.height / 2 }
}
So I'm creating a messaging type app, which consists of some blocks of UITextView containing varying lengths of text and these reside in a "bubble" UIView.
let textView: UITextView = {
let text = UITextView()
text.text = "SAMPLE"
text.translatesAutoresizingMaskIntoConstraints = false
text.backgroundColor = .clear
text.textColor = .white
return text
}()
let bubbleView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(r: 0, g: 137, b: 247)
view.layer.cornerRadius = 14
view.layer.masksToBounds = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var bubbleWidthAnchor: NSLayoutConstraint?
bubbleView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -8).isActive = true
bubbleView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
bubbleWidthAnchor = bubbleView.widthAnchor.constraint(equalToConstant: 250)
bubbleWidthAnchor?.isActive = true
bubbleView.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
textView.leftAnchor.constraint(equalTo: bubbleView.leftAnchor, constant: 8).isActive = true
textView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
textView.rightAnchor.constraint(equalTo: bubbleView.rightAnchor).isActive = true
textView.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
To set the height of the cell I am using a custom function which is supposed to not working properly.
Custom function:
private func estimatedFrameForText(text: String) -> CGRect {
let size = CGSize(width: 250, height: 250)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 16)], context: nil)
}
Which I call in the sizeForItemAt function for UICollectionView:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height: CGFloat = 80 //Arbitrary number
if let text = messages[indexPath.item].text {
height = estimatedFrameForText(text: text).height + 8
}
return CGSize(width: view.frame.width, height: height)
}
The simple problem I am having is... it does not working great:
Example
Any idea where I am going wrong, or a better solution to getting the estimated size I need for the cell, depending on the text?
As it turns out, all that I was missing was to set the text size in the textView.
Putting text.font = UIFont.systemFont(ofSize: 16) in was required because the function to get the estimated size has:
attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 16)
So you have to define both to be the same.
Swift 4.2 updated answer is to handle height and width of uicollectionviewCell on the basis of uilabel
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let size = (self.FILTERTitles[indexPath.row] as NSString).size(withAttributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14.0)])
return CGSize(width: size.width + 38.0, height: size.height + 25.0)
}