I'm implementing UITableView that contains UICollectionView inside. Of course, my UITableViewCell has a UICollectionView, but the problem is I don't know how to configure the height of the UICollectionViewCell. I tried to set height constraint to view inside my UICollectionViewCell, but it does not affect. There are many ways to play with size:
Handle size of UITableViewCell
Set height constraint to UICollectionView inside UITableViewCell
Use method of UICollectionViewDelegateFlowLayout
Set height constraint to view inside UICollectionViewCell (my method)
The last way is the most appropriate for me, but I want to know why it does not work. Here is the configuration of UICollectionView:
lazy var booksCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
var booksCollectionView = UICollectionView(frame: contentView.bounds, collectionViewLayout: layout)
booksCollectionView.backgroundColor = .clear
booksCollectionView.translatesAutoresizingMaskIntoConstraints = false
return booksCollectionView
}()
Constraints:
booksCollectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
booksCollectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
booksCollectionView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
booksCollectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
Cell inside UICollectionView contains only UIView:
bookView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
bookView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
bookView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
bookView.heightAnchor.constraint(equalToConstant: 440).isActive = true
Changing the height of the bookView does not affect at all. Why that happens? How should I handle the height for UICollectionViewCell?
Conform to protocol:UICollectionViewDelegateFlowLayout and implement the following method:
collectionView(_:layout:sizeForItemAt:)
Related
I have collection view when one of it's cell is cell that contain UITextView. What i want is to add textView to this cell that will expand accordingly to entered text.
I created UIView that have text view inside:
private var textView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isUserInteractionEnabled = true
textView.isEditable = true
return textView
}()
Inside i set up it's constraints to superview as following:
private func setConstraints() {
let width: CGFloat = 312
textView.topAnchor.constraint(equalTo: topAnchor).isActive = true
textView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
textView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
textView.widthAnchor.constraint(equalToConstant: width).isActive = true
}
Then inside cell i set constraints like that:
func setConstraints() {
textView.topAnchor.equalTo(topAnchor).isActive = true
textView.leftAnchor.equalTo(leftAnchor).isActive = true
textView.rightAnchor.equalTo(rightAnchor).isActive = true
textView.bottomAnchor.equalTo(bottomAnchor).isActive = true
}
I did achieve behaviour when cell is expanding after textView growth, but, i didn't set bottom constraint for UITextView->UIView, that why my collection view doesn't grow (scroll) when i type large bunch of text in my cell.
How to add cell with UITextView inside that will expand CollectionView it belongs to?
I think a better way to use manual estimation text view height and return one in
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
This approach is more difficult than using AutoLayout but gives more control and customization possibilities.
I created a collectionView with 4 cells, the cell frames are equal to the entire window frame so that I can scroll through them as they were a PageViewController.
lazy var collectionView : PagesCollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let collectionView = PagesCollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isPagingEnabled = true
collectionView.alwaysBounceHorizontal = false
return collectionView
}()
The goal I want to accomplish is that when I am in the first cell and scroll on the left (contentOffset negative) the collectionView scrolling stops, and when I am on the last cell the collectionView stops scrolling in the right direction, I want to see the cell still.
I tried different procedures:
This one blocked the scrollView only after having scrolled it in the first place, never re-enabling the scrolling in the other direction:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(scrollView.contentOffset.x == 0){
scrollView.isScrollEnabled = false
}
}
As suggested on other StackOverflow topics:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if(scrollView.contentOffset.x == 0){
collectionView.isScrollEnabled = false
}
collectionView.isScrollEnabled = true
}
But this just blocks the scrolling in both directions.
I actually don't know how to solve this.
I actually figured out how to solve this problem, it's really easy.
In my viewDidLoad method where I set the collectionView I set bounces equal to false:
collectionView.bounces = false
In my app I have a collection view with cells autosizing horizontally.
Here's some code:
// called in viewDidLoad()
private func setupCollectionView() {
let cellNib = UINib(nibName: SomeCell.nibName, bundle: nil)
collectionView.register(cellNib, forCellWithReuseIdentifier: SomeCell.reuseIdentifier)
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
flowLayout.itemSize = UICollectionViewFlowLayout.automaticSize
}
The cell has 1 view, which has constraint for heigth. This view subviews a label, which is limited with 2 rows and is not limited by width. The idea here is to allow label to calculate its own width fitting text in 2 rows.
In order to make this work I've added the following code to the cell class:
override func awakeFromNib() {
super.awakeFromNib()
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
Now, it works perfectly. Cells are autosizng, scrollView is scrolling horizontally etc. Until I call reloadData() at least once. Then cells have size 50x50 and never autosize any more until I leave the screen and come back again.
Do you have any ideas on why is it happening?
So far, I made my collection views which scroll horizontally in either left or right.I added UICollectionViewCells into one UICollectionView. My problem that I'm having is trying to find the right settings to make the cards stack on top of the first card, as demonstrated in the photo below.
Here are the settings for my collectionView and how it displays its cells.
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .Horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 78
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.clearColor()
cv.showsHorizontalScrollIndicator = false
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
You're going to have to subclass UICollectionViewLayout, set the UICollectionViewLayoutAttributes for each cell, and make sure to set zIndex to the indexPath.row of the item in order to get overlapping. You can take a look at my sample project on GitHub that implements something very similar.
There are some bugs in UICollectionViewLayout related to animating insertions of new cells when the cells are overlapping (which is why I made the sample project in the first place).
Due to the extensive updates since iOS7, I wanted to ask this question because of my limited experience with autolayout and the new stackview, and I am wondering what is the best design practice to implement the following in Objective-C (not swift yet):
In my view, there is a container scroll view, with a child container UIView. Within this UIView, there are a number of elements. One of the elements is a stack of UIViews which differ in number once in a while.
This element is followed by a map and other views.
This is how I plan on organizing it:
Questions
Is this the correct thing to do? How would I modify the height constraint for the stackview when I remove and add elements programmatically?
How do you add a subview to the UIStackView through interface builder? When I do, the subview takes the size of the containing stackview.
Swift 4.2
If you want use code instead of story board, i create an example using auto layout that don't need to estimate size of content.
you just need to add to stack view or remove from it and scroll height modify automatically.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(scrollViewContainer)
scrollViewContainer.addArrangedSubview(redView)
scrollViewContainer.addArrangedSubview(blueView)
scrollViewContainer.addArrangedSubview(greenView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// this is important for scrolling
scrollViewContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
}
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let scrollViewContainer: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 10
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let redView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 500).isActive = true
view.backgroundColor = .red
return view
}()
let blueView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 200).isActive = true
view.backgroundColor = .blue
return view
}()
let greenView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 1200).isActive = true
view.backgroundColor = .green
return view
}()
}
So you might want to make the whole layout contained within the stackview. Just something to consider.
There isn't really any right way to do things. I would not set a height constraint on your UIStackView (do add a width constraint that's equal to the view's width). Only set it on the elements you add to the stack view. You will get an error, but it's just IB complaining until you add an element to your UIStackView.
To size the elements in your stackview, you have to set a horizontal constraint on them. You can then modify that single horizontal constraint in code to change the height of the object.
To add a subview you simply do:
stackView.addArrangedSubview(childVC.view)
Or in interface builder, you just drag the element into the stack view. Make sure it has that horizontal constraint or it will resize on you.