Add View Outside of ContentView in Cell - ios

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

Related

SWIFT: programmatically created constraints not updating properly

So I have a UIView near the bottom of the superview, with a textfield inside of it. When the user taps inside the textfield to begin editing, I am bringing up the entire UIView with the keyboard. One problem with this, is that if you have constraints on said UIView, when you start typing in the textfield, the UIView conforms to its constraints and goes back down to its original spot, hidden by the keyboard. I created a work around by overriding updateViewContraints(), removing the default constraint (the one I set in storyboard), when the textfield is being edited, and adding a new constraint to keep it where I want it. Then, when the editing ends, the code is supposed to bring the UIView back down, and remove the new constraint, and replace it with the original. Here's that code:
override func updateViewConstraints() {
// The new constraint, active when the keyboard is shown
let constraintWhenKeyboardShown = NSLayoutConstraint(item: searchRadiusView!, attribute: .bottom, relatedBy: .equal, toItem: super.view, attribute: .bottom, multiplier: 1.0, constant: -310.0)
// The default constraint, active at all other times
let defaultConstraint = NSLayoutConstraint(item: searchRadiusView!, attribute: .bottom, relatedBy: .equal, toItem: super.view, attribute: .bottom, multiplier: 1.0, constant: -30.0)
if radiusTextfield.isEditing {
view.removeConstraint(defaultConstraint)
view.addConstraint(constraintWhenKeyboardShown)
print("new")
} else {
view.removeConstraint(constraintWhenKeyboardShown)
view.addConstraint(defaultConstraint)
print("default")
}
self.view.layoutIfNeeded()
super.view.updateConstraints()
}
I call the updateViewConstraints in each of my textfield methods:
// Raises the searchRadiusView upon editing
func textFieldDidBeginEditing(_ textField: UITextField) {
let newSearchRadiusViewYValue = self.searchRadiusView.center.y - 280
searchRadiusView.center.y = newSearchRadiusViewYValue
print(searchRadiusView.constraints)
self.updateViewConstraints()
}
// Lowers the searchRadiusView upon dismissal
func textFieldDidEndEditing(_ textField: UITextField) {
let newSearchRadiusViewYValue = self.searchRadiusView.center.y + 280
searchRadiusView.center.y = newSearchRadiusViewYValue
print("ended")
print(searchRadiusView.constraints)
self.updateViewConstraints()
}
However, upon ending editing, my UIView is not moving back down to its original position, and the debugger says
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x607000071590 UIView:0x61300002f280.bottom == UIView:0x613000071380.bottom - 310 (active)>",
"<NSLayoutConstraint:0x607000297f80 UIView:0x61300002f280.bottom == UIView:0x613000071380.bottom - 30 (active)>"
)
It seems that the 'constraintWhenKeyboardShown' constraint is still there (the one ending in -310), even though I have 'view.removeConstraint(constraintWhenKeyboardShown)
' in the updateViewConstraints() function. Is that what's causing my problem? Does anyone know how to fix this? Thanks

Storyboard shared constants for constraints

Is there any way to share constants between constraints in a storyboard (or better yet define them globally and use when needed)?
Say, I want a distance from top to be equal to the distance from the left - those are not achievable by setting any sort of symmetry, and I don't want to change both of them every single time, just change one and see the result.
I want a storyboard (clickable) solution, no coding required.
Well, you can't use defined constants in storyboard, but it is possible to share properties. Depending on how complex your design is it might not be worth the trouble. If you just want the same distance shared between two items you are probably better off editing their value. If you have several dependencies you can create "spacers" that share the same sizes.
It is quite simple, just add two or more (hidden) UIView objects to your storyboard. Choose one to be the master item, then set the others to have the size properties of that. The master can be set to have ratio 1:1, so that you only need to set and change the height of it to resize all of them in both X and Y. You then align your other items to these objects.
Also note that you can have other values than 1:1 for the multiplier.
Another note: if you want just one view to be positioned, one such hidden view will suffice.
It's hard to say without more info, but I think the best solution would be to subclass UIView, something like this:
SWIFT 4.0
class MySymmetricView {
#IBInspectable var inset: Float = 10.0 // Put your constraints here, #IBInspectable will expose them to IB, you can also set your constants here
required init?(coder: NSCoder) {
super.init(coder: coder)
setupConstraints()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupConstraints()
}
func setupConstraints() {
NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: superview, attribute: .leadingMargin, multiplier: 1.0, constant: inset).isActive = true
NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: superview, attribute: .leadingMargin, multiplier: 1.0, constant: inset).isActive = true
}
}
Or subclass the constraints themselves in a similar way.

view hierarchy is not prepared

