¿Utilizando el Formato Visual de Diseño Automático con Swift?


He estado tratando de usar el Lenguaje de Formato Visual Autolayout en Swift, usando NSLayoutConstraint.constraintsWithVisualFormat. Aquí hay un ejemplo de algún código que no hace nada útil, pero por lo que puedo decir debería hacer feliz al comprobador de tipos:

let foo:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(
  format: "", options: 0, metrics: {}, views: {})

Sin embargo, esto desencadena el error del compilador:

" No se puede convertir el tipo de expresión '[AnyObject]!'para escribir' Cadena!'".

Antes de asumir que este es un error digno de Radar, ¿hay algo obvio que me estoy perdiendo aquí? Esto sucede incluso sin el casting explícito del nombre de la variable, o con otro downcasting gratuito usando as. No puedo ver ninguna razón por la que el compilador esperaría que cualquier parte de esto se resuelva a String!.

Author: sudo make install, 2014-06-12

9 answers

Esto funciona para mí sin error:

let bar:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(
  nil, options: NSLayoutFormatOptions(0), metrics: nil, views: nil)

Actualización

La línea anterior no puede ser compilada ya que los parámetros 1st y 4th ya no pueden ser opcionales.

sintácticamente esos tienen que ser establecidos, como por ejemplo esto:

let bar:[AnyObject] = NSLayoutConstraint.constraintsWithVisualFormat("", options: NSLayoutFormatOptions(0), metrics: nil, views: ["": self.view])

Actualización

(para Xcode 7, Swift 2.0)

La sintaxis válida ahora también solicita el nombre de los parámetros, como:

NSLayoutFormatOptions(rawValue: 0)

NOTA: esta línea de código muestra solo la sintaxis correcta, los parámetros en sí no garantizan que la restricción sea correcta o incluso válida!

 67
Author: holex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-28 07:55:34

El primer gotcha aquí es que Swift Dictionary aún no está puenteado con NSDictionary. Para que esto funcione, querrá crear explícitamente un NSDictionary para cada parámetro de tipo NSDictionary.

También, como señala Spencer Hall, {} no es un diccionario literal en Swift. El diccionario vacío está escrito:

[:]

A partir de XCode 6 Beta 2, esta solución le permite crear restricciones con el formato visual:

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))
 13
Author: John M. P. Knox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-25 20:37:56

Intente esto: elimine el nombre inicial de la variable (format:), use NSLayoutFormatOptions(0), y simplemente pase nil para esos NSDictionaries opcionales:

let foo:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat("", options: NSLayoutFormatOptions(0), metrics: nil, views: nil)
 5
Author: Nate Cook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-20 12:05:23

FYI: si utiliza vistas con constraintWithVisualFormat-en lugar de envolver con NSMutableDict

["myLabel": self.myLabel!]

Y para ser más específicos

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
Author: StrangeDays,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-05 13:14:51

Esto funciona con 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)
    }

}

Entonces puedes usar llamadas como:

var views : [String: NSView] = ["box": self.box]
self.view.addConstraints("V:[box(100)]", views: views)

Esto funciona para agregar restricciones. Si está utilizando iOS, sustituya UIView por NSView


Probablemente deberías echarle un vistazo

  • Cartografía , que es un nuevo enfoque, pero bastante impresionante. Utiliza el diseño automático debajo del capó.
  • SnapKit , que no he probado, pero también es un marco de diseño automático DSL
 4
Author: Dan Rosenstark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-28 18:34:57

NSLayoutFormatOptions implementa el protocolo OptionSetType, que hereda de SetAlgebraType que hereda de ArrayLiteralConvertible, por lo que puede inicializar NSLayoutFormatOptions así: [] o esto: [.DirectionLeftToRight, .AlignAllTop]

Por lo tanto, puede crear las restricciones de diseño como esta:

NSLayoutConstraint.constraintsWithVisualFormat("", options: [], metrics: nil, views: [:])
 2
Author: juanjo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-19 18:42:06

Me molesta un poco que esté llamando NSLayoutConstraint (singular) para generar constraintsWithVisualFormat... (plural), aunque estoy seguro de que soy solo yo. En cualquier caso, tengo estas dos funciones de nivel superior:

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

Que se puede usar así:

superView.addConstraints(NSLayoutConstraints("B:|[v]|", view))

En otras palabras, las vistas se denominan automáticamente "v1" a "v\(views.count)" (excepto la primera vista que también se puede denominar "v"). Además, el prefijo del formato con "B:" generará tanto "H:" como "V:" limitación. Por lo tanto, la línea de ejemplo de código anterior significa, "asegúrese de que el view siempre se ajuste al superView".

Y con las siguientes extensiones:

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

Podemos hacer algunas tareas comunes mucho más elegantemente, como agregar un botón en un desplazamiento estándar desde la esquina inferior derecha:

superView.addSubview(button, constraints: "B:[v]-|")

Por ejemplo, en un patio de juegos iOS:

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]-|")
 1
Author: milos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-30 13:03:25

Tienes que acceder a la estructura NSLayoutFormatOptions.

Seguir obras para mí.

self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("",
options:NSLayoutFormatOptions.AlignAllBaseline,
metrics: nil, views: nil))
 0
Author: Borja Igartua,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-02 18:37:47
// 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)
 0
Author: Michael Peterson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-12 20:50:49