NSDictionaryOfVariableBindings быстрый эквивалент?

Документация Apple показывает неурегулированное пустое пространство в разделе "Создание словаря" ссылки UIKit здесь.

Кто-нибудь нашел замену макроса NSDictionaryOfVariableBindings, или мы должны просто написать наш собственный?

EDIT. Согласно this, возможно, правильный подход - написать глобальную функцию для обработки этого? Похоже, сложные макросы полностью исключены.

Ответ 1

Согласно исходному коду Apple:

NSDictionaryOfVariableBindings (v1, v2, v3) эквивалентен [NSDictionary dictionaryWithObjectsAndKeys: v1, @ "v1", v2, @ "v2", v3, @ "v3", nil];

Итак, в Swift вы можете сделать то же самое:

let bindings = ["v1": v1, "v2": v2, "v3": v3]

Ответ 2

NSDictionaryOfVariableBindings - это, как вы говорите, макрос. В Swift нет макросов. Так много для этого.

Тем не менее, вы можете легко написать функцию Swift, чтобы назначать имена строк вашим представлениям в словаре, а затем передавать этот словарь в constraintsWithVisualFormat. Разница в том, что, в отличие от Objective-C, Swift не может видеть ваши имена для этих просмотров; вам придется разрешить ему создавать новые имена.

[Чтобы быть ясным, это не значит, что ваш код Objective-C мог видеть ваши имена переменных; это то, что во время макрооценки препроцессор работал на вашем исходном коде как текст и переписывал его - и поэтому он мог просто использовать текст ваших имен переменных как внутри кавычек (для создания строк), так и снаружи (для создания значений) до образуют словарь. Но с Swift нет препроцессора.]

Итак, вот что я делаю:

func dictionaryOfNames(arr:UIView...) -> Dictionary<String,UIView> {
    var d = Dictionary<String,UIView>()
    for (ix,v) in arr.enumerate(){
        d["v\(ix+1)"] = v
    }
    return d
}

И вы называете это и используете его следующим образом:

    let d = dictionaryOfNames(myView, myOtherView, myFantasicView)
    myView.addConstraints(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "H:|[v2]|", options: nil, metrics: nil, views: d)
    )

Уловка заключается в том, что вам решать, что имя для myOtherView в вашей строке визуального формата будет v2 (поскольку оно было вторым в списке, переданном в dictionaryOfNames()). Но я могу жить с этим просто, чтобы избежать утомительной печати словаря вручную каждый раз.

Конечно, вы могли бы в равной степени написать более или менее эту же функцию в Objective-C. Это просто, что вы не беспокоились, потому что макрос уже существовал!

Ответ 3

Эта функциональность основана на макрорасширении, которое в настоящее время не поддерживается в Swift.

Я не думаю, что есть какой-либо способ сделать что-то подобное в Swift на данный момент. Я считаю, что вы не можете написать свою собственную замену.

Я боюсь, вам придется вручную развернуть определение словаря, даже если это означает повторение каждого имени дважды.

Ответ 4

Среда выполнения ObjC на помощь!

i создал альтернативное решение, но оно работает только в том случае, если каждое из представлений является переменными экземпляра одного и того же объекта.

func DictionaryOfInstanceVariables(container:AnyObject, objects: String ...) -> [String:AnyObject] {
    var views = [String:AnyObject]()
    for objectName in objects {
        guard let object = object_getIvar(container, class_getInstanceVariable(container.dynamicType, objectName)) else {
            assertionFailure("\(objectName) is not an ivar of: \(container)");
            continue
        }
        views[objectName] = object
    }
    return views
}

можно использовать следующим образом:

class ViewController: UIViewController {

    var childA: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = UIColor.redColor()
        return view
    }()

    var childB: UIButton = {
        let view = UIButton()
        view.setTitle("asdf", forState: .Normal)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = UIColor.blueColor()
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.addSubview(childA)
        self.view.addSubview(childB)

        let views = DictionaryOfInstanceVariables(self, objects: "childA", "childB")
        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[childA]|", options: [], metrics: nil, views: views))
        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[childB]|", options: [], metrics: nil, views: views))
        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[childA][childB(==childA)]|", options: [], metrics: nil, views: views))

    }

}

к сожалению, вам все равно придется вводить имя переменной в виде строки, но она, по крайней мере, будет утверждать, если есть опечатка. это определенно не будет работать во всех ситуациях, но, тем не менее, полезно

Ответ 5

Итак, я взломал что-то вместе, что, кажется, работает:

func dictionaryOfVariableBindings(container: Any, views:UIView...) -> Dictionary<String, UIView> {
    var d = Dictionary<String, UIView>()
    let mirror = Mirror(reflecting: container)
    let _ = mirror.children.compactMap {
        guard let name = $0.label, let view = $0.value as? UIView else { return }
        guard views.contains(view) else { return }
        d[name] = view
    }
    return d
}

Использование:

        let views = dictionaryOfVariableBindings(container: self, views: imageView)

Ответ 6

Как только вы сохранили все свои представления в качестве свойств, вы также можете использовать отражение так:

extension ViewController {
    func views() -> Dictionary<String, AnyObject> {
        var views = dictionaryOfProperties()
        views.forEach {
            if !($1 is UIView) {
                views[$0] = nil
            }
        }
        return views
    }
}

extension NSObject {

    func dictionaryOfProperties() -> Dictionary<String, AnyObject> {
        var result = Dictionary<String, AnyObject>()
        let mirror = Mirror(reflecting: self)
        for case let(label?, value) in mirror.children {
            result[label] = value as? AnyObject
        }
        return result
    }
}

Ответ 7

Основанное на fooobar.com/info/93203/... cherpak-evgeny, это расширение UIViewController предполагает, что контейнер является self, текущим экземпляром viewController.

extension UIViewController {

    // Alex Zavatone 06/04/2019
    // Using reflection, get the string name of the UIView properties passed in
    // to create a dictionary of ["viewPropertyName": viewPropertyObject…] like
    // Objective-C NSDictionaryForVariableBindings.
    func dictionaryOfBindings(_ arrayOfViews:[UIView?]) -> Dictionary<String, UIView> {
        var bindings = Dictionary<String, UIView>()
        let viewMirror = Mirror(reflecting: self)
        let _ = viewMirror.children.compactMap {
            guard let name = $0.label, let view = $0.value as? UIView else { return }
            guard arrayOfViews.contains(view) else { return }
            bindings[name] = view
        }
        return bindings
    }
}

Используйте это так в вашем viewController:

let viewArray = [mySwitch, myField, mySpinner, aStepper, someView]
let constraintsDictionary = dictionaryOfBindings(viewArray)

Протестировано в Xcode 10.2.1 и Swift 4.2.

Большое спасибо Черпаку Евгению за то, что он написал его.