Can't add constraints between UICollectionView cell and the cell subview - ios

After I run my app, it crashes:
Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal.
My image is subview of the cell.
What am I doing wrong?
let image = UIImageView(frame: CGRect(x: 0, y: 0, width: 300, height: 50))
image.image = UIImage.init(named: "Bitmap")
cell.contentView.addSubview(image)
let bottomConstr = NSLayoutConstraint(item: image, attribute: .bottom, relatedBy: .equal, toItem: cell.contentView, attribute: .bottom, multiplier: 1, constant: 0)
//let leftConstr = NSLayoutConstraint(item: image, attribute: .leading, relatedBy: .equal, toItem: cell.contentView, attribute: .leading, multiplier: 1, constant: 0)
//let rightConstr = NSLayoutConstraint(item: image, attribute: .trailing, relatedBy: .equal, toItem: cell.contentView, attribute: .trailing, multiplier: 1, constant: 0)
let heightConstr = NSLayoutConstraint(item: image, attribute: .height, relatedBy: .equal, toItem: nil , attribute: .notAnAttribute, multiplier: 1, constant: 10)
image.addConstraint(bottomConstr)
//image.addConstraint(leftConstr)
//image.addConstraint(rightConstr)
image.addConstraint(heightConstr)

You need to add constraint on Superview. so, add it on cell contentView.
cell.contentView.addConstraint(bottomConstr)
cell.contentView.addConstraint(heightConstr)

Constraints weren't working well for me when zooming UICollectionView. Here's how I resized a label to always fill the cell.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mycell", for: indexPath as IndexPath) as! SliceCollectionViewCell
cell.label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath) {
guard let cell = cell as? MyCollectionViewCell else {
print("ERROR: wrong cell type")
return
}
cell.label.frame = CGRect(x: 0, y: 0, width: cell.frame.width, height: cell.frame.height)
}

Related

UICollectionView Not Responding to Touch

I am trying to programmatically draw my CollectionView from within a custom class that subclasses UIView. The app loads up with no errors and the CollectionView is being drawn correctly as I can see the cell items, but the CollectionView does not respond to touch. When I try to scroll nothing happens.
I inspected the ViewController using the Debug View Hierarchy button in Xcode to see if there was possibly a view on top. There was not and I could see the cells clearly drawn separately.
Class:
class DrawUI_mainCollectionView : UIView, UICollectionViewDelegate, UICollectionViewDataSource {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
var collectionView : UICollectionView?
private func setup() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: CGFloat(kScreenWidth),height: 137)
let offset = 120.0
let collectionViewFrame = CGRect(x: 0, y: offset, width: Double(kScreenWidth), height: Double(kScreenHeight))
self.collectionView = UICollectionView(frame: collectionViewFrame, collectionViewLayout: layout)
self.collectionView!.alwaysBounceVertical = true
self.collectionView!.delegate = self
self.collectionView!.dataSource = self
self.collectionView!.register(UINib(nibName: "MealCategoryCell", bundle:Bundle.main), forCellWithReuseIdentifier: "cell")
self.addSubview(self.collectionView!)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MealCategoryCell
cell.label.text = "test"
cell.textDescription.text = "textDescription"
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: kScreenWidth, height: 137)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("cell \(indexPath.row)")
}
required init?(coder aDecoder: NSCoder) {
fatalError("no storyboard...")
}
}
How I am implementing it on my ViewController
let collectionView = DrawUI_mainCollectionView()
self.view.addSubview(collectionView)
What am I doing wrong? Thanks in advance.
I think your main issue is not using auto layout, if you will add auto layout for the items:
view that contains the collectionView in viewController
collectionView in the view
It should work, the implementation:
In ViewController:
let collectionView = DrawUI_mainCollectionView()
collectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(collectionView)
NSLayoutConstraint(item: collectionView , attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leadingMargin, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: collectionView , attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailingMargin, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: collectionView , attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .topMargin, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: collectionView , attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottomMargin, multiplier: 1.0, constant: 0.0).isActive = true
Inside the view:
private func setup() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: CGFloat(kScreenWidth),height: 137)
let offset = 120.0
let collectionViewFrame = CGRect(x: 0, y: offset, width: Double(kScreenWidth), height: Double(kScreenHeight))
self.collectionView = UICollectionView(frame: collectionViewFrame, collectionViewLayout: layout)
self.collectionView!.alwaysBounceVertical = true
self.collectionView!.delegate = self
self.collectionView!.dataSource = self
self.collectionView!.register(UINib(nibName: "MealCategoryCell", bundle:Bundle.main), forCellWithReuseIdentifier: "cell")
self.collectionView!.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.collectionView!)
NSLayoutConstraint(item: collectionView , attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leadingMargin, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: collectionView , attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailingMargin, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: collectionView , attribute: .top, relatedBy: .equal, toItem: self, attribute: .topMargin, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: collectionView , attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottomMargin, multiplier: 1.0, constant: 0.0).isActive = true
}

