Using Autolayout Visual Format with Swift?

谁都会走 提交于 2019-12-02 18:51:57

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!

John M. P. Knox

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))

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)

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)
    }

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

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: [:])

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]-|")
Borja Igartua

You have to access to the struct NSLayoutFormatOptions.

Following works for me.

self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("",
options:NSLayoutFormatOptions.AlignAllBaseline,
metrics: nil, views: nil))
// 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)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!