What is the difference of implementation between UITableView & UICollectionView when using Xib? - ios

I try to implement TableView and CollectionView with Xib Files. I implement UITableview properly but when I try to implement ColletionView I get an error "libc++abi.dylib: terminating with uncaught exception of type NSException". I got this error in var collectionView = UICollectionView() line.
Why I dont get this error this line var tableView = UITableView() ? And where is my mistake ? And What is the solution ? Thanks in advance.
var tableView = UITableView()
var collectionView = UICollectionView()
override func viewDidLoad() {
super.viewDidLoad()
let headerMenu = UIView()
headerMenu.translatesAutoresizingMaskIntoConstraints = false
headerMenu.backgroundColor = .green
self.view.addSubview(headerMenu)
headerMenu.heightAnchor.constraint(equalToConstant: 48.0).isActive = true
headerMenu.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
headerMenu.topAnchor.constraint(equalTo: view.safeTopAnchor).isActive = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
headerMenu.addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: headerMenu.topAnchor, constant: 0).isActive = true
collectionView.bottomAnchor.constraint(equalTo: headerMenu.bottomAnchor, constant: -10).isActive = true
collectionView.rightAnchor.constraint(equalTo: headerMenu.rightAnchor, constant: -10).isActive = true
collectionView.leftAnchor.constraint(equalTo: headerMenu.leftAnchor, constant: 10).isActive = true
collectionView.register(UINib(nibName: "MenuCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "MenuCollectionViewCell")
let mainView = UIView()
mainView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(mainView)
mainView.topAnchor.constraint(equalTo: headerMenu.bottomAnchor, constant: 0).isActive = true
mainView.bottomAnchor.constraint(equalTo: view.safeBottomAnchor, constant: 0).isActive = true
mainView.rightAnchor.constraint(equalTo: view.safeRightAnchor, constant: 0).isActive = true
mainView.leftAnchor.constraint(equalTo: view.safeLeftAnchor, constant: 0).isActive = true
mainView.backgroundColor = UIColor.white
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.estimatedRowHeight = UITableViewAutomaticDimension
mainView.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: mainView.topAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: mainView.bottomAnchor, constant: -10).isActive = true
tableView.rightAnchor.constraint(equalTo: mainView.rightAnchor, constant: -10).isActive = true
tableView.leftAnchor.constraint(equalTo: mainView.leftAnchor, constant: 10).isActive = true
tableView.separatorStyle = .none
tableView.register(UINib(nibName: "ProductTableViewCell", bundle: nil), forCellReuseIdentifier: "ProductTableViewCell")
}

You need to use other init to create UICollectionView instance:
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: rect, collectionViewLayout: layout)

UICollectionView requires a UICollectionViewLayout in order to render. When creating a collection view via the storyboard this is already set with a default value.
You can initialise it like this:
let flowLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: flowLayout)
Flow layout
A concrete layout object that organizes items into a grid with optional header and footer views for each section.
The flow layout is one of the default/built in options and is useful for creating a 'bookshelf' style grid in which the columns fill left to right and then top to bottom (unless you change the width).
There is a good tutorial on the Ray Wenderlich website which explains more about collection views and their layouts

Related

rowHeight of cell not adjusting to content increase

