So I have the following code that creates a TableView:
contentView.addSubview(carsTableView)
carsTableView.translatesAutoresizingMaskIntoConstraints = false
carsTableView.backgroundColor = r.darkGray
carsTableView.dataSource = self
carsTableView.isScrollEnabled = false
carsTableView.allowsSelection = true
carsTableView.delegate = self
carsTableView.separatorStyle = UITableViewCell.SeparatorStyle.singleLine
carsTableView.separatorColor = r.lightGray
carsTableView.separatorInset = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
carsTableView.register(CarCell.self, forCellReuseIdentifier: "CarCell")
Note that I use a single line separator. The following code are constraints for my Cell "CarCell"
NSLayoutConstraint.activate([
carView.heightAnchor.constraint(equalToConstant: 120),
carView.topAnchor.constraint(equalTo: contentView.topAnchor),
carView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
carView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
carView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
carImageView.leadingAnchor.constraint(equalTo: carView.leadingAnchor, constant: 15),
carImageView.centerYAnchor.constraint(equalTo: carView.centerYAnchor),
carImageView.heightAnchor.constraint(equalToConstant: carImageWidthAndHeight),
carImageView.widthAnchor.constraint(equalToConstant: carImageWidthAndHeight),
carImage.widthAnchor.constraint(equalTo: carImageView.widthAnchor),
carImage.heightAnchor.constraint(equalTo: carImageView.heightAnchor),
carImage.centerYAnchor.constraint(equalTo: carImageView.centerYAnchor),
carImage.centerXAnchor.constraint(equalTo: carImageView.centerXAnchor),
stringStack.leadingAnchor.constraint(equalTo: carImageView.trailingAnchor, constant: 20),
stringStack.centerYAnchor.constraint(equalTo: carView.centerYAnchor),
editButton.trailingAnchor.constraint(equalTo: carView.trailingAnchor, constant: -15),
editButton.centerYAnchor.constraint(equalTo: carView.centerYAnchor),
])
The result is a perfectly fine TableView which looks exactly like it should. However, the separator line breaks the height constraint from 120 to 120.33. How can I avoid that? I am honestly amazed by the fact that no one seems to have a similar problem. How do I get around it? The difference is obviously not noticeable, but the constraint issues bother me in the console. I'd like to have it run without throwing errors...
This is a common issue related to the way UIKit lays out table views and cells.
You're seeing 120 to 120.3333, but if you run the app on a device with #2x screen scale, the message will be 120.5.
When calculating "single pixel" lines, #2x devices can only use whole or 1/2 points, and #3x devices can only use whole, 1/3 or 2/3 points.
It is safe to ignore, but if you want to get rid of the error / warning messages, give your cells subview(s) a bottom constraint with less-than-required priority.
For example (I'm assuming from the code you posted that carImageView, carImage, stringStack, editButton are all subviews of carView):
let c = carView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
c.priority = .required - 1
NSLayoutConstraint.activate([
// don't use this one
//carView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
// activate the less-than-required bottom constraint
c,
// the rest of your constraints....
carView.heightAnchor.constraint(equalToConstant: 120),
carView.topAnchor.constraint(equalTo: contentView.topAnchor),
carView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
carView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
// and so on....
Related
What I want to achieve in AppKit (not in SwiftUI): [GIF] (example in SwiftUI)
The NSWindow max width should not be limited to the NSTextField max width.
The NSWindow min width should be limited to the NSTextField min width.
NSTextField need to have these parameters: [min width: 200, max width: 400]
I had several attempts to implement this behavior in AppKit. I've been trying to do this for a few days now, but it doesn't work. [PIC]
[GIF]
I tried to set the low priority on Leading / Trailing constraint.
This partially fixed the situation. I was able to change the size of the window normally, but the window size was not limited to the minimum size of NSTextField.
[GIF]
The important thing to notice here is that you only want the low priority constraints to be one way. That is, you don't want something like this:
// throughout the answer, I use "tf" for the text field, and "view" for its superview
let weakLeadingConstraint = tf.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let weakTrailingConstraint = tf.trailingAnchor.constraint(equalTo: view.trailingAnchor)
weakLeadingConstraint.priority = .dragThatCannotResizeWindow
weakTrailingConstraint.priority = .dragThatCannotResizeWindow
Because these constraints would break when the window is resized, allowing the window to be resizable to any width where the leading and trailing anchors are "not equal" to those of the text field.
Instead, the low priority constraints should be >= or <= constraints. Think of the 2 equality constraints as the following 4 inequality constraints:
tf.leading <= view.leading
tf.trailing >= view.trailing
tf.leading >= view.leading
tf.trailing <= view.trailing
It is the first 2 that you want to break, leaving the last 2 (which says that the text field should always be within the window) in tact, when you resize the window.
The other constraints are quite straightforward, so I'll just present the whole code here:
tf.translatesAutoresizingMaskIntoConstraints = false
let weakLeadingConstraint = tf.leadingAnchor.constraint(lessThanOrEqualTo: view.leadingAnchor)
let weakTrailingConstraint = tf.trailingAnchor.constraint(greaterThanOrEqualTo: view.trailingAnchor)
weakLeadingConstraint.priority = .dragThatCannotResizeWindow
weakTrailingConstraint.priority = .dragThatCannotResizeWindow
NSLayoutConstraint.activate([
tf.centerXAnchor.constraint(equalTo: view.centerXAnchor),
tf.centerYAnchor.constraint(equalTo: view.centerYAnchor),
tf.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor),
tf.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor),
weakLeadingConstraint,
weakTrailingConstraint,
tf.widthAnchor.constraint(greaterThanOrEqualToConstant: 200),
tf.widthAnchor.constraint(lessThanOrEqualToConstant: 400),
])
I have two views, previewSection, & settingsEditSection. I'm trying to layout these views to achieve the following:
previewSection's top is anchored to the top of the parent view
previewSection has a height of 400 if possible, but it will be smaller if necessary for settingsEditSection to achieve it's minimum height
settingsEditSection's top is anchored to bottom of previewSection
settingsEditSection's bottom is anchored to bottom of parent view
settingsEditSection has a height of at least 150
My problem is, when I give previewSection a height of lessThanOrEqualToConstant: 400 it has an actual height of 0.
Is there some way for me to say: "height of lessThanOrEqualToConstant, x, and as long as theres room, have the height of x"?
I want the height of 150 for settingsEditSection to be the first priority, and then before making it any larger, make previewSection 400 if possible, and if there's still room after that, then settingsEditSection can get larger than 150 to fill in the space.
Here's the code that I wrote, that makes the most sense to me:
let previewSection = PreviewSection()
view.addSubview(previewSection)
let settingsEditSection = SettingsEditSection()
view.addSubview(settingsEditSection)
// Preview section
previewSection.translatesAutoresizingMaskIntoConstraints = false
previewSection.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
previewSection.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
previewSection.topAnchor.constraint(equalTo: view.topAnchor, constant: topMargin).isActive = true
previewSection.heightAnchor.constraint(lessThanOrEqualToConstant: 400).isActive = true
previewSection.bottomAnchor.constraint(equalTo: settingsEditSection.topAnchor).isActive = true
// Settings & edit section
settingsEditSection.translatesAutoresizingMaskIntoConstraints = false
settingsEditSection.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
settingsEditSection.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
settingsEditSection.bottomAnchor.constraint(equalTo: upgradeButton.topAnchor).isActive = true
settingsEditSection.heightAnchor.constraint(greaterThanOrEqualToConstant: 150).isActive = true
settingsEditSection.topAnchor.constraint(equalTo: previewSection.bottomAnchor).isActive = true
Right now, previewSection has a height of 0, and then settingsEditSection's height just spans the entire parent view.
The context of this problem for me is I'm building out this profile page, and to layout the profile & buttons to look best on both an iPhone X and an iPhone 5S, this is the best way to do it.
You can use constraint priority here. After
previewSection.heightAnchor.constraint(lessThanOrEqualToConstant: 400).isActive = true
You can add one more constraint that gives a default height, but at a lower priority. Like this,
let previewSectionHeight = previewSection.heightAnchor.constraint(equalToConstant: 400)
previewSectionHeight.priority = .defaultHigh
previewSectionHeight.isActive = true
This will make previewSection to have a height of 400 until unless settingsEditSection pushes it up to make it small. For all of this to work you also need to give a constant height to the container view of previewSection and settingsEditSection.
I have a view controller consisting mainly of 2 views.
One that has leading, trailing and bottom anchor aligned with superview and proportional height to superview(0.25), and a scroll view that aligns leading top and trailing to superview/safe area and bottom the other view.
I have a view defined in a xib-file which I create multiple times using Bundle.main.loadNibNamed("VariantResultSlide", owner: self, options: nil)?.first and add them to an array slides. I want to add them to my ScrollView:
for i in 0 ..< slides.count {
scrollView.addSubview(slides[i])
NSLayoutConstraint.activate([
slides[i].leadingAnchor.constraint(equalTo: (i==0) ? scrollView.leadingAnchor : slides[i-1].trailingAnchor),
slides[i].topAnchor.constraint(equalTo: scrollView.topAnchor),
slides[i].bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
slides[i].widthAnchor.constraint(equalTo: scrollView.widthAnchor),
slides[i].heightAnchor.constraint(equalTo: scrollView.heightAnchor)
])
if(i==slides.count-1) {
NSLayoutConstraint.activate([slides[i].trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)])
}
self.updateResultSlides(index: i, vehicle: orderedVehiclesList[i])
}
But then Xcode gives me errors like:
2019-04-11 13:57:07.219263+0200 FleetView[545:190663] [LayoutConstraints] 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.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x282cecbe0 h=-&- v=-&- FleetView.VariantResultSlide:0x107f214a0.height == UIScrollView:0x102919200.height + 99 (active)>",
"<NSLayoutConstraint:0x282c92300 FleetView.VariantResultSlide:0x107f214a0.height == UIScrollView:0x102919200.height (active)>"
)
and the slides are way too big.
But I can't find any place where I set another constraint for them. Why is this happening?
Your view implements top, bottom and height constraints so obviously its superview will need to resize - but it can't due to constraints created automatically from autoresizing mask. Disable it in you superview with
yourViewsSuperview.translatesAutoresizingMaskIntoConstraints = false
I'm trying create a label which will be able to display different movie titles of differing sizes. When I use the code below
lbl.numberOfLines = 3
lbl.adjustsFontSizeToFitWidth = true
lbl.minimumScaleFactor = 0.05
I get the issue of longer words being split into multiple lines
See the issue below:
1st screen shot
When I use this code:
lbl.lineBreakMode = .byWordWrapping
lbl.numberOfLines = 3
lbl.adjustsFontSizeToFitWidth = true
lbl.minimumScaleFactor = 0.05
I get the issue of words being missed out(the label is supposed to display the text "What's Love Got to Do With It")
See the issue below:
2nd screen shot
The label has a simple set of constraints
lbl.topAnchor.constraint(equalTo: countdownLbl.bottomAnchor, constant: 20).isActive = true
lbl.bottomAnchor.constraint(equalTo: btnStackView.topAnchor, constant: 20).isActive = true
lbl.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
lbl.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 20).isActive = true
Any help would be greatly appreciated.
Use option-return when typing in the little box in Interface Builder to insert a line feed (\n). In Interface Builder's Label attributes, set # Lines = 0.
Select the label and then change Lines property to 0.
like in the above image, and then use \n in your string for line break.
You select the label and then in the attributes inspector in the LINES property you leave it at 0 and the AUTOSHRINK property you leave it set to (minimum font scale)enter image description here
I am struggling with adding one button from code to the right corner of the view.
Could someone explain me how can i do this without setting the left constraint ? This would be the left upper V:|-10-[v0], H:|-10-[v0] what would be inversion of it ? I was trying with this : V:[v0]-10-|, H:[v0]-0-| but it does not work like i thought
Thanks in advance!
Based on the comments, I'd like to provide two answers on how to place a UIButton on the top right.
VFL:
Your vertical pin is for the bottom right, not the top. Instead of V:[v0]-10-|, where the "pipe" character (that designates the bounds of the screen) is at the end, place it at the beginning - |-10-[v0].
Provided you've given the button some sort of height/width - which I think you have as the code for "top left" works - this should fix things.
Anchors
Introduced in iOS9, layout anchors (along with layout guides) are a third way to code auto layout. Like NSLayoutConstraints, this is less "visual" than VFL. But unlike NSLayoutConstraints it's less verbose - thus more "Swiftier" IMHO.
To pin a UIButton to the top left, you still need to give auto layout four things - height, width, and X/Y positions. In the following I'm assuming the superview of v0 is called view, like the root view of a UIViewController.
// declare your button
let v0 = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
// remember, you ALWAYS need to turn of the auto resize mask!
v0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v0)
// create a square button
v0.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
v0.heightAnchor.constraint(equalToConstant: 100.0).isActive = true
// pin the button 10 points from the left side of the view
v0.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
// here's how you would pin the button 10 points from the right side of the view
// note the use of a negative here!
// v0.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
// pin the button 10 points from the top of the view
v0.topAnchor.constraint(equalTo: view.topAnchor, constant: 10).isActive = true
}
Like NSLayoutConstraints, layout anchors have constants and multipliers, which you may change if you declare a name for the constraint.
You may combine NSLayoutConstraints with NSLayoutGuides for some nice "adaptive layouts". These guides act like "spacer/invisible" UIViews except for the overhead - they aren't views. You can get a set of Apple "standard" margins (UIView.layoutMarginsGuide), or you can create a set of equally size dynamic guides to space things out equally.
Here's two blogs about layout anchors and layout guides. The examples are written in Swift 2 but there's no syntax changes for Swift 3.