How should I set constraints to the subviews of my tableHeaderView? - ios

I have this viewController:
class CreateSkillGroupViewController: UIViewController {
lazy var headerStack: UIStackView = {
let stack = UIStackView(frame: CGRect(x: 0, y: 0, width: 20, height: 400))
stack.axis = .vertical
let titleField = UITextView(frame: CGRect(x: 0, y: 0, width: 300, height: 88))
titleField.backgroundColor = .green
titleField.snp.makeConstraints{ (make) in
make.height.equalTo(50)
}
let descriptionField = UITextView(frame: CGRect(x: 0, y: 0, width: 300, height: 120))
descriptionField.snp.makeConstraints{ (make) in
make.height.equalTo(100)
}
let headerImage = UIImageView(image: UIImage(named: "AppIcon-bw"))
headerImage.snp.makeConstraints{ (make) in
make.height.equalTo(300)
make.width.equalTo(200)
}
stack.addArrangedSubview(headerImage)
stack.addArrangedSubview(titleField)
stack.addArrangedSubview(descriptionField)
stack.backgroundColor = .blue
return stack
}()
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationItem()
skillsTableView = UITableView(frame: .zero, style: .insetGrouped)
skillsTableView.register(SkillSummaryCell.self)
skillsTableView.tableHeaderView = headerStack
view.addSubview(skillsTableView)
skillsTableView.tableHeaderView?.snp.makeConstraints{ (make) in
make.top.equalToSuperview()
make.left.equalToSuperview()
make.right.equalToSuperview()
make.width.equalToSuperview()
make.height.equalTo(400)
}
skillsTableView.snp.makeConstraints{ (make) in
make.edges.equalToSuperview()
}
...
This is what it creates...
As you can see I use the lazy var headerStack to setup the tableHeaderView which is a stackView. As you can see all of the constraints in that stack view are explicit number sizes. Then in the viewDidLoad, I add the constraints for the tableView itself.
I want to know how I would for instance, center the headerImage in the viewController, or in the tableView for that matter or make its width half of the tableView's width. I cannot set equalToSuperView because the view hasn't been laid out yet. And once its laid out, I cannot access the stack view subviews to retroactively add constraints to them.

First of all, I wouldn't use a stackView as a tableHeaderView because you need your tableHeaderView to be the same width as the tableView. Embed your stackView in a view and use that view as the header. Ensure that header remains the width of the tableView regardless of the stackView content.
Also, it looks like you are trying to mix autolayout with frame-based layout and that's gonna get you into trouble. I'm not sure why you were setting frames on some of your subviews.
Pay attention to how you define stackView.alignment and stackView.distribution. I'm not sure what your goal is so it's hard to give you much advice there. Bit I assume you want your subviews centered and to have their own unique width.
You defined a lot of your subviews in your stackView builder and that got you into trouble. Ensure that you have one builder for each subview. It helps keep your code clean.
Lastly, you can use autolayout to define the width equal to the width of the tableView. There are a lot of solutions on the web that make you compute the frames for your header manually and that's just a pain.
I changed some names around added some colors but I think this will help you:
extension UIColor {
static let headerImage = UIColor.systemPurple
static let header = UIColor.systemPink
static let titleField = UIColor.white
static let descriptionField = UIColor.systemYellow
static let headerStack = UIColor.systemOrange
static let tableView = UIColor.systemMint
}
class ViewController: UIViewController {
lazy var headerImage: UIImageView = {
let headerImage = UIImageView(image: UIImage(systemName: "checkmark"))
headerImage.translatesAutoresizingMaskIntoConstraints = false
headerImage.backgroundColor = .headerImage
return headerImage
}()
lazy var headerView: UIView = {
let header = UIView()
header.backgroundColor = .header
header.translatesAutoresizingMaskIntoConstraints = false
return header
}()
lazy var titleField: UITextView = {
let titleField = UITextView(frame: .zero)
titleField.translatesAutoresizingMaskIntoConstraints = false
titleField.backgroundColor = .titleField
return titleField
}()
lazy var descriptionField: UITextView = {
let descriptionField = UITextView(frame: .zero)
descriptionField.translatesAutoresizingMaskIntoConstraints = false
descriptionField.backgroundColor = .descriptionField
return descriptionField
}()
lazy var headerStack: UIStackView = {
let stack = UIStackView(frame: .zero)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.distribution = .fillProportionally
stack.alignment = .center
stack.spacing = 10
stack.backgroundColor = .headerStack
return stack
}()
lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .insetGrouped)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(SkillSummaryCell.self, forCellReuseIdentifier: "SkillSummaryCell")
tableView.backgroundColor = .tableView
tableView.delegate = self
tableView.dataSource = self
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
addViews()
arrangeViews()
tableView.layoutIfNeeded()
}
func addViews() {
view.addSubview(tableView)
headerStack.addArrangedSubview(headerImage)
headerStack.addArrangedSubview(titleField)
headerStack.addArrangedSubview(descriptionField)
headerView.addSubview(headerStack)
tableView.tableHeaderView = headerView
}
func arrangeViews() {
tableView.snp.makeConstraints{ (make) in
make.edges.equalTo(view.safeAreaLayoutGuide)
}
descriptionField.snp.makeConstraints{ (make) in
make.height.equalTo(100)
make.width.equalTo(300)
}
titleField.snp.makeConstraints{ (make) in
make.height.equalTo(100)
make.width.equalTo(300)
}
headerStack.snp.makeConstraints { make in
make.top.equalToSuperview()
make.bottom.equalToSuperview()
make.centerX.equalToSuperview()
}
headerView.snp.makeConstraints { make in
make.width.equalTo(tableView)
}
headerImage.snp.makeConstraints{ (make) in
make.width.equalTo(tableView).dividedBy(2)
make.height.equalTo(headerImage.snp.width)
}
}
}

