I have 2 "sister view" located one above the other (talking Y) embedded in the same parent view.
The first one is the size of the screen et the one below is outside of the screen at the bottom.
On a animation, the bottom view raises and shows up on the screen.
This animation should reduce the height of the upper view.
I thought using NSLayoutConstraint for this but I can't get work.
Right now I have following :
let constraintString : String = String(format: "V:[tranlucentView][buttonContainer(%lg)]", buttonHeight * countOfButtons)
let viewsDict:NSDictionary = ["tranlucentView" : self.translucentView, "buttonContainer" : self.buttonContainer]
var constraints : NSArray = NSLayoutConstraint.constraintsWithVisualFormat(constraintString, options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict)
self.view.addConstraints(constraints)
And that's the error I'm getting :
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)
(
"<NSLayoutConstraint:0x198f6530 V:[UIView:0x198ef880]-(0)-[UIView:0x177b11e0]>",
"<NSLayoutConstraint:0x198f6600 V:[UIView:0x177b11e0(0)]>",
"<NSAutoresizingMaskLayoutConstraint:0x19f7f7f0 h=--& v=--& UIView:0x198ef880.midY == + 284>",
"<NSAutoresizingMaskLayoutConstraint:0x1983e4c0 h=--& v=--& V:[UIView:0x198ef880(568)]>",
"<NSAutoresizingMaskLayoutConstraint:0x19f7f260 h=--& v=--& UIView:0x177b11e0.midY == + 472>"
)
Any suggestions for my problem ?
I found the solution myself :
I needed to translatesAutoresizingMaskIntoConstraints to false for my upper-view to allow its resize.
Also I needed to add a constraint for its width or else its width goes to 0.
let constraintString : String = String(format: "V:|[tranlucentView][buttonContainer(%lg)]|", buttonHeight * countOfButtons)
self.translucentView.setTranslatesAutoresizingMaskIntoConstraints(false)
let viewsDict:NSDictionary = ["tranlucentView" : self.translucentView, "buttonContainer" : self.buttonContainer]
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(constraintString, options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[tranlucentView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
Related
I have a complex layout that works perfectly. Now I want to overlay the existing layout with a view that is resizable with, say a 40 pixel margin, relative to the window. When I add a empty custom view to the content view, the constraint system goes haywire and says it cannot satisfy the constraints, and the results are quite random.
I add the view as:
contentView.addSubview(customView)
let dict = ["CustomView" : customView]
let s1 = "|-(40)-[CustomView]-(40)-|"
let s2 = "V:|-(40)-[CustomView]-(40)-|"
let con1 = NSLayoutConstraint.constraints(withVisualFormat: s1, options: [],
metrics: nil, views: dict as [String : Any])
let con2 = NSLayoutConstraint.constraints(withVisualFormat: s2, options: [],
metrics: nil, views: dict as [String : Any])
viewLayout.append(contentsOf: con1)
viewLayout.append(contentsOf: con2)
NSLayoutConstraint.activate(viewLayout)
As far as I can tell, this view should add no additional constraints as to the size or shape of the underlying content view, but should simply be resized to the appropriate size. It would seem that must be some implicit constraint on the view preventing it from simply being sized or I am missing something fundamental about constraints.
I got this to work. I needed to uncheck the "Translates Mask into Constraints" checkbox for the custom view.
I have an NSTableView whose first row is pushed down 10 pt from the top. Headers are turned off, there are no group rows, cell spacing is 0, and the enclosing scroll view's content inset is 0.
UPDATED
Here's a sample project that demonstrates the issue.
Here's a grab of the view hierarchy debugger:
Here's the vertical constraints of the first row's NSTableRowView while paused in the view hierarchy debugger:
I tried implementing the delegate's tableView(_:didAdd:forRow:) and inspecting the constraints, first with constraintsAffectingLayout(for:):
[<NSLayoutConstraint:0x60000390bd40 'NSTableRowView_Encapsulated_Layout_Height' NSTableRowView:0x7fdb12e26eb0.height == 24 priority:500 (active)>]
Then printing all the row view's constraints:
- 0 : <NSLayoutConstraint:0x60000390bcf0 'NSTableRowView_Encapsulated_Layout_Width' NSTableRowView:0x7fdb12e26eb0.width == 329 priority:500 (active)>
- 1 : <NSLayoutConstraint:0x60000390bd40 'NSTableRowView_Encapsulated_Layout_Height' NSTableRowView:0x7fdb12e26eb0.height == 24 priority:500 (active)>
- 2 : <NSAutoresizingMaskLayoutConstraint:0x60000390bbb0 h=--& v=-&- InlineCell.minX == 16 (active, names: InlineCell:0x7fdb12e283a0, '|':NSTableRowView:0x7fdb12e26eb0 )>
- 3 : <NSAutoresizingMaskLayoutConstraint:0x60000390bc00 h=--& v=-&- InlineCell.width == 297 (active, names: InlineCell:0x7fdb12e283a0 )>
- 4 : <NSAutoresizingMaskLayoutConstraint:0x60000390bc50 h=--& v=-&- InlineCell.minY == 0 (active, names: InlineCell:0x7fdb12e283a0, '|':NSTableRowView:0x7fdb12e26eb0 )>
- 5 : <NSAutoresizingMaskLayoutConstraint:0x60000390bca0 h=--& v=-&- V:[InlineCell]-(0)-| (active, names: InlineCell:0x7fdb12e283a0, '|':NSTableRowView:0x7fdb12e26eb0 )>
The cell's minY constraint is set to 0, but the row is using an autoresizing mask. The table uses a simple diffable datasource:
NSTableViewDiffableDataSourceReference(tableView: table) { tableView, column, row, item in
guard let cell = tableView.makeView(withIdentifier: inlineCellIdentifier, owner: self) as? NSTableCellView else {
preconditionFailure("Failed to create results cell")
}
cell.textField?.textColor = self.themeAttributes.color
cell.textField?.font = self.themeAttributes.font
cell.textField?.stringValue = self.displayString(for: item)
return cell
}
The only delegate method implemented is tableView(_:heightOfRow:). The table aligns itself with the lines of a sibling text view so it gets it row height from there:
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
guard let layoutManager = editor?.layoutManager else {
preconditionFailure("Missing layout manager in editor")
}
return height(of: row, in: layoutManager)
}
This seems like it's probably obvious, but I don't see why the table is forcing its rows to be offset like this. I've found plenty of questions on this forum asking how to insert a gap at the top of the table, but not how to remove one. Any advice?
It's not a bug, as stated in some comments, it's a new way how the NSTableView works in Big Sur (actually there's a bug, but elsewhere, see below). Free Pascal site contains nice overview of what's new.
New NSTableView properties
macOS Big Sur introduced new NSTableView properties:
style
effectiveStyle
style documentation:
The default value for this property is NSTableView.Style.automatic in macOS 11. Apps that link to previous macOS versions default to NSTableView.Style.fullWidth.
effectiveStyle documentation:
If the style property value is NSTableView.Style.automatic, then this property contains the resolved style.
.automatic documentation:
The system resolves the table view style in the following manner:
If the table view is in a sidebar split-view controller item, effectiveStyle resolves to NSTableView.Style.sourceList.
If the table’s scroll view has a border, effectiveStyle resolves to NSTableView.Style.fullWidth.
Otherwise effectiveStyle resolves to NSTableView.Style.inset. However, if the table needs extra space to fit its column cells, effectiveStyle resolves to NSTableView.Style.fullWidth.
Your table view has style set to .automatic in the Main.storyboard. Which means that the effective style resolves to .inset -> 10pt around content (based on the rules from the .automatic documentation).
Open the view debugger with a selected row. You can see the .inset style effect:
Use .fullWidth to remove 10pt insets.
You can also test the .automatic style behavior - try to add a border to the scroll view. Resolves to .fullWidth.
Bug & workaround
You probably tried to set the table view style to Full Width in the Interface Builder. It doesn't work. No matter what value you choose, it behaves like .automatic.
Add the following line to your code to workaround this issue:
tableView?.style = .fullWidth
Works as expected now:
Reported as FB8258910.
I have a small test project that I'm trying to import into my production project. The ViewController that is causing the problem consists of a back ground image and a Scroll View (SV).
3 images appear in the test SV, yet only 2 appear in production. There appears to be a gap where the first image should appear.
Please note. The first image is the background image for the VC. I set it, then delete it from the array that feeds the scrollview.
Here are the two ViewControllers. Please note I embedded the VC in a TabBar and NavBar controller in test because that is what I have int production.
What is most puzzling is the code is exactly the same. The image URL's are the same. But the number of UIImageViews added to the scrollView are different. Note the last print statement in the code:
func setupList() {
print(foodPairings.count)
let imageStringURL = foodPairings[0].imageURL
let encodedURL = imageStringURL.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)
guard let imageURL: URL = URL(string: encodedURL!) else {return}
bgImage.af_setImage(withURL: imageURL)
foodPairings.removeFirst()
print(foodPairings.count)
print(foodPairings.indices)
for i in foodPairings.indices {
let imageView = UIImageView()
let imageStringURL = foodPairings[i].imageURL
let encodedURL = imageStringURL.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)
guard let postURL: URL = URL(string: encodedURL!) else {return}
imageView.af_setImage(withURL: postURL, placeholderImage: #imageLiteral(resourceName: "Placeholder"), filter: nil, progress: nil, imageTransition: .noTransition, runImageTransitionIfCached: false, completion: nil)
imageView.tag = i
imageView.contentMode = .scaleAspectFill
imageView.isUserInteractionEnabled = true
imageView.layer.cornerRadius = 20.0
imageView.layer.masksToBounds = true
listView.addSubview(imageView)
//attach tap detector
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTapImageView)))
}
print(listView.subviews.count)
listView.backgroundColor = UIColor.clear
positionListItems()
}
The print statements in Test result in:
4 3
0..<3 4
Production prints the following:
4 3
0..<3 5
Why is listView.subviews.count different in production?
I have resolved this, but it is not clear what the source of the issue was.
In Test, my Scroll View had one subview even prior to me adding the array of UIImageViews. In Production, the Scroll View had two subviews prior to me adding the images in the array.
Just prior to the loop where I add my subviews, I remove all subviews from the ScrollView:
listView.subviews.forEach({ $0.removeFromSuperview() })
for i in foodPairings.indices {
print("SubviewCount: \(listView.subviews.count)")
let imageView = UIImageView()
...
Both my Test and Production macs are running XCode 9 Swift 4. Still remains a mystery why subview count was different.
I have a sample project which demonstrates the problem here
https://github.com/ericgorr/autolayout_with_addsubview.git
I have a view called CalcView which I want to programmatically add as a subview to a view on the main window for the app. When I resize my window, I want CalcView to be resized.
In windowDidLoad in MainWindowController, I add the subview by doing:
let calcViewController = ELIZCalcView()
let calcView = calcViewController.view
calcContentView?.addSubview( calcViewController.view )
I try to add the constraints by doing:
let bindings = [ "calcView": calcView ]
let horizontalContraint:[AnyObject] = NSLayoutConstraint.constraintsWithVisualFormat( "H:|[calcView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: bindings )
let verticalContraint:[AnyObject] = NSLayoutConstraint.constraintsWithVisualFormat( "V:|[calcView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: bindings )
calcContentView?.addConstraints( horizontalContraint )
calcContentView?.addConstraints( verticalContraint )
Now, for someone who knows how to properly interpret that code, it is likely very apparent that it will not work. After I run my app, I cannot resize the window at all. Additionally, I see the following error message in the console:
2015-07-04 16:04:45.019 aocsCalc[5797:3526462] Unable to simultaneously satisfy constraints: (
"<NSLayoutConstraint:0x618000082440 V:|-(0)-[NSView:0x600000120f00] (Names: '|':aocsCalc.ELIZHighlightView:0x608000120500 )>",
"<NSAutoresizingMaskLayoutConstraint:0x618000084100 h=--& v=&-- V:|-(-2)-[NSView:0x600000120f00] (Names: '|':aocsCalc.ELIZHighlightView:0x608000120500 )>" )
Will attempt to recover by breaking constraint <NSLayoutConstraint:0x618000082440 V:|-(0)-[NSView:0x600000120f00] (Names: '|':aocsCalc.ELIZHighlightView:0x608000120500 )>
Set the NSUserDefault NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints to YES to have -[NSWindow visualizeConstraints:] automatically called when this happens. And/or, break on objc_exception_throw to catch this in the debugger.
If I remove the vertical constraint, the error message goes away and I can resize the window vertically.
So, what simple thing do I need to do so CalcView is resized along with the window?
Before you add your calcView as subview try inserting this line of code:
calcView.translatesAutoresizingMaskIntoConstraints = false
This should solve your problem.
By default on UIView/NSView this property is set to YES/true and it creates it's own set of constraints based on autoresizing mask. These auto-made constraints conflict with the ones you've created in code.
It clearly says so in the error description too. On lines 2 and 3 it shows you that there's 2 set of vertical constraints regarding one view - NSView:0x600000120f00, which appears to be your calcView.
<NSLayoutConstraint:0x618000082440 V:|-(0)-[NSView:0x600000120f00]
<NSAutoresizingMaskLayoutConstraint:0x618000084100 h=--& v=&-- V:|-(-2)-[NSView:0x600000120f00]
They are both vertical. First one wants to snap view to the superview's top with no margin. The second one (created automatically) wants to snap it with a small margin, presumably taken from how it is layed out in Interface Builder.
UPDATE
Create a new Cocoa Application project and paste the following code:
override func viewDidLoad() {
super.viewDidLoad()
let view = NSView()
view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view)
//Making it red just to see a little better. Ignore this two lines.
view.wantsLayer = true
view.layer?.backgroundColor = CGColorCreateGenericRGB(1, 0, 0, 1)
//-----------------------------------------------------------------
let views = ["view" : view]
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-[view]-|", options: NSLayoutFormatOptions(0), metrics: nil, views: views))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[view]-|", options: nil, metrics: nil, views: views))
}
Newly created NSView is snapped to the View Controllers main view with standard margins (8 points, it's described as "-" in the visual format string) and is resizing with the main view (see pictures). A little tip - you don't have to specify the "H:" in the visual format, only "V:". It's horizontal by default.
This should give you a good idea of how adding constraints programmatically works. Code might not be optimal, I code in Obj-C and know very little Swift.
I've downloaded your project. Error probably lies somewhere in your complicated view hierarchy and xib manipulation. But that's a whole other story. Also be careful with scroll views, they are a bit tricky when it comes to autolayout, you can find a lot of coverage on that on SO. Happy coding :)
I have the following code to try and create an array of constraints to add to a view:
let views = ["button": button]
let metrics = ["margin": 16]
var constraints: [AnyObject] = []
constraints += NSLayoutConstraint.constraintsWithVisualFormat("|-margin-[button]-margin-|", options: 0, metrics: metrics, views: views)
From what I understand about Swift arrays, I should just be able to '+=' them together to join the two, but I get an error:
"Binary operator '+=' cannot be applied to two [AnyObject] operands"
What's wrong with this code?
It's not because of the operator.
It's because you are passing in an Int where you are actually supposed to pass NSLayoutFormatOptions enum type.
If you pass in one of the NSLayoutFormatOptions enum for the options parameter, the error will go away:
constraints += NSLayoutConstraint.constraintsWithVisualFormat("|-margin-[button]-margin-|", options: .AlignAllLeft, metrics: metrics, views: views)
Or you could also initialize the NSLayoutFormatOptions with the Int value that you want to use, like this:
NSLayoutFormatOptions(rawValue: 0)
0 would have worked in Objective-C, but you need to use the actual enum value in Swift.
The Swift errors are still often misleading in many cases, like this one.
Hope this helps.
I think you could try saving the results of constraintsWithVisualFormat into a temporary constant, then assigning it.
let newConstraints = NSLayoutConstraint.constraintsWithVisualFormat("|-margin-[button]-margin-|", options: 0, metrics: metrics, views: views)
constraints += newConstraints
Here's the answer:
Xcode was listing the wrong thing as the error, which is what led to all this confusion. Using an Int of 0 for my format options apparently does not make the compiler happy, so instead I had to use NSLayoutFormatOptions(0).
With that, the finished working code is like this:
var constraints: [AnyObject] = []
constraints += NSLayoutConstraint.constraintsWithVisualFormat("|-margin-[button]-margin-|", options: NSLayoutFormatOptions(0), metrics: metrics, views: views)
In my case using .AlignAllLeft instead of NSLayoutFormatOptions(rawValue: 0) was not what caused the error. The array of constraints should have been statically-typed.
// BEFORE:
// var viewConstraints: [AnyObject] = [AnyObject]()
// AFTER:
var viewConstraints: [NSLayoutConstraint] = [NSLayoutConstraint]()
let views = NSDictionary(object: customView, forKey: "customView")
viewConstraints += NSLayoutConstraint.constraintsWithVisualFormat("H:|[customView]|",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: views as! [String : AnyObject])
// "Convert to Swift 2.0" Xcode option suggests syntax:
viewConstraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|[customView]|",
options: [],
metrics: nil,
views: views as! [String : AnyObject])
That appears good, because I want "no options" value.