I use below code to add constraints programatically, no story board used
cell.descriptionDetail.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
cell.descriptionDetail.topAnchor.constraint(greaterThanOrEqualTo: cell.contentView.topAnchor, constant: 20).isActive = true
cell.descriptionDetail.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -20).isActive = true
cell.descriptionDetail.bottomAnchor.constraint(greaterThanOrEqualTo: cell.contentView.bottomAnchor, constant: 10).isActive = true
Now i use this in viewDidLaod
detailTableView.rowHeight = UITableView.automaticDimension
detailTableView.rowHeight = 40
even if i remove height constraints on all cell the row height does. not adjust, if i remove
detailTableView.rowHeight = 40
and add detailTableView.estimatedRowHeight = 40, i get error, currently this is what happens , content overlapping cells
UPDATE MY ENTIRE CODE OF FILE WHERE THE CELL IS BEING CREATED AND CONSTRAINED
let cell = detailTableView.dequeueReusableCell(withIdentifier: String(describing: TextOnlyCell.self), for: indexPath) as! TextOnlyCell
view.addSubview(cell.descriptionDetail)
view.addSubview(cell)
cell.descriptionDetail.numberOfLines = 0
cell.descriptionDetail.lineBreakMode = .byWordWrapping
cell.descriptionDetail.translatesAutoresizingMaskIntoConstraints = false
cell.descriptionDetail.widthAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
cell.descriptionDetail.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true
///Constraints
cell.descriptionDetail.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 15).isActive = true
cell.descriptionDetail.topAnchor.constraint(greaterThanOrEqualTo: cell.topAnchor, constant: 10).isActive = true
cell.descriptionDetail.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -5).isActive = true
cell.descriptionDetail.bottomAnchor.constraint(greaterThanOrEqualTo: cell.bottomAnchor, constant: 10).isActive = true
cell.descriptionDetail.text = restaurant.description
return cell
Add this method in your class... and be sure that your constraints are attached with top and bottom ... to help automaticDimension to calculate height
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableView.automaticDimension
}

Swift Programmatic Constraint not working

I am trying to create a uiview that has a segment control inside. I want to be able to add this uiview to my viewcontroller's view. the segment control should be right on top of my tableview. but everytime i setup the constraints i keep getting this error
"Thread 1: Exception: "Unable to activate constraint with anchors <NSLayoutYAxisAnchor:0x282ee24c0 "UIView:0x119d3a610.bottom"> and <NSLayoutYAxisAnchor:0x282ee2500 "UITableView:0x11a014a00.top"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal."" I tried working around by adding the subview first and what not but it's not working. here's my code if anyone can help me.
func configureTableView(){
setupSegmentControl()
view.addSubview(tableView)
setTableViewDelegates()
tableView.rowHeight = 50
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.tableView.topAnchor.constraint(equalToSystemSpacingBelow: self.view.topAnchor, multiplier: 20).isActive = true
tableView.register(UINib(nibName: "CustomCellNSB2", bundle: nil), forCellReuseIdentifier: "CustomCellNSB2")
}
func setTableViewDelegates(){
tableView.delegate = self
tableView.dataSource = self
}
func setupSegmentControl(){
var headerView = UIView()
var importanceSegmentControl = CustomSegmentControl()
headerView.addSubview(importanceSegmentControl)
self.view.addSubview(headerView)
importanceSegmentControl.addTarget(self, action: #selector(indexChanged(control:)),for: UIControl.Event.valueChanged)
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20).isActive = true
headerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20).isActive = true
headerView.bottomAnchor.constraint(equalTo: self.tableView.topAnchor, constant: 20).isActive = true
headerView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 10).isActive = true
importanceSegmentControl.translatesAutoresizingMaskIntoConstraints = false
importanceSegmentControl.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 20).isActive = true
importanceSegmentControl.trailingAnchor.constraint(equalTo: headerView.trailingAnchor, constant: -20).isActive = true
importanceSegmentControl.bottomAnchor.constraint(equalTo: headerView.topAnchor, constant: 20).isActive = true
importanceSegmentControl.topAnchor.constraint(equalTo: headerView.topAnchor, constant: 10).isActive = true
}
The tableView and importanceSegmentControl doesn't have any common ancestor at the time of adding the constraint to the importanceSegmentControl. So to fix the issue just switch the order of execution:
func configureTableView(){
view.addSubview(tableView)
setupSegmentControl()
//...
}

Async height change for UITableViewCell