use it:
viewForHeaderInSection
as
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let tableBounds = tableView.bounds // <- table size
let sectionIndex = section // <- Section index
}
In this method, you can customize the header for a specific section, and take into account the size of your table
ALSO:
You can use UIScreen.main.bounds - get the screen size of your phone at any time, this can be very useful, especially considering that tables are often equal in width to the width of the screen

Related

UIKit UIStackView with auto width

I'm making an app with UIkit and I'm making some UITableViewCells that contain an array of user images. I want these images to be displayed in a horizontal stack and overlay each other.
This is how I want it to look:
That's how it looks:
Code:
import UIKit
import SDWebImage
class CountryTableCell: UITableViewCell {
static let reuseIdentifier = "CountryTableCell"
//MARK: - Propeties
var viewModel: CountryViewModel? {
didSet {
viewModel?.delegate = self
setUpSpace(viewModel)
}
}
//MARK: - SubViews
private let container: UIView = {
let view = UIView()
view.withSize(CGSize(width: Dimensions.maxSafeWidth, height: 150))
view.backgroundColor = .secondary_background
view.layer.cornerRadius = 20
return view
}()
private let adminsContainer: UIView = {
let view = UIView()
view.withSize(CGSize(width: (Dimensions.maxSafeWidth - 32), height: Dimensions.image.mediumHeigthWithOverlay))
view.backgroundColor = .secondary_background
return view
}()
private let adminsStack: UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.alignment = .leading
stack.spacing = -10
stack.sizeToFit()
stack.distribution = .fillEqually
stack.withSize(CGSize(width: Dimensions.image.mediumHeigthWithOverlay, height: Dimensions.image.mediumHeigthWithOverlay))
return stack
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: UIFont.preferredFont(forTextStyle: .title2).pointSize, weight: .bold)
label.textColor = .primary_label
label.textAlignment = .left
label.sizeToFit()
label.numberOfLines = 0
return label
}()
var headerView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.withSize(CGSize(width: Dimensions.image.headerSizeForCell.width, height: ((Dimensions.image.mediumHeigth/2) + Padding.horizontal)))
iv.backgroundColor = .secondary_background
iv.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
iv.layer.cornerRadius = 20
return iv
}()
//MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(container)
container.center(inView: self)
addSubview(headerView)
headerView.anchor(top: container.topAnchor, left: container.leftAnchor, right: container.rightAnchor)
addSubview(nameLabel)
nameLabel.anchor(top: headerView.bottomAnchor, left: container.leftAnchor, right: container.rightAnchor, paddingLeft: Padding.horizontal, paddingRight: Padding.horizontal)
addSubview(adminsContainer)
adminsContainer.anchor(left: container.leftAnchor, bottom: headerView.bottomAnchor, right: container.rightAnchor ,paddingLeft: Padding.horizontal, paddingBottom: -(Dimensions.image.mediumHeigth/2), paddingRight: Padding.horizontal)
bringSubviewToFront(adminsContainer)
adminsContainer.backgroundColor = .clear.withAlphaComponent(0.0)
adminsContainer.addSubview(adminsStack)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Lifecycle
override func willRemoveSubview(_ subview: UIView) {
}
//MARK: Selectors
//MARK: - Helpers
private func setUpSpace(_ viewModel: CountryViewModel?) {
guard let viewModel = viewModel else {return}
if let url = viewModel.country.headerImage?.url {
headerView.sd_setImage(with: URL(string: url))
}
nameLabel.text = viewModel.space.name
}
}
// MARK: Extension
extension SpaceTableCell: CountryViewModelDelegate {
func didFetchAdmin(_ admin: User) {
if let admins = viewModel?.admins.count {
if admins == 1 {
adminsStack.withSize(CGSize(width: Dimensions.image.mediumHeigthWithOverlay, height: Dimensions.image.mediumHeigthWithOverlay))
} else if admins == 2 {
adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*2) - 10), height: Dimensions.image.mediumHeigthWithOverlay))
} else if admins == 3 {
adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*3) - 20), height: Dimensions.image.mediumHeigthWithOverlay))
} else if admins == 4 {
adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*4) - 30), height: Dimensions.image.mediumHeigthWithOverlay))
} else if admins == 5 {
adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*5) - 40), height: Dimensions.image.mediumHeigthWithOverlay))
}
}
let image = UserImageView(height: Dimensions.image.mediumHeigth)
image.sd_setImage(with: admin.profileImageURL)
adminsStack.addArrangedSubview(image)
}
}
class UserImageView: UIImageView {
//MARK: - Propeties
let selectedHeigth: CGFloat
init(height: CGFloat) {
self.selectedHeigth = height
super.init(frame: CGRect(x: 0, y: 0, width: selectedHeigth, height: selectedHeigth))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
contentMode = .scaleAspectFill
clipsToBounds = true
backgroundColor = .secondary_background
layer.cornerRadius = Dimensions.userImageCornerRadious(selectedHeigth)
}
}
Can someone please help me
Thank you :)
I'd suggest a couple changes:
Change #1:
Your UIStackView's distribution set to .fillEqually. By doing this, you're giving it the permission to stretch its arrangedSubviews as and when needed to fit the width that you've provided. This does prove useful in some cases but in your case, it's better to go with .fill instead
Here's an excerpt from Apple's documentation:
For all distributions except the UIStackView.Distribution.fillEqually
distribution, the stack view uses each arranged view’s
intrinsicContentSize property when calculating its size along the
stack’s axis. UIStackView.Distribution.fillEqually resizes all the
arranged views so they’re the same size, filling the stack view along
its axis. If possible, the stack view stretches all the arranged views
to match the view with the longest intrinsic size along the stack’s
axis.
I suggest that you go through this documentation and read more about this
Change #2:
Since you've now changed the distribution to respect intrinsic size, don't set up constraints that conflict with UIStackView's distribution axis. In your case, you have a horizontal stack view and seem to be also defining its width constraints based on the number of admins you have. I'd suggest getting rid of it and leave it to your stack view to take care of it.
One more pointer:
Make sure your UserImageViews have proper unified intrinsicContentSize i.e: the images you set follow the same size. If doubtful, I'd suggest setting up constraints for its width and height respectively. To do so, in your UserImageView's init, include:
self.widthAnchor.constraint(equalToConstant: height).isActive = true
self.heightAnchor.constraint(equalToConstant: height).isActive = true