collectionView hide my custom view in header

I'm trying to add customView into headerView of my collectionView. Here's my code:
I registed the headerView class in viewDidLoad:
collectionView.register(SearchReuseView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "reuseCell")
Then I setup the collectionView:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
var reuseView: UICollectionReusableView?
if indexPath.section == 0 || banners.count == 0 {
let view = UICollectionReusableView(frame: CGRect.zero)
reuseView = view
} else {
if kind == UICollectionElementKindSectionHeader {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "reuseCell", for: indexPath) as! SearchReuseView
view.backgroundColor = .black
view.delegate = self
view.frame = CGRect(x: 0, y: 0, width: collectionView.frame.width, height: 100)
view.item = banners[indexPath.section % banners.count]
return view
}
}
return reuseView!
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 0 {
return CGSize.zero
}
return CGSize(width: collectionView.frame.width, height: 100)
}
But when I run on Simulator, the collectionView shows the header but nothing inside.
Here is code of SearchReuseView :
class SearchReuseView: UICollectionReusableView {
weak var delegate: SearchReuseDelegate?
let adImageView: UIImageView = {
let img = UIImageView()
img.translatesAutoresizingMaskIntoConstraints = false
img.contentMode = .scaleAspectFit
return img
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.setupViews()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.setupViews()
}
func setupViews() {
backgroundColor = .black
adImageView.isUserInteractionEnabled = true
adImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
self.addConstraint(NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: self.adImageView, attribute: .bottom, multiplier: 1, constant: 0))
self.addConstraint(NSLayoutConstraint(item: self, attribute: .right, relatedBy: .equal, toItem: self.adImageView, attribute: .right, multiplier: 1, constant: 0))
self.addConstraint(NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: self.adImageView, attribute: .top, multiplier: 1, constant: 0))
self.addConstraint(NSLayoutConstraint(item: self, attribute: .left, relatedBy: .equal, toItem: self.adImageView, attribute: .left, multiplier: 1, constant: 0))
addSubview(adImageView)
}
var item: Banner! {
didSet {
guard let img = item.img else {return}
let url = URL(string: "\(img)")
self.adImageView.kf.setImage(with: url, placeholder: nil, options: [.transition(ImageTransition.fade(1))], progressBlock: { (receivedSize, totalSize) in
}) { (image, error, cacheType, imageURL) in
if error != nil {}
}
}
}
#objc func handleTap(){
guard let link = item.link else {
return
}
delegate?.didTapAds(link: link)
}
}
I wonder why your app is not crashing you should add subview first then add constraints to it
Replace your code with following code
func setupViews() {
backgroundColor = .black
addSubview(adImageView)
addSubview.translatesAutoresizingMaskIntoConstraints = false
adImageView.isUserInteractionEnabled = true
adImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
self.addConstraint(NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: self.adImageView, attribute: .bottom, multiplier: 1, constant: 0))
self.addConstraint(NSLayoutConstraint(item: self, attribute: .right, relatedBy: .equal, toItem: self.adImageView, attribute: .right, multiplier: 1, constant: 0))
self.addConstraint(NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: self.adImageView, attribute: .top, multiplier: 1, constant: 0))
self.addConstraint(NSLayoutConstraint(item: self, attribute: .left, relatedBy: .equal, toItem: self.adImageView, attribute: .left, multiplier: 1, constant: 0))
}
Hope it is helpful
if kind == UICollectionElementKindSectionHeader {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "reuseCell", for: indexPath) as! SearchReuseView
view.backgroundColor = .black
view.delegate = self
view.frame = CGRect(x: 0, y: 0, width: collectionView.frame.width, height: 100) //delete this line
view.item = banners[indexPath.section % banners.count]
return view
}
I delete the line set frame for header view and it's works

Shadow rate on UITableViewCell contenview increasing while scrolling