I am a newbie swift developer, following code examples in a good iOS 9 Swift book. The book has just demonstrated how to add a constraint (in code) between two objects (label, button) that are in two different branches of the view hierarchy.
myLabel is in viewBB container, which is in the top view controller.
myButton is in the top view controller, outside of viewBB.
The example code worked fine. But for an exercise, I wanted to create a second label2 inside of viewBB, and constrain its position to be offset from myLabel. So both labels (I think) are in viewBB.
Creating label2 in code and adding the constraints in code works fine.
But... if I create label2 in the interface builder, and try to remove the IB constraints and replace them with my own, the build succeeds but the app crashes with the usual "view hierarchy not prepared" error.
*** The interesting thing is the error message talks about a label and a button, suggesting that the book example code (I called it BLOCK 0 below) is the offender.
What am I doing wrong? (I've read everything I can find on the error message, including 4 posts in this forum. But I'm at a loss to know why the book code is failing.)
The error message:
2016-02-25 12:52:32.796 TwoViewConstraints[5713:1860768]
The view hierarchy is not prepared for the constraint: NSLayoutConstraint:0x7c9ce990 UILabel:0x7c9dbec0'Label'.centerX ==
UIButton:0x7c9deed0'Button'.centerX
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.
Message from debugger: Terminated due to signal 15
Here is my code:
class ViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var myButton: UIButton!
#IBOutlet weak var centerConstraint: NSLayoutConstraint!
#IBOutlet weak var viewBB: UIView!
#IBOutlet weak var lab2cxh: NSLayoutConstraint!
#IBOutlet weak var lab2cxv: NSLayoutConstraint!
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// BLOCK 0 - works fine, from the iOS9 book
// myLabel and myButton were created with the interface builder
// Goal is to remove an IB constraint, and replace it with one
// that is built in code. (Because label and buttons are in
// different branches of the view hierarchy).
//
// remove auto center constraint from parent label
viewBB.removeConstraint(centerConstraint)
// center the label and the button across views
let ccx = NSLayoutConstraint(item: myLabel,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: myButton,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: 0)
self.view.addConstraint(ccx)
// BLOCK 1 - Create a second label object in code
// this code works fine with the two added constraints below
// create a second label object and add it to the view
// let label2 = UILabel()
// label2.translatesAutoresizingMaskIntoConstraints = false
// label2.text="Label2"
// viewBB.addSubview(label2)
// BLOCK 2 - create a second label object in the interface builder
// Suggested horiz/vert constraints are set in the IB.
// So now I want to remove them, and replace them with my own,
// as was done in the Swift book example code in BLOCK 0.
// remove original label 2 constraints
viewBB.removeConstraint(lab2cxv)
viewBB.removeConstraint(lab2cxh)
// adding these two constraints works fine when the label2 object
// is created in code. But when I create label2 in the interface
// builder and try to remove the IB constraints (as was done in the
// first block of code), I get the error:
//
// ... "The view hierarchy is not prepared
// for the constraint "Label.CenterX to Button.CenterX", which is
// the first block of code.
//
// NOTE** To me, it looks like the error is coming from the
// BLOCK 0 constraint, since it cites a label and a button, not
// two labels.
// What am I doing wrong?
let c2h = NSLayoutConstraint(item: label2,
attribute: NSLayoutAttribute.CenterY,
relatedBy: NSLayoutRelation.Equal,
toItem: myLabel,
attribute: NSLayoutAttribute.CenterY,
multiplier: 1.0,
constant: -50)
self.viewBB.addConstraint(c2h)
// constrain label 2 to be diagonally left of label one
let c2v = NSLayoutConstraint(item: label2,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: myLabel,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: -100)
self.viewBB.addConstraint(c2v)
}
I did the whole piece of code over again, from scratch, and it worked. Go figure. Here is the code that worked properly. As far as I can see, it is pretty much the same code, with the exception of a minor name change here and there.
I have no idea why this version works, and the previous version did not. It makes me wonder if something was different in the Interface Builder setup.
class ViewController: UIViewController {
#IBOutlet weak var viewbb: UIView!
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var centerConstraint: NSLayoutConstraint!
#IBOutlet weak var myButton: UIButton!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label2centerConstraint: NSLayoutConstraint!
#IBOutlet weak var label2Yconstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// remove the IB centering constraint, then add our own between label and button
viewbb.removeConstraint(centerConstraint)
// make a new constraint to center label over button
let newcon = NSLayoutConstraint(item: myLabel,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: myButton,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: 0)
self.view.addConstraint(newcon)
// remove the IB centering constraint, then add our own between label and button
viewbb.removeConstraint(label2centerConstraint)
viewbb.removeConstraint(label2Yconstraint)
// make a new constraint to center label over button
let newcon2 = NSLayoutConstraint(item: label2,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: myLabel,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: -100) // left of mylabel
// make a new constraint to center label over button
let newcon3 = NSLayoutConstraint(item: label2,
attribute: NSLayoutAttribute.CenterY,
relatedBy: NSLayoutRelation.Equal,
toItem: myLabel,
attribute: NSLayoutAttribute.CenterY,
multiplier: 1.0,
constant: 0) // left of mylabel
self.view.addConstraint(newcon3)
self.view.addConstraint(newcon2)
}

How to make a button half the width of a UITableViewCell using constraints (Storyboard or Programatically)

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.

How To Properly Add Child View Controller in iOS 8 With Swift

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.

Resources