web-dev-qa-db-fra.com

Contrainte de début et de fin dans Swift par programmation (NSLayoutConstraints)

J'ajoute une vue xib dans mon ViewController. Ensuite, je mets ses contraintes pour l'adapter réellement 

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    ...
    ...
    view!.addSubview(gamePreview)
    gamePreview.translatesAutoresizingMaskIntoConstraints = false
    if #available(iOS 9.0, *) {
        // Pin the leading Edge of myView to the margin's leading Edge
        gamePreview.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor).active = true
        //Pin the trailing Edge of myView to the margin's trailing Edge
        gamePreview.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor).active = true

    } else {
        // Fallback on earlier versions
        view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .TrailingMargin, relatedBy: .Equal, toItem: view, attribute: .TrailingMargin, multiplier: 1, constant: 0))

        view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .LeadingMargin, relatedBy: .Equal, toItem: view, attribute: .LeadingMargin, multiplier: 1, constant: 0))
    }
    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0))
    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute,multiplier: 1, constant: 131))
}

Ce que j'essaie de faire: Ajuster réellement mon point de vue en le limitant au sommet, menant à la vue de ViewController, avec une hauteur préfixée. La vue que j'ajoute à la vue principale a sa propre vue en arrière-plan transparent, donc aucun besoin de marge (la vue est censée être la taille de la largeur du périphérique, donc).

J'ai placé 2 couples de lignes qui seraient supposées être égales (dans mes tentatives), avec le if, car les 2 premières lignes de if sont réellement disponibles dans iOS9> uniquement, alors que j'essaie de faire la même chose dans l'instruction else, pour chaque périphérique (à partir de iOS 8).

Voici ce que je reçois:

iOS9 + à gauche, iOS8 + à droite. L'arrière-plan transparent était coloré en rouge pour montrer ce qui se passait (ne vous dérangez pas à une hauteur différente dans les images, elles ont la même hauteur dans l'application, mais regardez plutôt les marges ajoutées à gauche et à droite).

J'ai aussi essayé AutoLayoutDSL-Swift, mais ça n'aide pas, je n'en suis pas un expert, mais chaque tentative n'a fait qu'empirer les choses.

Comment puis-je écrire ces contraintes en utilisant les méthodes classiques de NSLayoutConstraints, et comment pourrais-je tout écrire de manière plus efficace avec un framework comme AutoLayoutDSL ou un fork de celui-ci? (ou une alternative, bien que maintenant je suis surtout concerné par les bibliothèques officielles)

8
BlackBox

Vous devez utiliser les attributs leading et trailing, et non les attributs leadingMargin et trailingMargin:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    ...
    ...
    view!.addSubview(gamePreview)
    gamePreview.translatesAutoresizingMaskIntoConstraints = false

    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0))            
    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 0))

    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0))
    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute,multiplier: 1, constant: 131))
}
19
Paulw11

Deviné grâce à @ Paulw11 ... c'est la solution

view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0))     
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 0))        
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute,multiplier: 1, constant: 131))

De plus, j'ai réussi à le réécrire comme suit, à l'aide de AutoLayoutDSL-Swift pour les personnes intéressées.

view => gamePreview.trailing == view.trailing
     => gamePreview.leading == view.leading
     => gamePreview.height == 131
     => gamePreview.top == view.top + self.navigationController!.navigationBar.bounds.height + UIApplication.sharedApplication().statusBarFrame.size.height

La dernière ligne est un peu longue car view.top fait référence à la vue vraiment supérieure et ne considère pas le remplissage ajouté à la fois par les hauteurs statusBar et navigationBar. Ils pourraient être remplacés en tant que constantes, en attendant que quelqu'un propose une solution plus élégante.

2
BlackBox

Mise à jour Swift 4

stackView.translatesAutoresizingMaskIntoConstraints = false

stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .trailing, relatedBy: .equal, toItem: stackView, attribute: .trailing, multiplier: 1, constant: 0))
stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .leading, relatedBy: .equal, toItem: stackView, attribute: .leading, multiplier: 1, constant: 0))

stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: self.addLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0))
stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute,multiplier: 1, 
constant: 131))
1
SWARTY

Pourquoi n'utilisez-vous pas l'activation par contrainte? Vous pourriez faire ceci: 

    var anchors = [NSLayoutConstraint]()
    anchors.append(view.topAnchor.constraint(equalTo: gamePreview.topAnchor, constant: 0))
    anchors.append(view.leadingAnchor.constraint(equalTo: gamePreview.leadingAnchor, constant: 0))
    anchors.append(view.trailingAnchor.constraint(equalTo: gamePreview.trailingAnchor, constant: 0))
    anchors.append(view.heightAnchor.constraint(equalTo: gamePreview.heightAnchor, multiplier: 1, constant: 131))
    NSLayoutConstraint.activate(anchors)

