Messaging App: Textview not autoresizing it's height to display full message - swift

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

Related

uiCollectionView inside a uiCollectionViewCell is not scrolling horizontally

Hey Everyone and thank you for taking your time to view this, I am trying to make a uiCollectionView scroll horizontally within a uiCollectionViewCell, the background and elements are showing but im unable to scroll, the uiCollectionViewcontaining the uiCollectionViewCell (which contains the uiCollectionView i need to scroll horizontally) in scrolling vertically as expected. Below are the relative classes:
The uiCollectionView and its uiCollectionViewCells which contains the uiCollectionView that needs horizontal scrolling, in the collectionView method the cases that needs scrolling are Prop4 and Countries:
lazy var collView: UICollectionView = {
let sub = SubclassedCollectionViewCell.self
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: self.view.frame.width, height:self.view.frame.height/100)
layout.scrollDirection = .vertical
// layout.minimumLineSpacing = 1000
layout.sectionInset = UIEdgeInsets(top:100, left: 0, bottom: 100, right: 0)
print("the frame",self.view.frame)
let view = UICollectionView(frame: CGRect(x:0, y:0, width: self.view.frame.width, height: self.view.frame.height), collectionViewLayout: layout)
view.register(sub, forCellWithReuseIdentifier: "profInfo")
view.backgroundColor = .clear
view.isUserInteractionEnabled = true
view.dataSource = self
view.delegate = self
return view
}()
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "profInfo", for: indexPath) as! SubclassedCollectionViewCell
myCell.isUserInteractionEnabled = true
myCell.frame = CGRect(x: 10, y: (indexPath.row+1)*75, width: Int(self.view.frame.width) - 10, height: 50)
myCell.backgroundColor = .clear
let v = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 50))
let v2 = UIView(frame: CGRect(x: 150, y: 0, width: Int(self.view.frame.width) - 160, height: 50))
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
let label2 = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
let imageV = UIImageView(frame: CGRect(x: 0, y: 12.5, width: 25, height: 25))
let imageV2 = UIImageView(frame: CGRect(x: 0, y: 12.5, width: 25, height: 25))
label.center = CGPoint(x: 150, y: 25)
label.textColor = .white
label2.textColor = .white
label2.center = CGPoint(x: 115, y: 25)
label2.textAlignment = .center
imageV2.center = CGPoint(x: 115, y: 25)
label.text = prof!.arrInfo[indexPath.row].title + ":"
print("array info", prof!.arrInfo)
switch(prof!.arrInfo[indexPath.row].title){
case "Prop1" :
imageV.image = UIImage(named: "art.scnassets/prop1.png")?.resized(to: CGSize(width: 10, height: 10))
label2.text = prof!.arrInfo[indexPath.row].text
v2.addSubview(label2)
break;
case "DOB" :
imageV.image = UIImage(named: "art.scnassets/DOB.png")?.resized(to: CGSize(width: 10, height: 10))
label2.text = getDOBFormat(dateStr: prof!.arrInfo[indexPath.row].text!)
v2.addSubview(label2)
break;
case "Prop3" :
imageV.image = UIImage(named: "art.scnassets/prop3.png")?.resized(to: CGSize(width: 10, height: 10))
imageV2.image = UIImage(named: prof!.arrInfo[indexPath.row].text!)?.resized(to: CGSize(width: 10, height: 10))
break;
case "Prop4" :
imageV.image = UIImage(named: "art.scnassets/prop4.png")?.resized(to: CGSize(width: 10, height: 10))
collectionViews[prof!.arrInfo[indexPath.row].title] = GeneralCollectionView()
collectionViews[prof!.arrInfo[indexPath.row].title]?.type = "Images"
collectionViews[prof!.arrInfo[indexPath.row].title]?.data = prof!.arrInfo[indexPath.row].selected
myCell.contentView.addSubview(collectionViews[prof!.arrInfo[indexPath.row].title]!.Container)
break;
case "Countries" :
imageV.image = UIImage(named: "art.scnassets/translation.png")?.resized(to: CGSize(width: 10, height: 10))
collectionViews[prof!.arrInfo[indexPath.row].title] = GeneralCollectionView()
collectionViews[prof!.arrInfo[indexPath.row].title]?.type = "Countries"
collectionViews[prof!.arrInfo[indexPath.row].title]?.data = prof!.arrInfo[indexPath.row].selected
myCell.contentView.addSubview(collectionViews[prof!.arrInfo[indexPath.row].title]!.Container)
break;
case "location":
imageV.image = UIImage(named: "art.scnassets/location.png")?.resized(to: CGSize(width: 10, height: 10))
label2.text = prof!.arrInfo[indexPath.row].text
v2.addSubview(label2)
break;
default:
break;
}
v.addSubview(label)
v.addSubview(imageV)
v2.addSubview(imageV2)
myCell.contentView.addSubview(v)
myCell.contentView.addSubview(v2)
return myCell
}
And this is the uiCollectionView that needs Horizontal scrolling and thats within the uiCollectionViewCell:
public class GeneralCollectionView:NSObject,UICollectionViewDelegate,UICollectionViewDataSource{
weak var parent:AnyObject?
var data:[String]?
var type:String?
lazy var Container: UICollectionView = {
let sub = SubclassedCollectionViewCell.self
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 50, height:50)
layout.scrollDirection = .horizontal
let view = UICollectionView(frame: CGRect(x:150, y:0, width: 250, height: 50), collectionViewLayout: layout)
view.showsHorizontalScrollIndicator = false
view.isDirectionalLockEnabled = true
view.isScrollEnabled = true
view.isPagingEnabled = true
view.isUserInteractionEnabled = true
view.alwaysBounceHorizontal = true
view.clipsToBounds = true
view.translatesAutoresizingMaskIntoConstraints = false
view.register(sub, forCellWithReuseIdentifier: "myGenCell")
view.dataSource = self
view.delegate = self
view.backgroundColor = .white
layout.sectionInset = UIEdgeInsets(top:0, left: 10, bottom: 0, right: 10)
layout.minimumLineSpacing = 10 // space between rows
layout.minimumInteritemSpacing = 5 // space between items in the same
layout.itemSize = CGSize(width: 50, height: 50)
return view
}()
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data!.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "myGenCell", for: indexPath) as! SubclassedCollectionViewCell
myCell.isUserInteractionEnabled = true
if type == "Countries"{
let country = data![indexPath.item].split(separator: "-")
let label = UILabel()
label.frame.size = CGSize(width: 50, height: 50)
label.text = countryFlag(country: String(country[1]))
label.center = CGPoint(x: 30, y: 25)
myCell.contentView.addSubview(label)
print(label, countryFlag(country: String(country[1])))
} else {
let val = data![indexPath.item]
let imgView = UIImageView()
imgView.frame.size = CGSize(width: 20, height: 20)
imgView.image = UIImage(named: val)?.resized(to: CGSize(width: 25, height: 25))
imgView.center = CGPoint(x: 30, y: 25)
myCell.contentView.addSubview(imgView)
}
return myCell
}
}
It is because v2 is added after the horizontal collection view, therefore coming on top blocking all user interactions like your scroll gestures.
Give all your views different background colors to debug this sort of thing.
Set isUserInteractionEnabled = false to v2 and any other view blocking your scroll gestures.
Besides that,
Check out Modern collection views to get some examples of how to build a collection view.
Checkout Auto layout to properly size your views.

