2015-10-12 13 views
19

Ho una gerarchia di viste complesse, costruita in Interface Builder, con UIStackViews nidificate. Ricevo avvisi di "limiti insoddisfacenti" ogni volta che nascondo alcune delle mie viste di stack interne. Ho rintracciato giù a questo:Nested UIStackViews Vincoli interrotti

(
    "<NSLayoutConstraint:0x1396632d0 'UISV-canvas-connection' UIStackView:0x1392c5020.top == UILabel:0x13960cd30'Also available on iBooks'.top>", 
    "<NSLayoutConstraint:0x139663470 'UISV-canvas-connection' V:[UIButton:0x139554f80]-(0)-| (Names: '|':UIStackView:0x1392c5020)>", 
    "<NSLayoutConstraint:0x139552350 'UISV-hiding' V:[UIStackView:0x1392c5020(0)]>", 
    "<NSLayoutConstraint:0x139663890 'UISV-spacing' V:[UILabel:0x13960cd30'Also available on iBooks']-(8)-[UIButton:0x139554f80]>" 
) 

In particolare, il UISV-spacing vincolo: quando viene nascosto un UIStackView sua alta vincolo ottiene uno 0 costante, ma che sembra scontrarsi con vincolo spaziatura del stackview interno: richiede 8 punti tra la mia etichetta e il pulsante, che è inconciliabile con il vincolo di occultamento e quindi i vincoli si bloccano.

C'è un modo per aggirare questo? Ho cercato in modo ricorsivo di nascondere tutte le StackView interne della vista pila nascosta, ma questo si traduce in strane animazioni in cui il contenuto fluttua fuori dallo schermo e causa gravi interruzioni di FPS all'avvio, pur non risolvendo il problema.

risposta

11

Idealmente, potremmo semplicemente impostare la priorità del vincolo UISV-spacing su un valore inferiore, ma non sembra esserci un modo per farlo. :)

Sto avendo successo impostando la proprietà spacing delle viste dello stack nidificato su 0 prima di nasconderlo e ripristinando il valore corretto dopo averlo reso nuovamente visibile.

Penso che farlo in modo ricorsivo sulle viste dello stack nidificato funzionerebbe. È possibile memorizzare il valore originale della proprietà spacing in un dizionario e ripristinarlo successivamente.

Il mio progetto ha solo un unico livello di nidificazione, quindi non sono sicuro se ciò causerebbe problemi di FPS. Finché non si animano i cambiamenti nella spaziatura, non penso che creerebbe troppa hit.

+1

grazie, ci provo oggi e accetto la risposta se funziona :) –

12

Ho riscontrato un problema simile con l'occultamento di UISV. Per me, la soluzione era ridurre le priorità dei miei limiti da Required (1000) a qualcosa di meno. Quando vengono aggiunti i vincoli che nascondono UISV, hanno priorità e i vincoli non si scontrano più.

+2

