How to change frame of UICollectionViewCell to reveal hidden button on tap? - swift

I have a custom UICollectionViewCell that has a UIImage and a UIButton.
The frame for the extended cell is 110 x 60. By default it will be 60x60.
When the app loads, I'd like for the cell to start at 60x60 and only show the image. When the cell is tapped, the cell will update to the 110x60 frame and reveal the UIButton that is beside the image.
Currently, my app does load and the cells are 60x60, but due to my auto-layout setup the image is squished and the button is full size. If I tap on the cell, it does update it's frame and it looks great.
The goal is to only see the image first and then see the button after the cell has updated its frame.
I would also like to be able to tap on the cell again and resize it back to 60x60, hiding the button and only showing the image.
Here is what I am currently trying:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.performBatchUpdates(nil, completion: nil)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
switch collectionView.indexPathsForSelectedItems?.first {
case .some(indexPath):
return CGSize(width: 110.0, height: 60.0)
default:
return CGSize(width: 60.0, height: 60.0)
}
}
Per request, my CollectionViewCell Class code:
class myCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var myImageView: UIImageView!
#IBOutlet weak var myButton: UIButton!
var myCellDelegate : myCollectionViewCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
self.layer.cornerRadius = 30
self.layer.masksToBounds = true
myImageView.layer.cornerRadius = myImageView.frame.width / 2
myImageView.layer.masksToBounds = true
}
// MARK: - Actions
#IBAction func myButtonTapped(_ sender: Any) {
self.myCellDelegate?.actionClicked(self)
}
}
To note, there's not much there so not sure if it'll help any. I'm just adjusting the cornerRadius for the cell and my image and then creating a delegate for the action to the button.

I think much depends by your constraints in the nib file.
Option 1
Interface builder > select your ImageView > Right Panel > Size inspector > Play with the "content Hugging Priority" and "content Compression Resistance"
In particular the Horizontal compression resistance of the imageView has to be greater than compression resistance of the button.
The system chooses the view to stretch based of the priorities indicated for these two parameters.
Option 2
Top
+-------------+--------+
| | |
| | |
Left| (Image) |(Button)|Right
| | |
| | |
+-------------+--------+
Bottom
<------------->
Width
Left, Top, Right, Bottom ---> constraint to the cell contentView
Width ---> set to a fixed 60
(Remember to enable clipsToBounds)
When the cell enlarges your button will appear.
You can eventually add an animation.

Related

Vertical and Horizontal Scrolling When Embedding a Table View in a Collection View that has a Collection View Header and Horizontal Flow Layout

I am trying to implement an application that allows a user to swipe horizontally between collection view cells while also being able to scroll vertically to see the entire content of a particular cell.
I want all the collection view cells to have a table view embedded within them.
The problem I am running into as of now is that my horizontal scrolling works as I have set the collection view flow layout to horizontal. I know that collection view flow layouts can only support one direction. Because of this, I tried to implement the following solution.
As of now within my project, I have a view controller with a scroll view inside. Embedded within the scroll view, is a collection view. This collection view has its own header implemented via dequeueReusableSupplementaryView.
I know that constraints can often be an issue preventing vertical scrolling to take place so here is a picture of my constraints:
Additionally, here is some code that I have used to implement this system:
Determining the scroll view content size
override func viewDidLayoutSubviews() {
super.viewWillLayoutSubviews()
scrollView.contentSize.height = collectionView.frame.size.height
scrollView.contentSize.width = self.view.frame.size.width
}
Setting up the collection view
func setupCollectionView() {
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UINib(nibName: "TableViewHolderCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "tableViewHolderCollectionViewCell")
collectionView.register(UINib(nibName: "CustomCollectionViewHeaderView", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "CustomCollectionViewHeaderView")
collectionView.delaysContentTouches = true
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.bounces = false
collectionView.isPagingEnabled = true
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.sectionHeadersPinToVisibleBounds = true
layout.sectionInset = UIEdgeInsets(top: 0, left: -self.view.frame.size.width, bottom: 0, right: 0)
collectionView.setCollectionViewLayout(layout, animated: false)
}
Setting up the collection view data
extension TasksAndScheduleViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 8
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.view.frame.size.width, height: self.view.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tableViewHolderCollectionViewCell", for: indexPath) as! TableViewHolderCollectionViewCell
cell.backgroundColor = colorArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 343)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
//setting up header view
return headerView
}
}
After trying to debug, I realized that the collection view vertical scroll might have been overriding the vertical scroll of the scroll view I had added to my view controller. In order to solve this, I created a custom class (as seen below) which my collection view implemented. To my knowledge, this was successful at disabling the vertical scroll for the collection view but it was not successful in enabling the other scroll view's vertical scroll.
class CollectionViewVerticalScroll: UICollectionView {
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
let direction = panGestureRecognizer.direction(in: self)
if direction.contains(.Down) || direction.contains(.Up) {
return false
}
return true
}
}
My desired goal is to have the ability to swipe horizontally between these collection view cells that have table views while also being able to vertically scroll the collection view cell and the contents of the table view embedded inside of it. Ideally, the vertical scroll should allow me to scroll the table view cells while also moving the entire view upwards. The closest example to what I am trying to implement that I could find online is twitter's search page. The only difference is that my application has a collection header view and no navigation bar. I have attached a picture below:
I would appreciate any help. Please do let me know if you have any questions or if something doesn't make sense to you.

