So, I have the following XIB
This XIB when loaded as a tableviewcell looks like this
I've since decided that I will not need a TableView, so I changed my XIB class from UITableViewCell to UIView. In a ViewController I added this code to viewDidLoad()
var nView = MyChartView.instanceFromNib() as! MyChartView
self.view.addSubview(nView)
And I got this as a result
As you can see, it ignores the margins and continues to right side (ignore the red color since I was using it to try and debug the problem. No chart data is not the problem either). I've printed the xib's frame width and I've seen that it's quite a bit bigger than the screen size, but I've not been able to fix it. Anyone can figure out the problem?
When you add a subView programmatically, you should also add constraints between the subView and its superView
var nView = MyChartView.instanceFromNib() as! MyChartView
self.view.addSubview(nView)
nView.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[nView]|", options: [], metrics: nil, views: ["nView": nView]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[nView]|", options: [], metrics: nil, views: ["nView": nView]))
You haven't constrained the MyChartView instance's width, either by specifying an exact size when you add the subview, or programmatically adding constraints between the MyChartView instance and its superview. Without doing one or the other of these, the view's dimensions will match whatever they are in the xib.
Related
scroll view - layout programmatically - swift 3
Dear friends: I hope someone could revise this project and help me before my brain been burned. Thanks in advance.
The task: Horizontal Scroll - Layout an array of 4 images, square of 240 x 240 and 20 of spacing. The constraints for the scroll view set directly in the storyboard, but the images subviews had been added programmatically using Visual Format Language. Content size for scroll suppose done by this constraints.
What I have done: Set the array of images, create de ImageView programmatically and add the array using a for in loop. Create the constraints using the visual format. A way to do this can be found in this article: http://www.apeth.com/iOSBook/ch20.html.
Here the link to the project in GitHub
https://github.com/ricardovaldes/soloScrollEjercicio
Constraints for the ScrollView added directly in the storyboard.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myScroll: UIScrollView!
var carsArray = [UIImage]()
var constraints = [NSLayoutConstraint]()
override func viewDidLoad() {
super.viewDidLoad()
carsArray = [#imageLiteral(resourceName: "fasto1"), #imageLiteral(resourceName: "fasto2"), #imageLiteral(resourceName: "fasto3"), #imageLiteral(resourceName: "fasto4")]
var const = [NSLayoutConstraint]()
var views: [String: UIView]
var previous: UIImageView? = nil
for index in 0..<carsArray.count{
let newImageView = UIImageView()
newImageView.image = carsArray[index]
newImageView.translatesAutoresizingMaskIntoConstraints = false
myScroll.addSubview(newImageView)
self.myScroll.setNeedsLayout()
views = ["newImageView": newImageView, "myScroll": myScroll]
if previous == nil{
const.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:|[newImageView(240)]", metrics: nil, views: views))
}
else{
const.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:[previous]-20-[newImageView(240)]", metrics: nil, views: ["newImageView": newImageView, "previous": previous!]))
}
previous = newImageView
const.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:[previous]|", metrics: nil, views: ["previous": newImageView]))
const.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|[newImageView(240)]|", metrics: nil, views: views))
}
NSLayoutConstraint.activate(const)
}
}
Even though I have tried a lot of combinations I have the same error:
2018-04-29 21:24:34.347466-0500 soloScrollEjercicio[12002:1665919] [LayoutConstraints] Unable to simultaneously satisfy constraints.
each loop round, you add a right side pin constraint to scroll view for every new
added imageView, but there is 20 points between each other.
|-previous-20-newOne-| ** second round loop **
-20-newOne'-| ** third round loop **
this breaks imageView width(240) constraint
one way deal with it:
only add right side pin constraint to the last imageView.
scroll view constraint in your main storyboard also has a break.
+-------------------+
| | |
|-scroll view (240)-|
the bottom one with vertical spacing to super view should not be there. it would
break the scroll view height(240), so delete it will be fine.
maybe you should try:
set it's constraint priority to 999, or some other value not equal to
1000
uncheck installed box
delete it
and now, your scroll view should be OK.
p.s. I found your reference book is based on iOS 6? in the year 2018, starts from iOS 10 or iOS 11 may be a better choice.
happy hacking :)
I'm trying to create reusable views in a xib/nib file in swift iOS 10.
When I create a stack view and have 2 objects in it and make sure my stackview is constrained to the container view of the nib/xib (top to top, bottom to bottom, leading to leading and finally trailing to trailing), I get an error saying I'm missing my Y position for the first and the second object. Creating just one of them usually fixes it. Although this is where things to go sideways. In all my previous research, I shouldn't need to do this if I have my distribution of my stackview to Fill. Although this seems to quiet Xcode, it creates a over constraint issue when I try to run my program.
Here is what I used to load my nib in a view from my main storyboard :
public protocol NibOwnerLoadable: class {
static var nib: UINib { get }
}
//MARK:- Generic Implementation
public extension NibOwnerLoadable {
// Use the xib file with the same name as your UIView subclass located in the bundle of that class
static var nib: UINib {
return UINib(nibName: String(describing: self), bundle: Bundle(for: self))
}
}
//MARK:- Support for instation from the XIB file
public extension NibOwnerLoadable where Self: UIView {
// Function to load content and constraints automatically
func loadNibContent() {
let layoutAttributes: [NSLayoutAttribute] = [.top, .leading, .bottom, .trailing]
for view in Self.nib.instantiate(withOwner: self, options: nil) {
if let view = view as? UIView {
view.translatesAutoresizingMaskIntoConstraints = false
view.frame = bounds
self.addSubview(view)
layoutAttributes.forEach{ attribute in self.addConstraint(NSLayoutConstraint(item: view, attribute: attribute, relatedBy: .equal, toItem: self, attribute: attribute, multiplier: 1, constant: 0.0))
}
}
}
}
}
I know my issue is an extra constraint being added by Auto Layout but why and where. This other question is very close to what I'm trying to do but for some reason my AutoLayout knowledge is a jumbled in my head. Adding a subclassed UIView to a Nib with its auto layout constraints
A little help would be appreciate. Please explain the why, not just how to do it. I'm a guy who likes to understand the back story. It makes it logical and easier to retain.
Here are pictures of my filterView as a nib/xib that is loaded inside searchAndFilterView as FilterView (UIView) that is loaded inside one my view controller inside my storyboard. Think reusable Tool Views.
Woohou!
Here is how I figured this out.
I realized that my height constraints on my views using XIB/NIB are necessary to setup the view when its not hidden. But when I set the .isHidden to true value this would conflict with my height constraint.
I needed to let Autolayout do its auto layout when the view changed from hidden to not hidden and vice-versa. By setting a constraint with a Priority default value of 1000, I was telling to Autolayout to absolutely make sure this constraint is always active.
I realized that by setting the priority to 999, I told Autolayout it could override my constraint when its really needed to do it. Why 999, I assume that when a view isHidden, it really has no size and that is very high priority for Autolayout.
I wish I knew this or knew how to find out default autolayout priorities before.
If anyone knows more about this I would appreciate more info or a link!!!
I am attempting to show a loading view on top of a UITableViewController when a user taps on a cell or a button in the cell. For some reason, the view does not show up, nor does it show any constraint failures. Can someone spot error in my code. I need this view to show up covering the tableview on both orientations, when shown. I thought this to be a view render issue, tried the same code in viewWillAppear. Still does not work. Hence I eliminated layout rendering issues. Same code works perfectly fine on UIViewController derived classes. Seems to have issues on UITableViewController classes alone!!!
private func initActivityView() {
let overlayView = UIView(frame: CGRectZero)
overlayView.backgroundColor = UIColor.lightGrayColor()
overlayView.translatesAutoresizingMaskIntoConstraints = false
overlayView.alpha = 0.5
self.view.addSubview(overlayView)
// add constraints
let viewDictionary = ["overlayView":overlayView]
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[overlayView]-0-|",
options: .AlignAllBaseline, metrics: nil, views: viewDictionary))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[overlayView]-0-|",
options: .AlignAllBaseline, metrics: nil, views: viewDictionary))
}
You should let the constraints engine know that it needs update. See the triggering Auto layout section of the UIView Class Reference. Also I would consider making your overlay view an instance variable or find some other way to keep a reference of it so that when the time comes you can remove it.
Do not call updateConstraints() but rather setNeedsUpdateContraints()
From the UIView Class Reference.
Discussion
When a property of your custom view changes in a way that
would impact constraints, you can call this method to indicate that
the constraints need to be updated at some point in the future. The
system will then call updateConstraints as part of its normal layout
pass. Updating constraints all at once just before they are needed
ensures that you don’t needlessly recalculate constraints when
multiple changes are made to your view in between layout passes.
If your having trouble yo can always set a breakpoint on the UIViewController's
viewWillLayoutSubViews() and viewDidLayoutSubViews() and inspect your subview frames.
It is also pretty helpful to keep update with Apple's documentation for Auto Layout as Auto Layout is constantly changing and improving.
Apparently doing it the autolayout way will not work, due to the inherent nature of UITableViewController. However a better solution for this problem will be something like this
let overlayView1 = UIView(frame: self.tableView.frame)
overlayView1.autoresizingMask = self.tableView.autoresizingMask
overlayView1.backgroundColor = UIColor.lightGrayColor()
overlayView1.alpha = 0.5
self.tableView.addSubview(overlayView1)
Edited following comments below - using constraints to control the layout.
I am trying to add/remove views controllers, which are set up within the storyboard, via a button. The button and any previously added views should move down the screen as per the following visual format strings:
Optional("V:|-50-[viewXIB0(100)]-30-[buttonKey]")
Optional("V:|-50-[viewXIB1(100)]-30-[viewXIB0(100)]-30-[buttonKey]")
Optional("V:|-50-[viewXIB2(100)]-30-[viewXIB1(100)]-30-[viewXIB0(100)]-30-[buttonKey]")
(This is the println output of the visual format string fed into NSLayoutConstraint.constraintsWithVisualFormat.)
Thanks already to the comments below, the view does appear each time the button is pressed, and moves down the page correctly. However, the spacing between the superview and top view is zero - without margin. A similar issue also when additional views are added - each one gets tucked underneath the spacing. The bottom spacing is greater than the desired 30. And, after a few inserts, even weirder things start to happen towards the top of the screen.
There are no constraint errors on build or running. When my computer is running super slow, I do see it appear briefly in the correct place, and then snap to the top of the screen. I think I can hear it laughing at me too, but that might be a joke. Autolayout is enabled in both View Controllers.
The full method I'm using to add the view from a touch up inside button is:
#IBAction func addVCBenj(sender: AnyObject)
{
// remove original constraint from button
if (topConstraintBenjV != nil)
{
view.removeConstraint(topConstraintBenjV)
topConstraintBenjV = nil
println("addVCBen constraints removed")
}
// remove previous constraints as views are added
if (constraintV != nil)
{
view.removeConstraints(constraintV!)
println("removed")
}
// instantiate add add the view within the storyboard VC
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myVC = storyboard.instantiateViewControllerWithIdentifier("VCtoInsert") as UIViewController
view.addSubview(myVC.view)
// create and add a unique key for each view in the dictionary of views
viewsDictionaryKey = "\(viewsDictionaryString)\(viewsDictionaryCounter)"
viewsDictionaryCounter += 1
viewsDictionary[viewsDictionaryKey!] = myVC.view
// create the visual format string for layout
suffixVisualFormatString_V = "[" + viewsDictionaryKey! + "(100)]-30-" + suffixVisualFormatString_V
visualFormatString_V = prefixVisualFormatString_V + suffixVisualFormatString_V
println(visualFormatString_V)
// create and add horizontal and vertical constraints
let constraintH = NSLayoutConstraint.constraintsWithVisualFormat("H:[\(viewsDictionaryKey!)(>=300)]", options: NSLayoutFormatOptions(0), metrics: metricsDictionary, views: viewsDictionary)
myVC.view.setTranslatesAutoresizingMaskIntoConstraints(false)
constraintV = NSLayoutConstraint.constraintsWithVisualFormat(visualFormatString_V!, options: NSLayoutFormatOptions(0), metrics: metricsDictionary, views: viewsDictionary)
self.view.addConstraints(constraintV!)
self.view.addConstraints(constraintH)
}
I've seen the WWDC videos, and searched for a similar problem, but in truth, I'm now so confused, I don't know what to even search for anymore.
As per a comment by BooRanger, by adding an IBOutlet to the top and bottom contraints, it was possible to manipulate the views each time a button was pressed. Ultimately, however, collection views are an easier way to add and remove views.
I'd like to create an "adding a new credit card viewController".
We don't want to aggravate users with all of the required fields presented at once.
This action contains several steps.
On each step the view-controller reveals a new subview (which contains one or more textfields) and collapses an old one (the current text field after it's text is validated).
I've created the ViewController on the storyboard. and placed all of its subviews one above the other.
I've created all of the constraints on the storyBoard, each subviews' clips to the above subview etc'.
i.e:
NSMutableArray *constraints = [[NSLayoutConstraint
constraintsWithVisualFormat:
#"V:|[titleView]-[subtitleView]-[amountView]-[cardNumView]-[cardsImagesView]-[mmYYCvvView]-[billingInfoView]-[buttomView]|"
options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom
metrics:nil
views:variableBindings] mutableCopy];
Each of these subviews contain a height constraint.
In each step one of the height constraints are set to zero and another one is changed from zero to the required height.
i.e:
self.hgtCrtMMYYCvv.constant = showFields? 50 : 0;
self.hgtCrtBillingInfo.constant = showFields? 140 : 0;
self.mmYYCvvView.hidden = !showFields;
self.billingInfoView.hidden = !showFields;
I got two issues:
Without calling layoutIfNeeded the initial layout was valid but did not change after changing the height constraints.
Calling layoutIfNeeded did not clip the bottom view to the last visible one - placed it at the bottom of the view as if all the subviews appear at once, but since some are hidden a gap was created.
changing the height constraint of the subviews was applied on the screen but still the gap stayed.
Please advise.
Calling "layoutIfNeeded" did not clip the bottom view to the last visible one - placed it at the bottom of the view as if all the subviews appear at once
Look at your constraints. You have pinned the bottom of the bottom view to the bottom of its superview! So its bottom must appear at the bottom of the superview, since that is what you instructed it to do.
Indeed, I am surprised that your constraints work at all. You have basically overdetermined them. If you give every field a height and pin its top and bottom, for every field, then it will be impossible to satisfy your constraints unless you are very lucky. The height of the superview is fixed, so your constraints would have to add up perfectly to that height.
I'm going to suggest a complete alternative approach, which I think you will find easier. Instead of messing with individual constants, plan what the correct (not overdetermined) constraints would be for each possible situation, and store those constraints in properties. Now when you want to hide/reveal a field, you just remove all the constraints and swap in another set.
This will also solve the layoutIfNeeded problem.
It happens that I have an actual example showing how to do this. (It is written in Swift, but I'm sure you can compensate mentally.) In my example code, we have three rectangles; I then remove one rectangle and close the gap between the remaining two. The preparation of two sets of constraints is tedious but elementary:
let c1 = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(20)-[v(100)]", options: nil, metrics: nil, views: ["v":v1]) as [NSLayoutConstraint]
let c2 = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(20)-[v(100)]", options: nil, metrics: nil, views: ["v":v2]) as [NSLayoutConstraint]
let c3 = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(20)-[v(100)]", options: nil, metrics: nil, views: ["v":v3]) as [NSLayoutConstraint]
let c4 = NSLayoutConstraint.constraintsWithVisualFormat("V:|-(100)-[v(20)]", options: nil, metrics: nil, views: ["v":v1]) as [NSLayoutConstraint]
let c5with = NSLayoutConstraint.constraintsWithVisualFormat("V:[v1]-(20)-[v2(20)]-(20)-[v3(20)]", options: nil, metrics: nil, views: ["v1":v1, "v2":v2, "v3":v3]) as [NSLayoutConstraint]
let c5without = NSLayoutConstraint.constraintsWithVisualFormat("V:[v1]-(20)-[v3(20)]", options: nil, metrics: nil, views: ["v1":v1, "v3":v3]) as [NSLayoutConstraint]
self.constraintsWith.extend(c1)
self.constraintsWith.extend(c2)
self.constraintsWith.extend(c3)
self.constraintsWith.extend(c4)
self.constraintsWith.extend(c5with)
self.constraintsWithout.extend(c1)
self.constraintsWithout.extend(c3)
self.constraintsWithout.extend(c4)
self.constraintsWithout.extend(c5without)
NSLayoutConstraint.activateConstraints(self.constraintsWith)
But the payoff comes when it is time to swap the middle view in or out of the interface: it's trivial. Just remove or insert it, and then remove all constraints and now insert the complete new set of constraints appropriate to the situation, which we have already prepared:
#IBAction func doSwap(sender: AnyObject) {
if self.v2.superview != nil {
self.v2.removeFromSuperview()
NSLayoutConstraint.deactivateConstraints(self.constraintsWith)
NSLayoutConstraint.activateConstraints(self.constraintsWithout)
} else {
self.view.addSubview(v2)
NSLayoutConstraint.deactivateConstraints(self.constraintsWithout)
NSLayoutConstraint.activateConstraints(self.constraintsWith)
}
}
The preparation of the multiple sets of constraints is tedious but can be done by rule, i.e. the constraints can be "machine-generated" in a loop (writing this is left as an exercise for you). Swapping constraints in and out is again according to a simple rule, since only one set will be right for the particular set of fields you wish to show/hide. So once this is set up it will be much simpler and more maintainable than what you are doing now.