In my app, I'm using dynamic height cells using auto layout. So for creating cardview effect I've to use tableview willdisplaycell method. It only add shadow only once to cell. But I don't know why shadow increasing while scrolling.
Here's my code
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
//setup card view style on cell
if !cellArray.contains(indexPath.row) {
cell.contentView.backgroundColor = UIColor.clear
let whiteRoundedView : UIView = UIView(frame: CGRect(x: 5.0, y: 5.0, width: cell.contentView.frame.size.width-10, height: cell.contentView.frame.size.height-10))
whiteRoundedView.layer.backgroundColor = UIColor.white.cgColor
whiteRoundedView.layer.masksToBounds = false
whiteRoundedView.layer.cornerRadius = 5.0
whiteRoundedView.layer.shadowOffset = CGSize(width: -1, height: 1)
whiteRoundedView.layer.shadowOpacity = 0.5
cell.contentView.addSubview(whiteRoundedView)
cell.contentView.sendSubview(toBack: whiteRoundedView)
cellArray.add(indexPath.row)
}
}
this piece of line is the causing the issue.
cell.contentView.addSubview(whiteRoundedView)
whenever willDisplayCell gets called, you will be adding the view everytime. That is the reason shadow is getting increased while scrolling. The solution is,
1. check the cell content view if the view is already added or not. If not, then add the view. use view tag to do it.
2. otherwise, create a shadow view and initialize the specific things in the -(void)awakeFromNib, which will get called only once.
but personally, i prefer option 2, which will isolate your view render logic from view controller or cell.
add the below code to your custom cell class. In this case, i have given leading-trialing-top-bottom constraints as 15-15-15-15. If you wish you can set it to 0. but make sure that it should in sync with the background constraints.
override func awakeFromNib() {
super.awakeFromNib()
contentView.backgroundColor = UIColor.clear
let whiteRoundedView : UIView = UIView(frame: CGRect(x: 5.0, y: 5.0, width: contentView.frame.size.width-10, height: contentView.frame.size.height-10))
whiteRoundedView.layer.backgroundColor = UIColor.white.cgColor
whiteRoundedView.layer.masksToBounds = false
whiteRoundedView.layer.cornerRadius = 5.0
whiteRoundedView.layer.shadowOffset = CGSize(width: -1, height: 1)
whiteRoundedView.layer.shadowOpacity = 0.5
whiteRoundedView.tag = shadowTag
whiteRoundedView.translatesAutoresizingMaskIntoConstraints=false
contentView.addSubview(whiteRoundedView)
contentView.sendSubview(toBack: whiteRoundedView)
let leading = NSLayoutConstraint(item: whiteRoundedView,
attribute: .leading,
relatedBy: .equal,
toItem: contentView,
attribute: .leading,
multiplier: 1.0,
constant: 15.0)
let trailing = NSLayoutConstraint(item: whiteRoundedView,
attribute: .trailing,
relatedBy: .equal,
toItem: contentView,
attribute: .trailing,
multiplier: 1.0,
constant: -15.0)
let top = NSLayoutConstraint(item: whiteRoundedView,
attribute: .top,
relatedBy: .equal,
toItem: contentView,
attribute: .top,
multiplier: 1.0,
constant: 15.0)
let bottom = NSLayoutConstraint(item: whiteRoundedView,
attribute: .bottom,
relatedBy: .equal,
toItem: contentView,
attribute: .bottom,
multiplier: 1.0,
constant: -15.0)
contentView.addConstraint(leading)
contentView.addConstraint(trailing)
contentView.addConstraint(top)
contentView.addConstraint(bottom)
}
Ref screenshot :
You can add your code here
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: <idenitfier>, for: indexPath) as? JobListTableViewCell
if cell == nil {
//Initialization and setting of shadow
}
return cell
}
Hope this helps

UITableViewAutomaticDimension with programmatically added images

I have a problem with UITableViewAutomaticDimension:
In my tableview I have multiple imageviews created programmatically with calculated height:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "managePlayset", for: indexPath) as! ManagePlaysetTableViewCell
let playset = playsets[indexPath.row]
cell.playsetName.text = playset.name
if (playset.musicItems?.count)! > 0 {
var i = 0
for musicItem in (playset.musicItems?.array)! {
let imageView = UIImageView(image: (musicItem as! MusicItem).getFirstPhoto())
imageView.frame = CGRect(x: cell.contentView.frame.origin.x+CGFloat(i)*(imageWidth+10.0), y: cell.contentView.frame.origin.y+40, width: imageWidth, height: imageWidth)
imageView.contentMode = .scaleAspectFit
cell.contentView.addSubview(imageView)
let constraint = NSLayoutConstraint(item: imageView,
attribute: NSLayoutAttribute.bottomMargin, relatedBy:
NSLayoutRelation.equal, toItem: cell.contentView,
attribute: NSLayoutAttribute.bottomMargin, multiplier: 1,
constant: 0)
cell.contentView.addConstraint(constraint)
i = i + 1
}
}
cell.editButton.tag = indexPath.row
return cell
}
In viewDidLoad I set:
let width = view.frame.width
imageWidth = width / CGFloat(maxItemsOnPage) - 10.0
tableView.estimatedRowHeight = imageWidth + 20.0
tableView.rowHeight = UITableViewAutomaticDimension
But height is not adjusted properly (images are cut off)
Any advice is highly appreciated!
I think it's because UIImageView created programmatically.
Then it don't use autolayout system.
To change it, you have to add
imageView.translatesAutoresizingMaskIntoConstraints = false in your loop.
Have you tried constraining both top and bottom edges of the imageView? i.e.
let constraintTop = NSLayoutConstraint(item: imageView,
attribute: NSLayoutAttribute.top, relatedBy:
NSLayoutRelation.equal, toItem: cell.contentView,
attribute: NSLayoutAttribute.topMargin, multiplier: 1,
constant: 0)
let constraintBottom = NSLayoutConstraint(item: imageView,
attribute: NSLayoutAttribute.bottom, relatedBy:
NSLayoutRelation.equal, toItem: cell.contentView,
attribute: NSLayoutAttribute.bottomMargin, multiplier: 1,
constant: 0)
cell.contentView.addConstraints([constraintTop, constraintBottom])

