可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've been trying to use the Autolayout Visual Format Language in Swift, using NSLayoutConstraint.constraintsWithVisualFormat
. Here's an example of some code that does nothing useful, but as far as I can tell should make the type checker happy:
let foo:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(
format: "", options: 0, metrics: {}, views: {})
However, this triggers the compiler error:
"Cannot convert the expression's type '[AnyObject]!' to type 'String!'".
Before I assume this is a Radar-worthy bug, is there anything obvious I'm missing here? This happens even without the explicit casting of the variable name, or with other gratuitous downcasting using as
. I can't see any reason why the compiler would be expecting any part of this to resolve to a String!
.
回答1:
this works for me with no error:
let bar:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(
nil, options: NSLayoutFormatOptions(0), metrics: nil, views: nil)
update
the line above may not be compiled since the 1st and 4th parameters cannot be optionals anymore.
syntactically those have to be set, like e.g. this:
let bar:[AnyObject] = NSLayoutConstraint.constraintsWithVisualFormat("", options: NSLayoutFormatOptions(0), metrics: nil, views: ["": self.view])
update
(for Xcode 7, Swift 2.0)
the valid syntax now requests the parameters's name as well, like:
NSLayoutFormatOptions(rawValue: 0)
NOTE: this line of code shows the correct syntax only, the parameters itself won't guarantee the constraint will be correct or even valid!
回答2:
The first gotcha here is that Swift Dictionary is not yet bridged with NSDictionary. To get this to work, you'll want to explicitly create a NSDictionary for each NSDictionary-typed parameters.
Also, as Spencer Hall points out, {} isn't a dictionary literal in Swift. The empty dictionary is written:
[:]
As of XCode 6 Beta 2, this solution allows you to create constraints with the visual format:
var viewBindingsDict: NSMutableDictionary = NSMutableDictionary()
viewBindingsDict.setValue(fooView, forKey: "fooView")
viewBindingsDict.setValue(barView, forKey: "barView")
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[fooView]-[barView]-|", options: nil, metrics: nil, views: viewBindingsDict))
回答3:
Try this - remove the initial variable name (format:
), use NSLayoutFormatOptions(0)
, and just pass nil
for those optional NSDictionaries:
let foo:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat("", options: NSLayoutFormatOptions(0), metrics: nil, views: nil)
回答4:
FYI:
if you use views with constraintWithVisualFormat - instead of wrapping with NSMutableDict
["myLabel": self.myLabel!]
and to be more specific
var constraints = [NSLayoutConstraint]()
NSLayoutConstraint.constraintsWithVisualFormat("H:|-15-[myLabel]-15-|",
options:NSLayoutFormatOptions.allZeros,
metrics: nil,
views: ["myLabel": self.myLabel!]).map {
constraints.append($0 as NSLayoutConstraint)
}
回答5:
This works with Xcode 6.1.1:
extension NSView {
func addConstraints(constraintsVFL: String, views: [String: NSView], metrics: [NSObject: AnyObject]? = nil, options: NSLayoutFormatOptions = NSLayoutFormatOptions.allZeros) {
let mutableDict = (views as NSDictionary).mutableCopy() as NSMutableDictionary
let constraints = NSLayoutConstraint.constraintsWithVisualFormat(constraintsVFL, options: options, metrics: metrics, views: mutableDict)
self.addConstraints(constraints)
}
}
Then you can use calls like:
var views : [String: NSView] = ["box": self.box]
self.view.addConstraints("V:[box(100)]", views: views)
This works to add constraints. If you are using iOS, substitute UIView
for NSView
You should probably check out
- Cartography, which is a new approach, but quite awesome. It uses Autolayout under the hood.
- SnapKit, which I haven't tried but is also a DSL autolayout framework
回答6:
NSLayoutFormatOptions
implements the OptionSetType
protocol, which inherits from SetAlgebraType
which inherits from ArrayLiteralConvertible
, so you can initialise NSLayoutFormatOptions
like this: []
or this: [.DirectionLeftToRight, .AlignAllTop]
So, you can create the layout constraints like this:
NSLayoutConstraint.constraintsWithVisualFormat("", options: [], metrics: nil, views: [:])
回答7:
It slightly annoys me that I'm calling NSLayoutConstraint
(singular) to generate constraintsWithVisualFormat...
(plural), though I'm sure that's just me. In any case, I have these two top level functions:
snippet 1 (Swift 1.2)
#if os(iOS)
public typealias View = UIView
#elseif os(OSX)
public typealias View = NSView
#endif
public func NSLayoutConstraints(visualFormat: String, options: NSLayoutFormatOptions = .allZeros, views: View...) -> [NSLayoutConstraint] {
return NSLayoutConstraints(visualFormat, options: options, views: views)
}
public func NSLayoutConstraints(visualFormat: String, options: NSLayoutFormatOptions = .allZeros, views: [View] = []) -> [NSLayoutConstraint] {
if visualFormat.hasPrefix("B:") {
let h = NSLayoutConstraints("H\(dropFirst(visualFormat))", options: options, views: views)
let v = NSLayoutConstraints("V\(dropFirst(visualFormat))", options: options, views: views)
return h + v
}
var dict: [String:View] = [:]
for (i, v) in enumerate(views) {
dict["v\(i + 1)"] = v
}
let format = visualFormat.stringByReplacingOccurrencesOfString("[v]", withString: "[v1]")
return NSLayoutConstraint.constraintsWithVisualFormat(format, options: options, metrics: nil, views: dict) as! [NSLayoutConstraint]
}
Which can be used like so:
superView.addConstraints(NSLayoutConstraints("B:|[v]|", view))
In other words, views are auto-named "v1"
to "v\(views.count)"
(except the first view which can be also referred to as "v"
). In addition, prefixing the format with "B:"
will generate both the "H:"
and "V:"
constraints. The example line of code above therefore means, "make sure the view
always fits the superView
".
And with the following extensions:
snippet 2
public extension View {
// useMask of nil will not affect the views' translatesAutoresizingMaskIntoConstraints
public func addConstraints(visualFormat: String, options: NSLayoutFormatOptions = .allZeros, useMask: Bool? = false, views: View...) {
if let useMask = useMask {
for view in views {
#if os(iOS)
view.setTranslatesAutoresizingMaskIntoConstraints(useMask)
#elseif os(OSX)
view.translatesAutoresizingMaskIntoConstraints = useMask
#endif
}
}
addConstraints(NSLayoutConstraints(visualFormat, options: options, views: views))
}
public func addSubview(view: View, constraints: String, options: NSLayoutFormatOptions = .allZeros, useMask: Bool? = false) {
addSubview(view)
addConstraints(constraints, options: options, useMask: useMask, views: view)
}
}
We can do some common tasks much more elegantly, like adding a button at a standard offset from the bottom right corner:
superView.addSubview(button, constraints: "B:[v]-|")
For example, in an iOS playground:
import UIKit
import XCPlayground
// paste here `snippet 1` and `snippet 2`
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
XCPShowView("view", view)
view.backgroundColor = .orangeColor()
XCPShowView("view", view)
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
button.setTitle("bottom right", forState: .Normal)
view.addSubview(button, constraints: "B:[v]-|")
回答8:
You have to access to the struct NSLayoutFormatOptions
.
Following works for me.
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("",
options:NSLayoutFormatOptions.AlignAllBaseline,
metrics: nil, views: nil))
回答9:
// topLayoutGuide constraint
var views: NSMutableDictionary = NSMutableDictionary()
views.setValue(taskNameField, forKey: "taskNameField")
views.setValue(self.topLayoutGuide, forKey: "topLayoutGuide")
let verticalConstraint = "V:[topLayoutGuide]-20-[taskNameField]"
let constraints:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(verticalConstraint, options: NSLayoutFormatOptions(0), metrics: nil, views: views)
self.view.addConstraints(constraints)
// bottomLayoutGuide constraint
var views: NSMutableDictionary = NSMutableDictionary()
views.setValue(logoutButton, forKey: "logoutButton")
views.setValue(self.bottomLayoutGuide, forKey: "bottomLayoutGuide")
let verticalConstraint = "V:[logoutButton]-20-[bottomLayoutGuide]"
let constraints:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(verticalConstraint, options: NSLayoutFormatOptions(0), metrics: nil, views: views)
self.view.addConstraints(constraints)