In one of my projects, I need to change the height of UIImageView in UITableViewCell according to image size, but the problem is that sometimes I have to do this after the cell is already shown.
So, my current solution works like a charm if I know all the image sizes beforehand, but if I'm trying to calculate this with some delay – it's completely broken (especially with scrolling but it's broken even without it).
I made the example project to illustrate this. There is no async downloading, but I'm trying to dynamically change the height of UIImageView after some delay (1s). The height depends on UIImageView, so every next UIImageView should be slightly higher (10 pixels) than previous one. Also, I have a UILabel, constrained to UIImageView.
It looks like that (UIImageViews are the red ones)
If I'm trying to do this async, it looks like this, all the UILabels are really broken here.
and this is one after the scroll (async too):
What am I doing wrong here? I've read several threads about dynamic heights, but none of the solutions worked for me yet.
My code is fairly simple:
func addTableView() {
tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
tableView.backgroundColor = .black
tableView.register(DynamicCell.self, forCellReuseIdentifier: "dynamicCell")
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamicCell", for: indexPath) as! DynamicCell
cell.message = messageArray[indexPath.row]
cell.backgroundColor = .clear
cell.selectionStyle = .none
cell.buildCell()
return cell
}
DynamicCell.swift (delegate is doing nothing right now):
var backView: UIView!
var label: UILabel!
var picView: UIImageView!
var message: DMessage?
var picViewHeight: NSLayoutConstraint!
var delegate: RefreshCellDelegate?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backView = UIView()
backView.translatesAutoresizingMaskIntoConstraints = false
backView.backgroundColor = .white
backView.clipsToBounds = true
backView.layer.cornerRadius = 8.0
self.addSubview(backView)
label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .left
label.textColor = .black
label.numberOfLines = 0
backView.addSubview(label)
picView = UIImageView()
picView.translatesAutoresizingMaskIntoConstraints = false
picView.clipsToBounds = true
picView.backgroundColor = .red
backView.addSubview(picView)
addMainConstraints()
}
func addMainConstraints() {
backView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8).isActive = true
backView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -32).isActive = true
backView.topAnchor.constraint(equalTo: self.topAnchor, constant: 4).isActive = true
backView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -4).isActive = true
picView.topAnchor.constraint(equalTo: backView.topAnchor, constant: 0).isActive = true
picView.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 0).isActive = true
picView.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: 0).isActive = true
label.topAnchor.constraint(equalTo: picView.bottomAnchor, constant: 0).isActive = true
label.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 8).isActive = true
label.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: -8).isActive = true
label.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -4).isActive = true
picViewHeight = NSLayoutConstraint(item: picView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
picViewHeight.priority = UILayoutPriority(999)
picViewHeight.isActive = true
}
override func prepareForReuse() {
picViewHeight.constant = 0
//picViewHeight.constant = 0
}
func buildCell() {
guard let message = message else {return}
label.attributedText = NSAttributedString(string: message.text)
changeHeightWithDelay()
//changeHeightWithoutDelay()
}
func changeHeightWithoutDelay() {
if let nh = self.message?.imageHeight {
self.picViewHeight.constant = nh
self.delegate?.refreshCell(cell: self)
}
}
func changeHeightWithDelay() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if let nh = self.message?.imageHeight {
self.picViewHeight.constant = nh
self.delegate?.refreshCell(cell: self)
}
}
}
putting this as an answer.
one thing I noticed, when you are playing around with cell, it's always better to use the contentView instead of directly using self. ie self.contentView.addSubview(). what does refreshcell function do? have you tried marking it as needsSetDisplay so in the next draw cycle it will be updated? have you tried calling layoutIfNeeded?
To explain a bit further, your view has already been 'rendered' the moment you want to change the height/width of your view you need to inform it that there's an update. this happens when you mark the view as setNeedsDisplay and in the next render cycle it will be updated
more info on apple's documentation here
You can use this method or the setNeedsDisplay(_:) to notify the system that your view’s contents need to be redrawn. This method makes a note of the request and returns immediately. The view is not actually redrawn until the next drawing cycle, at which point all invalidated views are updated.

How to set constraint relationship to view, not subviews programmatically?