Last UICollectionViewCell appearing different to the others

I'm creating a UICollectionView on my main menu and all the cells look fine bar the last one, where the image is not centered relative to the cell- it appears to be anchored to the top left corner of the cell (not sure about that though).
here is the image that contains the problem.
I didn't really know where to start with this, as all the cells use the same code and constraints.
I checked that it wasn't an image issue by repeating a previously used image (see above screenshot)
My only idea is that the final cell is in a row on its own whereas the other rows have two cells per row.
extension MainMenuViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return mainMenuOptions.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MainMenuCell", for: indexPath) as! MainMenuCollectionViewCell
let imageView = cell.mainMenuImage!
let label = cell.mainMenuLabel!
imageView.image = mainMenuImages[indexPath.item]
imageView.frame.size.height = cell.frame.size.width - 5
imageView.frame.size.width = cell.frame.size.width - 10
cell.mainMenuLabel.text = mainMenuOptions[indexPath.item]
let labelText = cell.mainMenuLabel!
label.frame.size.height = Utils.heightForView(text: labelText.text!, font: labelText.font!, width: cell.frame.width)
label.frame.size.width = cell.frame.size.width
label.center.x = cell.frame.size.width / 2
label.center.y = cell.mainMenuImage.frame.size.height + (cell.frame.size.height - cell.mainMenuImage.frame.size.height) / 2
cell.layer.borderColor = UIColor.darkGray.cgColor
cell.layer.borderWidth = CGFloat(0.5)
cell.layer.cornerRadius = CGFloat(10)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MainMenuCell", for: indexPath) as! MainMenuCollectionViewCell
let padding: CGFloat = 40
let cellWidth = (collectionView.frame.size.width - padding) / 2
let labelText = mainMenuOptions[indexPath.item]
let cellHeight = cellWidth + Utils.heightForView(text: labelText, font: cell.mainMenuLabel.font!, width: cell.frame.width) + 70
return CGSize(width: cellWidth, height: cellHeight)
}
}
Utils.heightForView() is just a function that calculates the size required for a label to fit all the text. If you need to see it I'll happily add it.
Thanks very much in advance! I hope this is the right amount of code needed but if not let me know and I'll add more.
EDIT: Cell class
class MainMenuCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var mainMenuLabel: UILabel!
#IBOutlet weak var mainMenuImage: UIImageView!
}
When you dequeue a cell in cellForItem it doesn't necessarily have its final size at that point, yet you're depending on that being correct for your code to work.
You should be using autolayout constraints or a stack view inside the cell to give you the correct layout without needing to do any work at dequeue time.
You seem to be trying to give variable heights for your cells as well, which is going to look pretty messy in a flow layout, although it doesn't seem to be making any difference to the cells in the screenshot which I'd expect to have different heights given their titles. Dequeuing a cell in sizeForItem could have some unexpected side effects too, since that's going to mess with the reuse pool.