UIScrollView for ViewController horizontally and vertically

My code tutorial works for scrolling horizontally with ScrollView.
I would like to make it with ViewControllers (instead of views) from my Storyboard.
And I would like to add a vertical ScrollView (To move from center to right or left = ScrollView horizontal, and to move from center to top or bottom (vertically).
Is that possible? Could someone give me a hint ?
Thanks
For information, this is my code :
import UIKit
class ViewController: UIViewController {
lazy var view0: UIView = {
let view = UIView()
view.backgroundColor = .systemTeal
let label = UILabel()
label.text = "Page 0"
label.textAlignment = .center
view.addSubview(label)
label.edgeTo(view: view)
return view
}()
lazy var view1: UIView = {
let view = UIView()
view.backgroundColor = .systemPink
let label = UILabel()
label.text = "Page 1"
label.textAlignment = .center
view.addSubview(label)
label.edgeTo(view: view)
return view
}()
lazy var view2: UIView = {
let view = UIView()
view.backgroundColor = .systemYellow
let label = UILabel()
label.text = "Page 2"
label.textAlignment = .center
view.addSubview(label)
label.edgeTo(view: view)
return view
}()
lazy var views = [view0, view1, view2]
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.isPagingEnabled = true
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(views.count), height: view.frame.height)
for i in 0..<views.count {
scrollView.addSubview(views[i])
views[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
}
scrollView.delegate = self
return scrollView
}()
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.numberOfPages = views.count
pageControl.currentPage = 0
pageControl.addTarget(self, action: #selector(pageControlTapHandler(sender:)), for: .touchUpInside)
return pageControl
}()
#objc
func pageControlTapHandler(sender: UIPageControl) {
scrollView.scrollTo(horizontalPage: sender.currentPage, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(scrollView)
scrollView.edgeTo(view: view)
view.addSubview(pageControl)
pageControl.pinTo(view)
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x / view.frame.width)
pageControl.currentPage = Int(pageIndex)
}

UICollectionview in UIStackView in UITableViewCell

I have a tableview and would want to add views dynamically to it, based on the data. For every other view it is easy. Works like charm but collectionview, I don't even see the view on stackview.
The UICollectionView's cellForItem delegate is not called, the numberOfItems delegate is called but not cellForItem.
Not sure if I am missing anything.
func testView() -> UIView {
let containerView = UIView(frame: .zero)
containerView.backgroundColor = .red
let layout = KTCenterFlowLayout()
layout.minimumInteritemSpacing = 10.0
layout.minimumLineSpacing = 10.0
layout.estimatedItemSize = CGSize(width: 80, height: 100)
layout.sectionInset = UIEdgeInsetsMake(20, 30, 20, 30)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout).then {
$0.dataSource = self
$0.isScrollEnabled = false
$0.setCollectionViewLayout(layout, animated: false)
$0.register(NPSCollectionViewCell.self, forCellWithReuseIdentifier: "npsCell")
$0.contentSize = CGSize(width: view.frame.size.width-100, height: 500.0)
containerView.addSubview($0)
}
collectionView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
make.height.equalTo(500).priority(500)
}
return containerView
}
I am adding this above view in CellForRowAtIndexPath in
cell?.stackView.addArrangedSubview(testView())
Any pointers?
Edit: here is the screenshot
I just see a blank space
Here is my StackTableViewCell
lazy var stackView : UIStackView! = {
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fillProportionally
stack.alignment = .center
stack.spacing = 10.0
stack.translatesAutoresizingMaskIntoConstraints = false
stack.setContentCompressionResistancePriority(UILayoutPriority.required, for: .vertical)
return stack
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.top.equalToSuperview().offset(10)
make.left.equalToSuperview().offset(20)
make.right.equalToSuperview().offset(-20)
make.bottom.equalToSuperview().offset(-10)
}
I had a problem that the cell wasn't displaying and I just gave it a size manually using a property of the collectionView try this approach