Ceci est un autre. J'espère que ça aide. Vous pouvez permuter view et gamePreview en fonction du parent/enfant ou créer une extension d'assistance générale pour vos contraintes. J'en ai trouvé un dans un tutoriel fourni par YouTuber Here et l'a légèrement modifié pour accepter les contraintes de début/fin, gauche/droite. Vous pouvez également ajouter safeLayoutGuide au paragraphe ci-dessous, ce qui le rend convivial pour l’avenir. Quelque chose comme ça: 

    func anchor(_ top: UIView? = nil, left: UIView? = nil, bottom: UIView? = nil, right: UIView? = nil, width: UIView? = nil, height: UIView? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0, isForLeading: Bool = false) -> [NSLayoutConstraint] {
    translatesAutoresizingMaskIntoConstraints = false

    var anchors = [NSLayoutConstraint]()
    if #available(iOS 11.0, *) {
        if let top = top {
            anchors.append(topAnchor.constraint(equalTo: top.safeAreaLayoutGuide.topAnchor, constant: topConstant))
        }

        if let left = left {
            if isForLeading  {
                anchors.append(leadingAnchor.constraint(equalTo: left.safeAreaLayoutGuide.leadingAnchor, constant: leftConstant))
            } else {
                anchors.append(leftAnchor.constraint(equalTo: left.safeAreaLayoutGuide.leftAnchor, constant: leftConstant))
            }
        }

        if let bottom = bottom {
            anchors.append(bottomAnchor.constraint(equalTo: bottom.safeAreaLayoutGuide.bottomAnchor, constant: -bottomConstant))
        }

        if let right = right {
            if isForLeading  {
                anchors.append(trailingAnchor.constraint(equalTo: right.safeAreaLayoutGuide.leadingAnchor, constant: rightConstant))
            } else {
                anchors.append(rightAnchor.constraint(equalTo: right.safeAreaLayoutGuide.rightAnchor, constant: -rightConstant))
            }
        }
        if let width = width {
            anchors.append(widthAnchor.constraint(equalTo: width.safeAreaLayoutGuide.widthAnchor, multiplier: 1, constant: widthConstant))
        } else if widthConstant > 0 {
            anchors.append(widthAnchor.constraint(equalToConstant: widthConstant))
        }

        if let height = height {
            anchors.append(heightAnchor.constraint(equalTo: height.safeAreaLayoutGuide.heightAnchor, multiplier: 1, constant: widthConstant))
        } else if heightConstant > 0 {
            anchors.append(widthAnchor.constraint(equalToConstant: heightConstant))
        }
        anchors.forEach({$0.isActive = true})
    } else {
        if let top = top {
            anchors.append(topAnchor.constraint(equalTo: top.topAnchor, constant: topConstant))
        }

        if let left = left {
            if isForLeading  {
                anchors.append(leadingAnchor.constraint(equalTo: left.leadingAnchor, constant: leftConstant))
            } else {
                anchors.append(leftAnchor.constraint(equalTo: left.leftAnchor, constant: leftConstant))
            }
        }

        if let bottom = bottom {
            anchors.append(bottomAnchor.constraint(equalTo: bottom.bottomAnchor, constant: -bottomConstant))
        }

        if let right = right {
            if isForLeading  {
                anchors.append(trailingAnchor.constraint(equalTo: right.leadingAnchor, constant: rightConstant))
            } else {
                anchors.append(rightAnchor.constraint(equalTo: right.rightAnchor, constant: -rightConstant))
            }
        }

        if let width = width {
            anchors.append(widthAnchor.constraint(equalTo: width.widthAnchor, multiplier: 1, constant: widthConstant))
        } else if widthConstant > 0 {
            anchors.append(widthAnchor.constraint(equalToConstant: widthConstant))
        }

        if let height = height {
            anchors.append(heightAnchor.constraint(equalTo: height.heightAnchor, multiplier: 1, constant: widthConstant))
        } else if heightConstant > 0 {
            anchors.append(widthAnchor.constraint(equalToConstant: heightConstant))
        }
        anchors.forEach({$0.isActive = true})
    }

    return anchors
}

Et puis appelez ça comme ça: 

        _ = view.anchor(gamePreview, left: gamePreview, right: gamePreview, height: gamePreview, heightConstant: 131, isForLeading: true)
0
Septronic