Need to move the cell inside the uicollectionview cell && Textfield image adding

need to move the collection view cell accordingly. I have attached my screenshot for your reference:
the above is my current image but I need that to be like this:
Then I need to add image to the textfield but I have added some code but I could not add it
searchtextfield.leftViewMode = UITextFieldViewMode.always
searchtextfield.leftView = UIImageView(image: UIImage(named: "search"))
For the collection view cell width and height you have to use below method:
extension "Your Controller" : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width/2 - 15, height: collectionView.frame.size.width/2 - 15)
}
}
To make the the cell square I have added the with and height same. You can change it according to your requirement. The collection view width changes for different devices so you have to change the cell width and height too.
Don't forget to do the change in this too according to your requirement:
Hope this helps.

Swift: Dynamic Cell in table view

I'm trying to make the height of table view cell = the content of the cell.
I implemented these 2 lines in the viewDidLoad():
tableView.estimatedRowHeight = 200.0
tableView.rowHeight = UITableViewAutomaticDimension
Still, the cell is changing its height!
TextView will not expand to fit the entire text by default because it has scrolling capabilities, for what you want you should disable scrolling in the textView.
Select the textView and in the Attributes Inspector tab scroll down and uncheck the "Scrolling Enabled"
It appears to me that your issue may be that the height of the UITextView is not explicitly stated. The natural behaviour of the text view is not to be a tall as it's content.
I would suggest adding a height constraint within interface builder, hooking it up to an outlet, and then within the cell layoutSubviews function calculating the height like so:
#IBOutlet var textViewHeightConstraint: NSLayoutConstraint!
func layoutSubviews() {
super.layoutSubviews()
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
textViewHeightConstraint.constant = newSize.height
}
try to use heightForRowAt indexpath
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {return UITableViewAutomaticDimension}
+
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
In ViewDidLoad
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200
And put your both Title Label and Text Label (replace Text View) in a UIView. and give constraints to UIView
1. trailing, leading, bottom and top space as ZERO
2. give fixed hight as 200 and change relation as Greater than or equal (>=)
Then give constrains to Title label
1. trailing, leading and top space as ZERO
2. give fixed hight as 20 (your choice)
Give constrains to Text Label
1. trailing, leading, bottom and top space as ZERO
2. give fixed hight as 180 and change relation as Greater than or equal (>=)

UICollectionView Cell with a thick border

I am attempting to create a round collection view cell.
In the cell I have a red UIView that is 0 from top, leading, trailing, and bottom. Inside of that cell I have another view which is white and it is 4 from the top, leading, trailing, and bottom. Within that view is a UILabel and a UIImageView.
The goal would be to have 2 cells per column and it is has a red ring around a white circle and text and an image.
To create the round UIViews I have an extension for UIView like this
extension UIView {
func createRoundView() {
layer.cornerRadius = frame.size.width/2
clipsToBounds = true
}
}
Inside of my cellForItemAt I say
cell.whiteBackgroundView.createRoundView()
cell.colorStatusView.createRoundView()
The goal is on the left, but what is happening is on the right.
Here is my Storyboard
The constraints are all blue, nothing red.
And to get the 2 cell per column I use this delegate method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 25
let collectionCellSize = collectionView.frame.size.width - padding
return CGSize(width: collectionCellSize/2, height: collectionCellSize/2)
}
Any idea what I might be doing wrong?
In the end it doesn't matter if I have a view within a view. I change the ring or border color based on other data....so I just need to be able to easily change that border color.
This code is not enough, to tell what's going on there... try to set all
backgroundColors to .clear
then set those properties to your view
customView.layer.cornerRadius = half of your width
customView.layer.borderWidth = 5
customView.layer.borderColor = UIColor.red.cgColor
Using #blinkmeoff answer I figured it out mostly (although follow up question to come)
Inside the cell class I added the following
self.backgroundViewContainer.layoutIfNeeded()
self.backgroundViewContainer.layer.cornerRadius = min(backgroundViewContainer.frame.size.width, backgroundViewContainer.frame.size.height)/2
self.backgroundViewContainer.clipsToBounds = true
I kept getting weird shapes by just doing the /2 so I added the min(ba...
and that got me perfect circles