il problema è che non ho alcun vincolo su quegli elementi :(tuttavia, questo ha risolto altri problemi simili nel caso in cui io –

+0

Ho avuto un problema simile e questo ha risolto il problema per me – SeanR

+0

Holy cow! Dopo una giornata di riflessione, di meraviglia e di un serio brainstorming, era proprio così: è logico che UIStackView funzioni su vincoli che noi non vedere, ma intrinsecamente in conflitto con i nostri vincoli .Grande risposta, grazie @Jaanus! – kbpontius

13

Questo è un problema noto con l'occultamento di viste di stack annidate.

Ci sono essenzialmente 3 soluzioni a questo problema:

  1. modificare la spaziatura a 0, ma poi avrete bisogno di ricordare il valore di spaziatura precedente.
  2. Chiama il innerStackView.removeFromSuperview(), ma poi dovrai ricordare dove inserire la vista dello stack.
  3. Consente di avvolgere la vista stack in un UIView con almeno un vincolo 999. Per esempio. top @ 1000, che porta @ 1000, seguito @ 1000, bottom @ 999.

La terza opzione è la migliore a mio parere. Per ulteriori informazioni su questo problema, perché accade, sulle diverse soluzioni e su come implementare la soluzione 3, vedere my answer to a similar question.

+1

Soluzione 3 ha funzionato per me, ho messo la vista dello stack all'interno di un UIView vincolato a tutti i bordi (come uno normale). Sembra che la vista stack non piaccia essere la vista radice. – malhal

0

Ecco l'implementazione del suggerimento di Senseful n. 3 scritto come classe Swift 3 utilizzando i vincoli di SnapKit.Ho provato anche ignorando le proprietà, ma mai ottenuto che funziona senza avvisi, quindi mi bastone con avvolgimento UIStackView:

class NestableStackView: UIView { 
    private var actualStackView = UIStackView() 

    override init(frame: CGRect) { 
     super.init(frame: frame); 
     addSubview(actualStackView); 
     actualStackView.snp.makeConstraints { (make) in 
      // Lower edges priority to allow hiding when spacing > 0 
      make.edges.equalToSuperview().priority(999); 
     } 
    } 

    convenience init() { 
     self.init(frame: CGRect.zero); 
    } 

    required init?(coder aDecoder: NSCoder) { 
     fatalError("init(coder:) has not been implemented") 
    } 

    func addArrangedSubview(_ view: UIView) { 
     actualStackView.addArrangedSubview(view); 
    } 

    func removeArrangedSubview(_ view: UIView) { 
     actualStackView.removeArrangedSubview(view); 
    } 

    var axis: UILayoutConstraintAxis { 
     get { 
      return actualStackView.axis; 
     } 
     set { 
      actualStackView.axis = newValue; 
     } 
    } 

    open var distribution: UIStackViewDistribution { 
     get { 
      return actualStackView.distribution; 
     } 
     set { 
      actualStackView.distribution = newValue; 
     } 
    } 

    var alignment: UIStackViewAlignment { 
     get { 
      return actualStackView.alignment; 
     } 
     set { 
      actualStackView.alignment = newValue; 
     } 
    } 

    var spacing: CGFloat { 
     get { 
      return actualStackView.spacing; 
     } 
     set { 
      actualStackView.spacing = newValue; 
     } 
    } 
} 
7

Quindi, avete questo:

broken animation

e il problema è , quando si primo crollo della pila interna, si ottiene auto errori di layout:

2017-07-02 15:40:02.377297-0500 nestedStackViews[17331:1727436] [LayoutConstraints] Unable to simultaneously satisfy constraints. 
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
     (1) look at each constraint and try to figure out which you don't expect; 
     (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x62800008ce90 'UISV-canvas-connection' UIStackView:0x7fa57a70fce0.top == UILabel:0x7fa57a70ffb0'Top Label of Inner Stack'.top (active)>", 
    "<NSLayoutConstraint:0x62800008cf30 'UISV-canvas-connection' V:[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...']-(0)-| (active, names: '|':UIStackView:0x7fa57a70fce0)>", 
    "<NSLayoutConstraint:0x62000008bc70 'UISV-hiding' UIStackView:0x7fa57a70fce0.height == 0 (active)>", 
    "<NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...'] (active)>" 
) 

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...'] (active)> 

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. 
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful. 

il problema, come avrete notato, è che la vista dello stack esterno applica un HEIG ht = 0 vincolo alla vista pila interna. Ciò è in conflitto con il vincolo di riempimento di 8 punti applicato dalla vista dello stack interno tra le proprie sottoview. Entrambi i vincoli non possono essere soddisfatti contemporaneamente.

La vista pila esterna utilizza questo vincolo altezza = 0, credo, perché sembra meglio quando è animato che lasciare che la vista interna sia nascosta senza restringersi prima.

C'è una semplice soluzione per questo: avvolgere la vista della pila interna in un semplice UIView e nascondere tale wrapper. Dimostrerò.

Ecco lo schema scena per la versione rotto sopra:

broken outline

Per risolvere il problema, selezionare la vista interna di stack. Dalla barra dei menu, scegliere Editor> Incorpora in> Vista:

embed in view

Interface Builder ha creato un vincolo di larghezza sulla vista involucro quando ho fatto questo, in modo da eliminare il vincolo di larghezza:

delete width constraint

Avanti, crea vincoli tra i quattro bordi dell'involucro e la vista pila interna:

create constraints

A questo punto, il layout è effettivamente corretto in fase di esecuzione, ma Interface Builder lo disegna in modo errato. Puoi sistemarlo impostando le priorità di abbraccio verticale dei bambini dello stack interno più in alto. Li ho impostata su 800:

hugging priorities

non abbiamo in realtà risolto il problema vincolo insoddisfacibile a questo punto. Per fare ciò, trova il vincolo di fondo che hai appena creato e imposta la sua priorità a meno del necessario. Cambiamo a 800:

change bottom constraint priority

Infine, è presumibilmente avuto una presa nel vostro controller della vista collegata alla vista interna di stack, perché stavi cambiando la sua proprietà hidden. Cambia quella presa per connetterti alla vista del wrapper invece della vista della pila interna. Se il tipo di presa è UIStackView, sarà necessario cambiarlo in UIView.Il mio era già di tipo UIView, quindi ho solo ricollegata nella storyboard:

change outlet

Ora, quando si alternare proprietà hidden della vista involucro, viene visualizzata la vista dello stack al collasso, con nessun avviso vincolo insoddisfacibile. Sembra praticamente identico, quindi non mi preoccuperò di pubblicare un'altra GIF dell'applicazione in esecuzione.

È possibile trovare il mio progetto di prova in this github repository.

+0

Nota anche che puoi facilmente modificare l'effetto dell'animazione usando la vista wrapper. Prova ad attivare "Clips To Bounds" nella vista wrapper e imposta la priorità del vincolo inferiore a 600. Si ottiene un buon effetto slide-under invece di un effetto "squishing". –

0

Un altro approccio

cercare di evitare UIStackViews nidificate. Li amo e costruisco quasi tutto con loro. Ma quando ho riconosciuto che aggiungono segretamente dei vincoli, cerco di usarli al massimo livello e non nidificati dove possibile. In questo modo posso specificare la 2a priorità più alta .defaultHigh al vincolo di spaziatura che risolve i miei avvertimenti.

Questa priorità è sufficiente per evitare la maggior parte dei problemi di layout.

Naturalmente è necessario specificare alcuni altri vincoli ma in questo modo si ha il pieno controllo su di essi e si rende esplicito il layout della vista.