I am trying to setup a couple of views programmatically. On my main view I add two subviews, one anchored to the top and one to the bottom:
//Button View
view.addSubview(buttonsLabel)
buttonsLabel.translatesAutoresizingMaskIntoConstraints = false
buttonsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
buttonsLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
buttonsLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
buttonsLabel.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5, constant: -20).isActive = true
//Calculator View
calcLabel.layer.cornerRadius = 25
view.addSubview(calcLabel)
calcLabel.translatesAutoresizingMaskIntoConstraints = false
calcLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
calcLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
calcLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
//calcLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
calcLabel.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5, constant: -40).isActive = true
This works fine, both views are 50% of the frame height (minus the constants) and both are shown (one at the top, one at the bottom). But when I try to add a third view, which is 75% of the frames height and which should be placed on top of the other two views, the layout is destroyed and everything is moved almost outside of the frame.
I am trying to anchor the third view to the bottom again:
thirdView.layer.cornerRadius = 25
view.addSubview(thirdView)
thirdView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
thirdView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
thirdView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
thirdView.heightAnchor.constraint(equalTo: view.heightAnchor,multiplier: 0.75).isActive = true
This is how everything should look like (left the two views, right the third view on top:
Am I doing the anchors and constraints right (or whats abetter way) and how to add the constraint for the third view, so that it is 75% of the frames height and placed like in the image on top of everything.
Your code looks good the problem is else where, check the view hierarchy in the debugger to see which constraint(s) failed, perhaps you forgot translatesAutoresizingMaskIntoConstraints as beyowulf commented. you should be using constants as well, this makes code much more maintainable.
here is my implementation:
import UIKit
class ViewController: UIViewController {
//MARK: - SubViews
let topHalfView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.gray
return view
}()
let bottomHalfView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.gray
return view
}()
let threeQuarterView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.black
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//add, layout subviews with 9+ constraints
setupViews()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setupViews() {
self.view.addSubview(topHalfView)
self.view.addSubview(bottomHalfView)
self.view.addSubview(threeQuarterView)
let guide = self.view.safeAreaLayoutGuide
let spacing:CGFloat = 12
let viewHeight = self.view.frame.height - spacing
topHalfView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
topHalfView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: spacing).isActive = true
topHalfView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -spacing).isActive = true
topHalfView.heightAnchor.constraint(equalToConstant: viewHeight * 0.5).isActive = true
bottomHalfView.topAnchor.constraint(equalTo: topHalfView.bottomAnchor, constant: spacing).isActive = true
bottomHalfView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: spacing).isActive = true
bottomHalfView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -spacing).isActive = true
bottomHalfView.heightAnchor.constraint(equalToConstant: viewHeight * 0.5).isActive = true
threeQuarterView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
threeQuarterView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: spacing).isActive = true
threeQuarterView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -spacing).isActive = true
threeQuarterView.heightAnchor.constraint(equalToConstant: self.view.frame.height * 0.75).isActive = true
}
}
The View hierarchy:

Swift: is it okay to add constraints to UICollectionview?

I have this class
class HomePage: UIViewController,UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate
And in this class i have a lot of UIViews.One of which is tabView.Now i want to add my collectionView to the bottom of tabView,how can i do that? Here is my `collectionView
var flowLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect(x:0,y:500,width:self.view.frame.size.width,height:100 ), collectionViewLayout: flowLayout)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = .red`
And constraints to tabView
tabView.topAnchor.constraint(equalTo: profileInfWrapper.bottomAnchor).isActive = true
tabView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tabView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.size.width/4).isActive = true
tabView.heightAnchor.constraint(equalToConstant: 80).isActive = true
You can add the collectionView inside tabView and setup constraints:
collectionView.translatesAutoresizingMaskIntoConstraints = false
tabView.addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: tabView.topAnchor, constant: 20).isActive = true
.... And add other constraints
Or you can add it under tab view like so:
self.view.addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: tabView.bottomAnchor, constant: 20).isActive = true
__
BTW you can set tabView's width like so:
tabView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.25).isActive = true

Resources