Place UILabel center of parent

I am trying to put UILable at the center of each cell of UICollectionView.
Here is my code:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CollectionViewCell
// Configure the cell
cell.backgroundColor = UIColor.brownColor()
let title = UILabel(frame: CGRectMake(cell.bounds.midX, cell.bounds.midY, cell.bounds.size.width, 20)) //
title.text = "ddd"
title.textColor = UIColor.whiteColor()
// title.center = cell.center
cell.contentView.addSubview(title)
// let xCenterConstraint = NSLayoutConstraint(item: title, attribute: .CenterX, relatedBy: .Equal, toItem: cell, attribute: .CenterX, multiplier: 1, constant: 0)
// let yCenterConstraint = NSLayoutConstraint(item: title, attribute: .CenterY, relatedBy: .Equal, toItem: cell, attribute: .CenterY, multiplier: 1, constant: 0)
// let leadingConstraint1 = NSLayoutConstraint(item: title, attribute: .Leading, relatedBy: .Equal, toItem: cell, attribute: .Leading, multiplier: 1, constant: 20)
// let trailingConstraint1 = NSLayoutConstraint(item: title, attribute: .Trailing, relatedBy: .Equal, toItem: cell, attribute: .Trailing, multiplier: 1, constant: -20)
// cell.addConstraints([xCenterConstraint, yCenterConstraint, leadingConstraint1, trailingConstraint1])
// title.centerXAnchor.constraintEqualToAnchor(cell.centerXAnchor).active = true
// title.centerYAnchor.constraintEqualToAnchor(cell.centerYAnchor).active = true
return cell
}
The output:
Seems I need to change my code, so what am I missing?
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CollectionViewCell
// Configure the cell
cell.backgroundColor = UIColor.brownColor()
//Editing Starts
let title = UILabel(frame: CGRectMake(cell.bounds.midX -(cell.bounds.size.width/2) , cell.bounds.midY - 10, cell.bounds.size.width, 20))
//Editing Ends
//
title.text = "ddd"
title.textColor = UIColor.whiteColor()
// title.center = cell.center
cell.contentView.addSubview(title)
// let xCenterConstraint = NSLayoutConstraint(item: title, attribute: .CenterX, relatedBy: .Equal, toItem: cell, attribute: .CenterX, multiplier: 1, constant: 0)
// let yCenterConstraint = NSLayoutConstraint(item: title, attribute: .CenterY, relatedBy: .Equal, toItem: cell, attribute: .CenterY, multiplier: 1, constant: 0)
// let leadingConstraint1 = NSLayoutConstraint(item: title, attribute: .Leading, relatedBy: .Equal, toItem: cell, attribute: .Leading, multiplier: 1, constant: 20)
// let trailingConstraint1 = NSLayoutConstraint(item: title, attribute: .Trailing, relatedBy: .Equal, toItem: cell, attribute: .Trailing, multiplier: 1, constant: -20)
// cell.addConstraints([xCenterConstraint, yCenterConstraint, leadingConstraint1, trailingConstraint1])
// title.centerXAnchor.constraintEqualToAnchor(cell.centerXAnchor).active = true
// title.centerYAnchor.constraintEqualToAnchor(cell.centerYAnchor).active = true
return cell
}
Try following code it may helps you
let title = UILabel(frame: CGRectMake(0, 0, cell.bounds.size.width, 20))
title.textAlignment=.Center
title.text = "ddd"
title.textColor = UIColor.whiteColor()
title.center = cell.contentView.center
cell.contentView.addSubview(title)

Resources