CollectionView Disappears within StackView (Swift)

I'm trying to achieve the stackView arrangement shown in the middle of this figure:, but for some reason the top stack, containing a collectionView, disappears when using: a .fill distribution
stackView.distribution = .fill (stack containing collectionView disappears)
stackView.distribution = .fillEqually (collectionView appears fine in stackView)
I've been struggling with this for days, and you'll see residues in my commented out sections: setting compressionResistance/hugging priorities, attempting to change the intrinsic height, changing .layout.itemSize of UICollectionViewFlowLayout(), etc... Nothing works in my hands. The code here will run if you simply paste it in and associate it with an empty UIViewController. The top, collectionView stack contains a pickerView, and the stacks below that are a pageControllView, subStack of buttons, and a UIView. It all works fine in the .fillEqually distribution, so this is purely a layout issue. Much Thanks!
// CodeStackVC2
// Test of programmatically generated stack views
// Output: nested stack views
// To make it run:
// 1) Assign the blank storyboard VC as CodeStackVC2
// 2) Move the "Is Inital VC" arrow to the blank storyboard
import UIKit
class CodeStackVC2: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout, UIGestureRecognizerDelegate {
let fruit = ["Apple", "Orange", "Plum", "Qiwi", "Banana"]
let veg = ["Lettuce", "Carrot", "Celery", "Onion", "Brocolli"]
let meat = ["Beef", "Chicken", "Ham", "Lamb"]
let bread = ["Wheat", "Muffin", "Rye", "Pita"]
var foods = [[String]]()
let button = ["bread","fruit","meat","veg"]
var sView = UIStackView()
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
foods = [fruit, veg, meat, bread]
setupViews()
}
//MARK: Views
lazy var cView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
layout.itemSize = CGSize(width: self.view.frame.width, height: 120)
let cv = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
cv.backgroundColor = UIColor.lightGray
cv.isPagingEnabled = true
cv.dataSource = self
cv.delegate = self
cv.isUserInteractionEnabled = true
// var intrinsicContentSize: CGSize {
// return CGSize(width: UIViewNoIntrinsicMetric, height: 120)
// }
return cv
}()
lazy var pageControl: UIPageControl = {
let pageC = UIPageControl()
pageC.numberOfPages = self.foods.count
pageC.pageIndicatorTintColor = UIColor.darkGray
pageC.currentPageIndicatorTintColor = UIColor.white
pageC.backgroundColor = .lightGray
pageC.addTarget(self, action: #selector(changePage(sender:)), for: UIControlEvents.valueChanged)
// pageC.setContentHuggingPriority(900, for: .vertical)
// pageC.setContentCompressionResistancePriority(100, for: .vertical)
return pageC
}()
var readerView: UIView = {
let rView = UIView()
rView.backgroundColor = UIColor.brown
// rView.setContentHuggingPriority(100, for: .vertical)
// rView.setContentCompressionResistancePriority(900, for: .vertical)
return rView
}()
func makeButton(_ name:String) -> UIButton{
let newButton = UIButton(type: .system)
let img = UIImage(named: name)?.withRenderingMode(.alwaysTemplate)
newButton.setImage(img, for: .normal)
newButton.contentMode = .scaleAspectFit
newButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleButton)))
newButton.isUserInteractionEnabled = true
newButton.backgroundColor = .orange
return newButton
}
//Make a 4-item vertical stackView containing
//cView,pageView,subStackof 4-item horiz buttons, readerView
func setupViews(){
cView.register(FoodCell.self, forCellWithReuseIdentifier: cellId)
//generate an array of buttons
var buttons = [UIButton]()
for i in 0...foods.count-1 {
buttons += [makeButton(button[i])]
}
let subStackView = UIStackView(arrangedSubviews: buttons)
subStackView.axis = .horizontal
subStackView.distribution = .fillEqually
subStackView.alignment = .center
subStackView.spacing = 20
//set up the stackView
let stackView = UIStackView(arrangedSubviews: [cView,pageControl,subStackView,readerView])
stackView.axis = .vertical
//.fillEqually works, .fill deletes cView, .fillProportionally & .equalSpacing delete cView & readerView
stackView.distribution = .fillEqually
stackView.alignment = .fill
stackView.spacing = 5
//Add the stackView using AutoLayout
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 5).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func handleButton() {
print("button pressed")
}
//pageControl page changer
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * cView.frame.size.width
cView.setContentOffset(CGPoint(x:x, y:0), animated: true)
}
//MARK: horizontally scrolling Chapter collectionView
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// let scrollBarLeft = CGFloat(scrollView.contentOffset.x) / CGFloat(book.chap.count + 1)
// let scrollBarWidth = CGFloat( menuBar.frame.width) / CGFloat(book.chap.count + 1)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let index = targetContentOffset.pointee.x / view.frame.width
pageControl.currentPage = Int(index) //change PageControl indicator
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return foods.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! FoodCell
cell.foodType = foods[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 120)
}
}
class FoodCell:UICollectionViewCell, UIPickerViewDelegate, UIPickerViewDataSource {
var foodType: [String]? {
didSet {
pickerView.reloadComponent(0)
pickerView.selectRow(0, inComponent: 0, animated: true)
}
}
lazy var pickerView: UIPickerView = {
let pView = UIPickerView()
pView.frame = CGRect(x:0,y:0,width:Int(pView.bounds.width), height:Int(pView.bounds.height))
pView.delegate = self
pView.dataSource = self
pView.backgroundColor = .darkGray
return pView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
backgroundColor = .clear
addSubview(pickerView)
addConstraintsWithFormat("H:|[v0]|", views: pickerView)
addConstraintsWithFormat("V:|[v0]|", views: pickerView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if let count = foodType?.count {
return count
} else {
return 0
}
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let pickerLabel = UILabel()
pickerLabel.font = UIFont.systemFont(ofSize: 15)
pickerLabel.textAlignment = .center
pickerLabel.adjustsFontSizeToFitWidth = true
if let foodItem = foodType?[row] {
pickerLabel.text = foodItem
pickerLabel.textColor = .white
return pickerLabel
} else {
print("chap = nil in viewForRow")
return UIView()
}
}
}
The problem is that you have a stack view with a fixed height that contains two views (cView and readerView) that have no intrinsic content size. You need to tell the layout engine how it should size those views to fill the remaining space in the stack view.
It works when you use a .fillEqually distribution because you are telling the layout engine to make all four views in the stack view have an equal height. That defines a height for both the cView and readerView.
When you use a .fill distribution there is no way to determine how high the cView and readerView should be. The layout is ambiguous until you add more constraints. The content priorities do nothing as those views have no intrinsic size that can be stretched or squeezed. You need to set the height of one of the views with no intrinsic size and the other will take the remaining space.
The question is how high should the collection view be? Do you want it to be the same size as the reader view or maybe some proportion of the container view?
For example, suppose your design calls for the collection view to be 25% of the height of the container view with the readerView using the remaining space (the two other views are at their natural intrinsic content size). You could add the following constraint:
NSLayoutConstraint.activate([
cView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.25)
])
A Simpler Example
To reduce the layout to its most basic elements. You have a stack view pinned to its superview with four arranged subviews two of which have no intrinsic content size. For example, here is a view controller with two plain UIView, a label and a button:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
private func setupViews() {
let blueView = UIView()
blueView.backgroundColor = .blue
let titleLabel = UILabel()
titleLabel.text = "Hello"
let button = UIButton(type: .system)
button.setTitle("Action", for: .normal)
let redView = UIView()
redView.backgroundColor = .red
let stackView = UIStackView(arrangedSubviews: [blueView, titleLabel, button, redView])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 8
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stackView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor),
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
blueView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.25)
])
}
}
Here is how it looks on an iPhone in portrait with the blue view using 25% of the vertical space:
UIStackView works well with arranged subviews that are UIView but not directly with UICollectionView.
I suggest you put all your subviews items inside a UIView before stack them in a UIStackView, also you can use .fill distribution without use intrinsic content size, use instead constraints to make your subviews proportional as you need.
This solution also work seamless with autolayout without force translatesAutoresizingMaskIntoConstraints = false which make you less compliant with trait changes if you know what I mean.
/GM
Set the top, bottom, leading and trailing constraint of desired controls inside xib or storyboard.
Provide distribution of stack .fill.
Then provide height constraint of all stacks in Xib or storyboard.
Then set appropriate heights for every stacks inside code.
Hopefully it works for you.
I had the same issue, and for me it worked when I gave height and width constraints to the collection view which was placed inside the stack view.
I experienced this behavior with Xamarin CollectionView and tracked it down to an interaction being made with the CollectionView after the page was removed from the MainPage as the result of a web api call. Even blocking that, though it still had issues reloading the page. I finally resolved to clearing the collection list when the page is about to be hidden and saving a backup copy of the items, then on display of the page, running an async task that waited 10ms and then reinstalled the items. Failing to clear the list or installing items into the list immediately upon redisplay both leads to the error. The following shows in the console list and the CollectionView seems to flag itself to longer try to work after this message:
2022-04-16 19:56:33.760310-0500 .iOS[30135:2117558] The behavior of the UICollectionViewFlowLayout is not defined because:
2022-04-16 19:56:33.760454-0500 .iOS[30135:2117558] the item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values.
2022-04-16 19:56:33.760581-0500 .iOS[30135:2117558] Please check the values returned by the delegate.
2022-04-16 19:56:33.760754-0500 .iOS[30135:2117558] The relevant UICollectionViewFlowLayout instance is <Xamarin_Forms_Platform_iOS_ListViewLayout: 0x7f99e4c4e890>, and it is attached to <UICollectionView: 0x7f99e562a000; frame = (0 0; 420 695); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x6000015ad9b0>; layer = <CALayer: 0x600005be5860>; contentOffset: {0, 0}; contentSize: {0, 0}; adjustedContentInset: {0, 0, 0, 0}; layout: <Xamarin_Forms_Platform_iOS_ListViewLayout: 0x7f99e4c4e890>; dataSource: <Xamarin_Forms_Platform_iOS_GroupableItemsViewController_1: 0x7f99e4c7ace0>>.
2022-04-16 19:56:33.760829-0500 .iOS[30135:2117558] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.