Is it possible to change y position of a view for smaller screen size?

I am working on a UICollectionview and setting my collectionView as below,
var isListView = false
var collectionView: UICollectionView?
func collectionViewSetUp() {
collectionView?.showsVerticalScrollIndicator = false
collectionView?.showsHorizontalScrollIndicator = false
collectionView = UICollectionView(frame: .zero,collectionViewLayout: gridLayout)
collectionView?.translatesAutoresizingMaskIntoConstraints = false
collectionView?.dataSource = self
collectionView?.delegate = self
collectionView?.allowsMultipleSelection = true
collectionView?.allowsSelection = true
collectionView?.isUserInteractionEnabled = true
collectionView?.register(collectionViewListLayoutCell.self, forCellWithReuseIdentifier: "collectionViewListLayoutCell")
collectionView?.register(collectionViewCell.self, forCellWithReuseIdentifier: "collectionViewCell")
And i have two layouts as,
lazy var listLayout: UICollectionViewFlowLayout = {
//Assign a new cell for list view
let listLayout = UICollectionViewFlowLayout()
listLayout.scrollDirection = .vertical
listLayout.sectionInset = UIEdgeInsets(top: 4, left: 12, bottom: 4, right: 12)
listLayout.itemSize = CGSize(width: view.frame.size.width/1.12, height: 98)
listLayout.minimumInteritemSpacing = 1
listLayout.minimumLineSpacing = 1
folderCollectionView = UICollectionView(frame: .zero,collectionViewLayout: listLayout)
view.addSubview(collectionView!)
isListView = true
return listLayout
}()
and for Grid-
lazy var gridLayout: UICollectionViewFlowLayout = {
let gridLayout = UICollectionViewFlowLayout()
gridLayout.scrollDirection = .vertical
gridLayout.sectionInset = UIEdgeInsets(top: 5, left: 10, bottom: 15, right: 10)
gridLayout.itemSize = CGSize(width: view.frame.size.width/3.5, height: view.frame.size.width/2.3)
gridLayout.minimumInteritemSpacing = 3
gridLayout.minimumLineSpacing = 9
folderCollectionView = UICollectionView(frame: .zero,collectionViewLayout: gridLayout)
view.addSubview(folderCollectionView!)
isListView = false
return gridLayout
}()
Here Setting viewDidLayoutSubViews,
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView?.translatesAutoresizingMaskIntoConstraints = false
collectionView?.backgroundColor = appBackgroundColor
collectionView?.dataSource = self
collectionView?.delegate = self
collectionView?.frame = CGRect(x: 11, y: 162, width: self.view.frame.width-20, height: self.view.frame.height-165)
}
On the UIButton action i am just switching my layouts and refreshing my collectionView.
Now as I have set x and y position there, the space above UICollectionview is looking too much for older devices like iphone-8 and older. But in devices like iphone x and newer it is looking fine.
So how can i adjust it for older devices, not having top notch like newer?
Is it possible to do that?
Here i am not setting NSLayoutConstraints for collectionView.
Problem that i am facing with setting constraints to my collectioView is that my collectionView is not changing the layout from grid to list.

CollectionView Cell Changing Size when screen reloads

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

Multiple overlayed images in the Navigation bar title

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 }
}

Estimating size of UICollectionView cell for some text

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)
}