constraint - ¿Cómo usar Autolayout Visual Format con Swift?
programmatically constraints swift 4 (9)
El primero que nos llegó aquí es que Swift Dictionary aún no está enlazado con NSDictionary. Para que esto funcione, querrá crear explícitamente un NSDictionary para cada parámetro de tipo NSDictionary.
Además, 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))
He intentado usar el lenguaje de formato visual de Autolayout en Swift , usando NSLayoutConstraint.constraintsWithVisualFormat
. Aquí hay un ejemplo de un código que no hace nada útil, pero hasta donde puedo decir debería hacer feliz al verificador 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 ''String!'' ".
Antes de asumir que este es un error digno de Radar, ¿hay algo obvio que me falta aquí? Esto sucede incluso sin la conversión explícita del nombre de la variable, o con otros downcasting gratuitos usando as
. No veo ninguna razón por la cual el compilador esperaría que parte de esto se resolviera en una String!
.
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á usando iOS, sustituya UIView
por NSView
Probablemente deberías echar un vistazo
- Cartography , que es un nuevo enfoque, pero bastante impresionante. Utiliza Autolayout debajo del capó.
- SnapKit , que no he probado, pero también es un marco de diseño de DSL.
FYI: si usa vistas con constraintWithVisualFormat - en lugar de envolver con NSMutableDict
["myLabel": self.myLabel!]
y ser más específico
var constraints = [NSLayoutConstraint]()
NSLayoutConstraint.constraintsWithVisualFormat("H:|-15-[myLabel]-15-|",
options:NSLayoutFormatOptions.allZeros,
metrics: nil,
views: ["myLabel": self.myLabel!]).map {
constraints.append($0 as NSLayoutConstraint)
}
Intente esto: elimine el nombre de la variable inicial ( format:
NSLayoutFormatOptions(0)
, use NSLayoutFormatOptions(0)
y simplemente pase nil
para esos NSDictionaries opcionales:
let foo:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat("", options: NSLayoutFormatOptions(0), metrics: nil, views: nil)
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:
fragmento 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 a la que también se puede hacer referencia como "v"
). Además, el prefijo del formato con "B:"
generará las restricciones "H:"
y "V:"
. La línea de código de ejemplo anterior significa, por lo tanto, "asegúrese de que la view
siempre se ajuste a la 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 de forma mucho más elegante, 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 de 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]-|")
Tienes que acceder a struct NSLayoutFormatOptions
.
Los siguientes trabajos para mí.
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("",
options:NSLayoutFormatOptions.AlignAllBaseline,
metrics: nil, views: nil))
esto funciona para mí sin ningún error:
let bar:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(
nil, options: NSLayoutFormatOptions(0), metrics: nil, views: nil)
actualizar
la línea anterior puede no compilarse ya que los parámetros 1º y 4º ya no pueden ser opcionales .
sintácticamente deben establecerse, como por ejemplo esto:
let bar:[AnyObject] = NSLayoutConstraint.constraintsWithVisualFormat("", options: NSLayoutFormatOptions(0), metrics: nil, views: ["": self.view])
actualizar
(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í mismos no garantizarán que la restricción sea correcta o incluso válida!
NSLayoutFormatOptions
implementa el protocolo OptionSetType
, que hereda de SetAlgebraType
que hereda de ArrayLiteralConvertible
, por lo que puede inicializar NSLayoutFormatOptions
siguiente manera: []
o esto: [.DirectionLeftToRight, .AlignAllTop]
Entonces, puedes crear las restricciones de diseño como esta:
NSLayoutConstraint.constraintsWithVisualFormat("", options: [], metrics: nil, views: [:])
// 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)