How to update constraints of a view when other view is hidden using Snapkit?

There are three views from top to bottom: redView, yellowView, blueView.
I want to hide yellowView and change the constraint of blueView in order to let blueView below redView.
The picture is below:
this picture is original
this picture is what I want
The code is below:
private lazy var redView: UIView = {
let redView = UIView()
redView.backgroundColor = UIColor.red
return redView
}()
private lazy var yellowView: UIView = {
let yellowView = UIView()
yellowView.backgroundColor = UIColor.yellow
return yellowView
}()
private lazy var blueView: UIView = {
let blueView = UIView()
blueView.backgroundColor = UIColor.blue
return blueView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(redView)
view.addSubview(yellowView)
view.addSubview(blueView)
redView.snp.makeConstraints { (make) in
make.top.left.right.equalTo(view)
make.height.equalTo(40)
}
yellowView.snp.makeConstraints { (make) in
make.top.equalTo(redView.snp.bottom)
make.left.right.height.equalTo(redView)
}
blueView.snp.makeConstraints { (make) in
make.top.equalTo(yellowView.snp.bottom)
make.left.right.height.equalTo(yellowView)
}
}
if yellowView.isHidden == true {
//how is the code?
} else {
//how is the code?
}
You need set height constraint for yellow view and take IBOutlet for its height constraint of yellowView and modify its constant as per hide/show .
if yellowView.isHidden == true {
//how is the code?
ibHeightOutletOfYellow.constant = 0; // hide here
} else {
//how is the code?
ibHeightOutletOfYellow.constant = 50; // as per your needed
}

Resources