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?
Related
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
Edit1: I am not using a storyboard everything has been added programmatically
Edit2: Updated code snippet
Hello, i've got the following issues with UICollectionView
The CollectionView won't allow you to scroll no matter what you do.
Sometimes the cells even disappear complete leaving you with a white background CollectionView.
The problem occurs when you try to scroll up to view the cells that are not visible.
I've create a CollectionView which has different type of cells.
The CollectionView is nested inside a ViewController below an ImageView.
Constraints are added and they work just fine.
How do i make it scrollable?
GIF representing the problem
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationItem.title = "Featured"
self.view.backgroundColor = UIColor.white
// Add UImage carousel
let carousel: UIImageView = {
let imageView = UIImageView()
let image = UIImage()
imageView.backgroundColor = UIColor.purple
return imageView
}()
self.view.addSubview(carousel)
carousel.translatesAutoresizingMaskIntoConstraints = false
// Add CollectionView
let featuredControllerLayout = UICollectionViewFlowLayout()
// Add CollectionViewController
featuredControllerLayout.scrollDirection = .vertical
let featuredController = FeaturedCollectionViewController(collectionViewLayout: featuredControllerLayout)
guard let featuredView = featuredController.collectionView else { return }
self.view.addSubview(featuredView)
featuredView.translatesAutoresizingMaskIntoConstraints = false
// Setup Constraints
if #available(iOS 11.0, *) {
let guide = self.view.safeAreaLayoutGuide
let guideSize = guide.layoutFrame.size
carousel.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
carousel.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
carousel.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
carousel.frame.size.height = guideSize.width/2
carousel.heightAnchor.constraint(equalToConstant: carousel.frame.size.height).isActive = true
featuredView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
featuredView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
featuredView.topAnchor.constraint(equalTo: carousel.bottomAnchor).isActive = true
featuredView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
}
}
Heyy what you are telling is you are not able to scroll up and see the entire collection view items ?
if that is the case then you have not accounted the height of tabbar controller so you can enable translucent for example I have a git repo I made you can check and let me know if you are stuck anywhere
https://github.com/dwivediashish00/socialApp
I have been stuck on this problem for nearly a week now. I have even reverted my code and wrote the same code as some tutorials but I cannot seem to get my UICollectionView to be displayed to the view for the life of me. I have a lot of other code going on in my view controllers so I am going to display the code that pertains to the UICollectionView. This is what I have so far:
view controller:
class UARTModuleViewController: UIViewController, CBPeripheralManagerDelegate, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
}
let menuBar: MenuBar = {
let mb = MenuBar()
return mb
}()
private func setupMenuBar() {
view.addSubview(menuBar)
let horizontalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":menuBar])
let verticalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "V:|[v0(100)]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":menuBar])
view.addConstraints(horizontalConstraint)
view.addConstraints(verticalConstraint)
}
}
MenuBar.swift:
class MenuBar : UIView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.red
cv.dataSource = self
cv.delegate = self
return cv
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(collectionView)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MenuCell")
//took out constraints here just to make the warning mentioned below easier to follow.
}
I do have my protocols implemented but I didn't add them just to save the amount of code I'm posting. I do get this warning after the UARTModuleViewController loads :
[LayoutConstraints] Unable to simultaneously satisfy constraints.
I've tried reasoning through it and can't seem to come to a solution. I do have a cell class as well but thought it was not necessary to include since I can't get the UICollectionView to be displayed. The only other thing that I may add is that I have a UIImageView in the middle of the view that I added in story board and put constraints on like so:
Does anyone have any suggestions as to what I may be doing wrong? Thanks in advance for any help or advice that can be given.
According to comments:
First of all, every time you use constraint in code, you must set translatesAutoresizingMaskIntoConstraints = false to the view you want to add constraint.
You're not telling the collection view to fill entire MenuBar space.
Constraints applied to MenuBar in setupMenuBar are not enaugh to determine the size and position of the view.
These are changes you should apply to tell the MenuBar to fill width, be 60 pt height and to position on bottom of the screen:
private func setupMenuBar() {
view.addSubview(menuBar)
menuBar.translatesAutoresizingMaskIntoConstraints = false // this will make your constraint working
menuBar.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true // every constraint must be enabled.
menuBar.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
menuBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
menuBar.heightAnchor.constraint(equalToConstant: 60).isActive = true
}
To tell the collection view to fill entire MenuBar view do this in MenuBar init(frame:) method:
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(collectionView)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MenuCell")
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
//took out constraints here just to make the warning mentioned below easier to follow.
}
As you can see in my code, I usually use "anchor" methods to apply constraint because are easier than visual format.
When my MSMessagesAppViewController changes the presentationStyle from compact to expanded and back to compact, my UITableView is messed up regarding its scrolling.
I am using AutoLayout to setup a UITableView inside of a View called contentView.
// Inside MSMessagesAppViewController
func createTableView() {
let tableViewController = MyTableViewController()
self.addChildViewController(tableViewController)
tableViewController.tableView.backgroundColor = UIColor.clear
tableViewController.tableView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(tableViewController.tableView)
NSLayoutConstraint.activate([
tableViewController.tableView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0),
tableViewController.tableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0),
tableViewController.tableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0),
tableViewController.tableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0)
])
}
After expanding and collapsing the MSMessagesAppViewController, I can suddenly scroll past my last cell in the tableView...
Everything is working perfectly fine in the initial state. I noticed, that the scrollbar is visible on startup, but is not present after the size change...
This is how I setup my tableView in the controller:
// Inside MyTableViewController
func setupTableView() {
tableView.dataSource = self
tableView.register(QuickStandardTableViewCell.self, forCellReuseIdentifier: "standardCell")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 64
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorColor = .clear
tableView.sectionHeaderHeight = UITableViewAutomaticDimension;
tableView.estimatedSectionHeaderHeight = 8.0
self.extendedLayoutIncludesOpaqueBars = false
}
What I checked:
The contentView frame changes correctly
The tableView frame changes correctly
The contentInset does not change
The contentSize does not change
Does anybody know what I am missing?
Thanks in advance!
Try using a UIViewController with a UITableView inside instead of using UITableViewController.
I had the same problem with an UICollectionViewController and switching to UIViewController solved this problem.
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.