I designed a drop down menu for Swift 4. In order to do this I used a button and tableview. I am trying to center the imageview() by using cell.imageview but I get an error.
Here is my code :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
customImageView.translatesAutoresizingMaskIntoConstraints = false
cell.backgroundColor = UIColor.white
cell.imageView?.image = dropDownOptions[indexPath.row].bankImage
// Constraints that i used.
cell.imageView?.addConstraint(NSLayoutConstraint(item: cell.contentView, attribute: .centerX, relatedBy: .equal, toItem: customImageView, attribute: .centerX, multiplier: 1.0, constant: 0))
cell.imageView?.addConstraint(NSLayoutConstraint(item: cell.contentView, attribute: .centerY, relatedBy: .equal, toItem: customImageView, attribute: .centerY, multiplier: 1.0, constant: 0))
return cell
}
Here is error when I try to give constraints.
Full Project
DropDownExample[4701:223823] [LayoutConstraints] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x60c000285190 UITableViewCellContentView:0x7f9386d09ad0.centerX == UIImageView:0x7f9386d04cf0.centerX (inactive)>
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView(UIConstraintBasedLayout) _viewHierarchyUnpreparedForConstraint:] to debug.
2018-10-11 17:13:24.499450+0300 DropDownExample[4701:223823] [LayoutConstraints] View hierarchy unprepared for constraint.
First better don't create a new UITableViewCell() better use:
tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier")
This will create new cells only if needed. But be aware of the reuse case.
In your case you always get a UITableViewCell with default style. This results in an image and a textlabel. You can't customize this kind of cell well.
Better create a prototype cell inside your storyboard and dequeue it. A custom cell can get customized completely.
If you don't wont to use storyboards - add a Custom TableViewCell class with a xib file and register it for reuse within the tableview.
tableView.register(UINib(nibName: "myCell", bundle: nil), forCellReuseIdentifier: "myCell")
Related
I have a collection view cell and I want to add a view to it. Apple states that views should be added to the contentView. This is there documentation:
When configuring a cell, you add any custom views representing your cell’s content to this view. The cell object places the content in this view in front of any background views.
https://developer.apple.com/documentation/uikit/uicollectionviewcell/1620133-contentview
However, it seems I can also add view not inside the contentView and there is no warnings or crashes. Here is my code. Notice the comment QUESTION -- I want to know if this is ok to do:
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet private var imageView: UIImageView!
private let titleLabel = UILabel(frame: .zero)
override func awakeFromNib() {
super.awakeFromNib()
// QUESTION: Is this ok? Notice I am not adding the `titleLabel` to `self.contentView`
self.addSubview(titleLabel)
let horizontalCenterConstraint = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1.0, constant: 0)
let verticalCenterConstraint = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0)
self.addConstraint(horizontalCenterConstraint)
self.addConstraint(verticalCenterConstraint)
}
}
In the code above, instead of adding the view with self.contentView.addSubview... instead I simply do self.addSubview. Is this ok?
Most things are possible, The question is why do you want to do this?
By directly going against an API author's recommendation, you're risking a whole host of potential issues. e.g.
You may be breaking something you're unaware of
You may be adversely affecting performance
Even if it works currently, it may break in the future and the author has no reason to prevent this from happening.
If what you're trying to do is impossible otherwise and you're ok with the risks, go for it. Otherwise, stick to the author's explicit instructions.
Yes, I tried to add cell directly instead of contentView. Now, weird thing happens, the collection view could not scroll. It took me 1 hour to figure out that I should add itself to contentView
I have a label in the cell, now i am customize my cell and added a label into the cell. I am creating a chatting apps, however on the top of the conversation , i need to determine the top constraint suppose to given to the profile image or the message label, if the message label only got one line , the top constraint is given to the profile image, if the message label is taller than the profile image, i will give the constraint to the message label. Ok the logic sound good, but the problem is come, i added the above logic under the initialiser as below
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
print(messageLabel.frame.height)
}
However, when the cell is being created, the print result gave to me is 0, in the end i suspect the message label is havent created so the height is 0.
In this case, i use the following function
override func layoutSubviews() {
super.layoutSubviews()
if messageLabel.frame.height != 0
{
if self.messageLabel.tag != 1
{
messageLabel.tag = 1
if (bubbleImageView.frame.height > 60)
{
contentView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1, constant: 4.5))
} else
{
contentView.addConstraint(NSLayoutConstraint(item: profileImageView, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1, constant: 4.5))
}
}
}
}
However it is work, the message label returned me the value of the height, but there is a bug on the above code which is, the cell is display first, the code only can be execute , in this case , when i debug it in the simulator, it took one second before execute the code, in this case, the user will see how the cell transform from none constraint to have constraint which is a bad practice for the user experience. In the end, i have to return back how to get the number of lines of the message label before the cell being displayed. So i can i achieve it ?
You don't want to do this in func layoutSubviews() since your code now will add the constraint several times (func layoutSubviews() is executed more than once). The issue you are having is pretty much what your conclusion is, the label hasn't been laid out yet since this will happen when layoutSubviews() is executed. What you can do however is to force the layoutSubviews() to run before you add your constraint.
I don't really understand which views needs to be forced though, but in viewDidLoad for your UITableViewController you do
view.setNeedsLayout()
view.layoutIfNeeded()
and in your init for your cell you do the same for self. Maybe even for the label itself.
This function will return the number of line tableview will show
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray.count
}
It is numberOfRowsInSection from tableView delegate function
I have a label in a custom cell and it won't resize. Here is the code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell2 = tableView.dequeueReusableCellWithIdentifier("privatecell1", forIndexPath: indexPath) as! YourPrivateControllerCell
myCell2.barfront1.frame.size.width = 200
myCell2.barfront1.layer.masksToBounds = true
return myCell2
}
I know I can set the width by adding a constraint but the label will be a different size for every row. The code works for a normal view controll view but doesn't seem to work for a tableview cell/row. The actual code will be:
myCell2.barfront1.frame.size.width = myCell2.barback1.frame.size.width * percent
but I cant even get the label to resize to 200.
You can use constraints and connect the constraints with #IBOutlet like this. Of course you have to put this in the YourPrivateControllerCell.
#IBOutlet weak var labelWidth: NSLayoutConstraint? = nil
Then call the following method will be change the width of you label.
labelWidth.constant = 200
Good Luck.
I got it to work. Instead of setting a width for the label I added a layout width constraint. Code:
myCell2.barfront1.addConstraint(NSLayoutConstraint(item: myCell2.barfront1, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: myCell2.barback1.frame.size.width * barwidth1[indexPath.row] * 0.01))
Now the label is a different size for every row.
I have a project I'm working on that needs two buttons with some data that will take up exactly half the width of the UITableViewCell that they are in.
In the past when I have wanted to do this I usually set a constraint that the button will be equal widths to its superview and give it a multiplier of 0.5.
For some reason however inside the UITableViewCell I can't get the Storyboards to give me this option. The "Equal Widths" constraint in the GUI is grayed out.
I resolved to just do it programmatically so in the custom cell I tied the following code. I've tried putting the cellInit() method below being called in the awakeFromNib and that gave an error. I've tried also just calling it on cellForRowAtIndexPath when the cell is loaded, and got the same error.
import UIKit
class PollCell: UITableViewCell {
#IBOutlet weak var option1: UIButton!
#IBOutlet weak var option2: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
//cellInit() //Commented out because causes error
}
func cellInit(){
option1.addConstraint(NSLayoutConstraint(item: option1, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: 0.5, constant: 0))
}
}
This is the error that I am getting:
'NSInternalInconsistencyException', reason: 'Impossible to set up layout with view hierarchy unprepared for constraint.
What I'm trying to achieve is pretty standard, so I assume this isn't anything to crazy and I'm probably doing something the wrong way. Either way I assume plenty of newcomers like myself will run into this. Thanks in advance!
In the comments, we discussed that you leverage contentView.frame.maxX
Alternatively, you can use AutoLayout: Make sure you setTranslatesAutoresizingMaskIntoConstraints(false)
Assign tags (optional). You only havetwo buttons but for more than two, I would use tags so you don't need to manually type UIButton for every button.
addConstraint(NSLayoutConstraint(item: self.viewWithTag(1) as UIButton, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 0.33, constant: 0))
addConstraint(NSLayoutConstraint(item: self.viewWithTag(2) as UIButton, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 0.66, constant: 0))
OR VFL using a Dictionary:
for button in buttonsDictionary.keys {
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[\(button1)]-[\(button2)]|", options: .allZeros, metrics: nil, views: buttonsDictionary))
}
call cell.updateConstraints() in your cellForRowAtIndexPath in TableView.
You can learn more in the link below: They have an example of two side by side buttons:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutinCode/AutoLayoutinCode.html#//apple_ref/doc/uid/TP40010853-CH11-SW1
I've encountered a similar problem like yours before. What I did was put a UIView in the cell first with its top, left, right, bottom constraints set to all 0, then place the button on top of the view. This way I get the 'equal width' option.
I've been going through the documentation and still seem to be on a sticking point.
I have a view controller object C_SelectPhoto. This has a container view. Inside the container view I want the childed view controller, C_SelectPhotoControllerView, to fit inside it. It will just be an array of photos. However, setting the frame and adding the child view controller is not working. If I move the x value of the desired child view controller, no effect happens.
To figure out what is going on I color coded everything. The container, below, is orange. The view the container expects, according to the storyboard is yellow. The view I actually want to fit in there is red.
Here is the storyboard:
Here is my controller code for C_SelectPhoto
class C_SelectPhoto:Controller
{
#IBOutlet weak var selectPhotoControllerView: UIView!
var _collectionViewController:C_SelectPhotoControllerView!
//TODO PERMISSION IS NEEDED BEFORE FETCHING
func initController()
{
_collectionViewController = Controller.STORYBOARD.instantiateViewControllerWithIdentifier("selectPhotoControllerView") as C_SelectPhotoControllerView
displayControllerViewController()
}
//show the photo selection
private func displayControllerViewController()
{
addChildViewController(_collectionViewController)
_collectionViewController.view.frame = CGRectMake(100, 0, 500, 500)
self.view.addSubview(_collectionViewController.view)
_collectionViewController.didMoveToParentViewController(self)
}
}
However the result is produces is below:
First, the yellow class shouldn't be added at all, I wanted only the red (the UICollectionViewController class). Second, I can tell the red class is being added to the wrong spot because its x value hasn't moved it over at all.
So my question is:
How can I add a UIContainerViewController, as a child to the main view controller, C_SelectPhoto, but have the UIContainerViewController frame FIT the container I have in the main view controller?
Thank you!!!
NOTE: The views I am trying to add are UICollectionViewControllers. When I add a UIViewController, the framing works just fine, but as you can see when adding the UICollectionViewControllers, the framing does NOT work, and they are getting added to random offsets and are not respecting my attempts to size them with frame assignments.
use following Extension for adding childViewController On View
extension UIViewController {
func configureChildViewController(childController: UIViewController, onView: UIView?) {
var holderView = self.view
if let onView = onView {
holderView = onView
}
addChildViewController(childController)
holderView.addSubview(childController.view)
constrainViewEqual(holderView, view: childController.view)
childController.didMoveToParentViewController(self)
childController.willMoveToParentViewController(self)
}
func constrainViewEqual(holderView: UIView, view: UIView) {
view.translatesAutoresizingMaskIntoConstraints = false
//pin 100 points from the top of the super
let pinTop = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal,
toItem: holderView, attribute: .Top, multiplier: 1.0, constant: 0)
let pinBottom = NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal,
toItem: holderView, attribute: .Bottom, multiplier: 1.0, constant: 0)
let pinLeft = NSLayoutConstraint(item: view, attribute: .Left, relatedBy: .Equal,
toItem: holderView, attribute: .Left, multiplier: 1.0, constant: 0)
let pinRight = NSLayoutConstraint(item: view, attribute: .Right, relatedBy: .Equal,
toItem: holderView, attribute: .Right, multiplier: 1.0, constant: 0)
holderView.addConstraints([pinTop, pinBottom, pinLeft, pinRight])
}}
Updated for Swift 5+
Just one line in your view controller to add child view controller.
Super scalable methods in the extension if you want to add it on any custom view.
public extension UIViewController {
/// Adds child view controller to the parent.
///
/// - Parameter child: Child view controller.
func add(_ child: UIViewController) {
addChild(child)
view.addSubview(child.view)
child.didMove(toParent: self)
}
/// It removes the child view controller from the parent.
func remove() {
guard parent != nil else {
return
}
willMove(toParent: nil)
removeFromParent()
view.removeFromSuperview()
}
}
How to use:
Adding: In the view controller where you want to add the child view controller.
// let yourChildViewController = Load fro the storyboard or XIB
add(yourChildViewController)
Removing:
yourChildViewController.remove()
If you want the red controller to be the child controller, delete the yellow one, and control-drag from the container to the red controller. There's no need to add it in code, or do any resizing. The red controller will be set to the same size as the container in the storyboard.