Estoy implementando un contenedor personalizado que es bastante similar a UINavigationController excepto para no retener la totalidad del controlador de la pila. Tiene un UINavigationBar está limitada al contenedor del controlador de topLayoutGuide, que pasa a ser 20 píxeles en la parte superior, que está bien.

Cuando agrego un niño de controlador de vista y poner su vista en la jerarquía quiero su topLayoutGuide visto en IB y se utiliza para la colocación de la salida del niño de la vista del controlador de vista del subvistas que aparecen en la parte inferior de mi barra de navegación. Hay una nota de lo que se debe hacer en la documentación pertinente:

El valor de esta propiedad es, específicamente, el valor de la longitud
la propiedad del objeto que se devuelve cuando se consulta esta propiedad. Este
el valor está limitado por el controlador de vista o por su adjuntando
contenedor de vista controlador (tales como la navegación o la barra de pestañas
controlador), de la siguiente manera:

  • Un controlador de vista no dentro de un contenedor de vista controlador limita esta propiedad para indicar la parte inferior de la barra de estado, si está visible,

    o bien para indicar el borde superior de la vista del controlador de vista.
  • Un controlador de vista dentro de un contenedor de controlador de vista no establece el valor de esta propiedad. En su lugar, el contenedor de vista controlador
    limita el valor a indicar:

    • La parte inferior de la barra de navegación, si una barra de navegación es visible
    • La parte inferior de la barra de estado, si sólo una barra de estado visible
    • El borde superior de la vista del controlador de vista, si no hay una barra de estado ni de la barra de navegación es visible

Pero no acabo de entender cómo se «limitan el valor» ya que tanto el topLayoutGuide y la longitud de las propiedades son de sólo lectura.

He probado este código para añadir a un niño de controlador de vista:

[self addChildViewController:gamePhaseController];
UIView *gamePhaseControllerView = gamePhaseController.view;
gamePhaseControllerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentContainer addSubview:gamePhaseControllerView];

NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[gamePhaseControllerView]-0-|"
                                                                         options:0
                                                                         metrics:nil
                                                                           views:NSDictionaryOfVariableBindings(gamePhaseControllerView)];

NSLayoutConstraint *topLayoutGuideConstraint = [NSLayoutConstraint constraintWithItem:gamePhaseController.topLayoutGuide
                                                                            attribute:NSLayoutAttributeTop
                                                                            relatedBy:NSLayoutRelationEqual
                                                                               toItem:self.navigationBar
                                                                            attribute:NSLayoutAttributeBottom
                                                                           multiplier:1 constant:0];
NSLayoutConstraint *bottomLayoutGuideConstraint = [NSLayoutConstraint constraintWithItem:gamePhaseController.bottomLayoutGuide
                                                                               attribute:NSLayoutAttributeBottom
                                                                               relatedBy:NSLayoutRelationEqual
                                                                                  toItem:self.bottomLayoutGuide
                                                                               attribute:NSLayoutAttributeTop
                                                                              multiplier:1 constant:0];
[self.view addConstraint:topLayoutGuideConstraint];
[self.view addConstraint:bottomLayoutGuideConstraint];
[self.contentContainer addConstraints:horizontalConstraints];
[gamePhaseController didMoveToParentViewController:self];

_contentController = gamePhaseController;

En el IB puedo especificar «en Virtud de la parte Superior de las Barras de» y «en Virtud de la parte Inferior de Barras» para el gamePhaseController. Uno de los puntos de vista es específicamente limitada a la parte superior guía de diseño, de todos modos en el dispositivo parece ser 20px fuera de la parte inferior del recipiente de la barra de navegación…

¿Cuál es la manera correcta de implementar un contenedor personalizado controlador con este comportamiento?

  • Entiendo que la documentación que se cita, la forma correcta de tratar con este escenario sería sobrescribir topLayoutGuide en el contenedor controlador de vista y regresando el correspondiente valor de y. Por desgracia, esto no parece ser posible, como @Stefan Fisk, señaló. Me gustaría considerar esto un error (o, más bien, una característica que falta) en el lado de apple.
  • Esto funciona muy bien en iOS 8. Alguna idea de lo qué ha cambiado ?
  • Todavía no funciona para mí. El sistema tiende a cero, la altura y el cero arriba compensar las limitaciones para el niño controlador de vista de la parte superior guía de diseño. Ahora puede ser fácilmente confirmada mediante la vista de la jerarquía de la depuración en Xcode 6. Agregar más restricciones, se crea el «unsatisfiable las limitaciones de la» situación.
  • Reemplazar topLayoutGuide y suministro de la clase que se muestra en esta respuesta stackoverflow.com/a/33215299/259521
InformationsquelleAutor Danchoys | 2013-10-25

4 Comentarios

  1. 30

    Como lo que he sido capaz de decirle después de horas de depuración, las guías de diseño son de sólo lectura, y derivado de las clases privadas se utiliza para las restricciones basadas en el diseño. Reemplazando los descriptores de acceso no hace nada (aunque ellos lo llaman), y todo es sólo craptastically molesto.

    • por favor, tenga en cuenta que mi respuesta se basa en el trabajo que hice en diciembre, las cosas podrían haber cambiado en las versiones posteriores.
    • Por desgracia, esta es la respuesta correcta. He iniciado un radar («Función de Solicitud: reemplazar layoutGuides»), siéntase libre de engañar es: openradar.appspot.com/21123507
    • la propiedad es de sólo lectura, pero usted puede simplemente pasar a él niño, por lo que puede hacer ajustes personalizados, vea mi respuesta abajo
  2. 5

    (ACTUALIZACIÓN: ya está disponible como cocoapod, ver https://github.com/stefreak/TTLayoutSupport)

    Una solución de trabajo es quitar de apple restricciones de diseño y añadir sus propias limitaciones. He hecho un poco de categoría de este.

    Aquí está el código, pero sugiero la cocoapod. Tiene pruebas de unidad y es más probable que sea hasta la fecha.

    //
    // UIViewController+TTLayoutSupport.h
    //
    // Created by Steffen on 17.09.14.
    //
    
    #import <UIKit/UIKit.h>
    
    @interface UIViewController (TTLayoutSupport)
    
    @property (assign, nonatomic) CGFloat tt_bottomLayoutGuideLength;
    
    @property (assign, nonatomic) CGFloat tt_topLayoutGuideLength;
    
    @end

    #import "UIViewController+TTLayoutSupport.h"
    #import "TTLayoutSupportConstraint.h"
    #import <objc/runtime.h>
    @interface UIViewController (TTLayoutSupportPrivate)
    //recorded apple's `UILayoutSupportConstraint` objects for topLayoutGuide
    @property (nonatomic, strong) NSArray *tt_recordedTopLayoutSupportConstraints;
    //recorded apple's `UILayoutSupportConstraint` objects for bottomLayoutGuide
    @property (nonatomic, strong) NSArray *tt_recordedBottomLayoutSupportConstraints;
    //custom layout constraint that has been added to control the topLayoutGuide
    @property (nonatomic, strong) TTLayoutSupportConstraint *tt_topConstraint;
    //custom layout constraint that has been added to control the bottomLayoutGuide
    @property (nonatomic, strong) TTLayoutSupportConstraint *tt_bottomConstraint;
    //this is for NSNotificationCenter unsubscription (we can't override dealloc in a category)
    @property (nonatomic, strong) id tt_observer;
    @end
    @implementation UIViewController (TTLayoutSupport)
    - (CGFloat)tt_topLayoutGuideLength
    {
    return self.tt_topConstraint ? self.tt_topConstraint.constant : self.topLayoutGuide.length;
    }
    - (void)setTt_topLayoutGuideLength:(CGFloat)length
    {
    [self tt_ensureCustomTopConstraint];
    self.tt_topConstraint.constant = length;
    [self tt_updateInsets:YES];
    }
    - (CGFloat)tt_bottomLayoutGuideLength
    {
    return self.tt_bottomConstraint ? self.tt_bottomConstraint.constant : self.bottomLayoutGuide.length;
    }
    - (void)setTt_bottomLayoutGuideLength:(CGFloat)length
    {
    [self tt_ensureCustomBottomConstraint];
    self.tt_bottomConstraint.constant = length;
    [self tt_updateInsets:NO];
    }
    - (void)tt_ensureCustomTopConstraint
    {
    if (self.tt_topConstraint) {
    //already created
    return;
    }
    //recording does not work if view has never been accessed
    __unused UIView *view = self.view;
    //if topLayoutGuide has never been accessed it may not exist yet
    __unused id<UILayoutSupport> topLayoutGuide = self.topLayoutGuide;
    self.tt_recordedTopLayoutSupportConstraints = [self findLayoutSupportConstraintsFor:self.topLayoutGuide];
    NSAssert(self.tt_recordedTopLayoutSupportConstraints.count, @"Failed to record topLayoutGuide constraints. Is the controller's view added to the view hierarchy?");
    [self.view removeConstraints:self.tt_recordedTopLayoutSupportConstraints];
    NSArray *constraints =
    [TTLayoutSupportConstraint layoutSupportConstraintsWithView:self.view
    topLayoutGuide:self.topLayoutGuide];
    //todo: less hacky?
    self.tt_topConstraint = [constraints firstObject];
    [self.view addConstraints:constraints];
    //this fixes a problem with iOS7.1 (GH issue #2), where the contentInset
    //of a scrollView is overridden by the system after interface rotation
    //this should be safe to do on iOS8 too, even if the problem does not exist there.
    __weak typeof(self) weakSelf = self;
    self.tt_observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceOrientationDidChangeNotification
    object:nil
    queue:[NSOperationQueue mainQueue]
    usingBlock:^(NSNotification *note) {
    __strong typeof(self) self = weakSelf;
    [self tt_updateInsets:NO];
    }];
    }
    - (void)tt_ensureCustomBottomConstraint
    {
    if (self.tt_bottomConstraint) {
    //already created
    return;
    }
    //recording does not work if view has never been accessed
    __unused UIView *view = self.view;
    //if bottomLayoutGuide has never been accessed it may not exist yet
    __unused id<UILayoutSupport> bottomLayoutGuide = self.bottomLayoutGuide;
    self.tt_recordedBottomLayoutSupportConstraints = [self findLayoutSupportConstraintsFor:self.bottomLayoutGuide];
    NSAssert(self.tt_recordedBottomLayoutSupportConstraints.count, @"Failed to record bottomLayoutGuide constraints. Is the controller's view added to the view hierarchy?");
    [self.view removeConstraints:self.tt_recordedBottomLayoutSupportConstraints];
    NSArray *constraints =
    [TTLayoutSupportConstraint layoutSupportConstraintsWithView:self.view
    bottomLayoutGuide:self.bottomLayoutGuide];
    //todo: less hacky?
    self.tt_bottomConstraint = [constraints firstObject];
    [self.view addConstraints:constraints];
    }
    - (NSArray *)findLayoutSupportConstraintsFor:(id<UILayoutSupport>)layoutGuide
    {
    NSMutableArray *recordedLayoutConstraints = [[NSMutableArray alloc] init];
    for (NSLayoutConstraint *constraint in self.view.constraints) {
    //I think an equality check is the fastest check we can make here
    //member check is to distinguish accidentally created constraints from _UILayoutSupportConstraints
    if (constraint.firstItem == layoutGuide && ![constraint isMemberOfClass:[NSLayoutConstraint class]]) {
    [recordedLayoutConstraints addObject:constraint];
    }
    }
    return recordedLayoutConstraints;
    }
    - (void)tt_updateInsets:(BOOL)adjustsScrollPosition
    {
    //don't update scroll view insets if developer didn't want it
    if (!self.automaticallyAdjustsScrollViewInsets) {
    return;
    }
    UIScrollView *scrollView;
    if ([self respondsToSelector:@selector(tableView)]) {
    scrollView = ((UITableViewController *)self).tableView;
    } else if ([self respondsToSelector:@selector(collectionView)]) {
    scrollView = ((UICollectionViewController *)self).collectionView;
    } else {
    scrollView = (UIScrollView *)self.view;
    }
    if ([scrollView isKindOfClass:[UIScrollView class]]) {
    CGPoint previousContentOffset = CGPointMake(scrollView.contentOffset.x, scrollView.contentOffset.y + scrollView.contentInset.top);
    UIEdgeInsets insets = UIEdgeInsetsMake(self.tt_topLayoutGuideLength, 0, self.tt_bottomLayoutGuideLength, 0);
    scrollView.contentInset = insets;
    scrollView.scrollIndicatorInsets = insets;
    if (adjustsScrollPosition && previousContentOffset.y == 0) {
    scrollView.contentOffset = CGPointMake(previousContentOffset.x, -scrollView.contentInset.top);
    }
    }
    }
    @end
    @implementation UIViewController (TTLayoutSupportPrivate)
    - (NSLayoutConstraint *)tt_topConstraint
    {
    return objc_getAssociatedObject(self, @selector(tt_topConstraint));
    }
    - (void)setTt_topConstraint:(NSLayoutConstraint *)constraint
    {
    objc_setAssociatedObject(self, @selector(tt_topConstraint), constraint, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (NSLayoutConstraint *)tt_bottomConstraint
    {
    return objc_getAssociatedObject(self, @selector(tt_bottomConstraint));
    }
    - (void)setTt_bottomConstraint:(NSLayoutConstraint *)constraint
    {
    objc_setAssociatedObject(self, @selector(tt_bottomConstraint), constraint, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (NSArray *)tt_recordedTopLayoutSupportConstraints
    {
    return objc_getAssociatedObject(self, @selector(tt_recordedTopLayoutSupportConstraints));
    }
    - (void)setTt_recordedTopLayoutSupportConstraints:(NSArray *)constraints
    {
    objc_setAssociatedObject(self, @selector(tt_recordedTopLayoutSupportConstraints), constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (NSArray *)tt_recordedBottomLayoutSupportConstraints
    {
    return objc_getAssociatedObject(self, @selector(tt_recordedBottomLayoutSupportConstraints));
    }
    - (void)setTt_recordedBottomLayoutSupportConstraints:(NSArray *)constraints
    {
    objc_setAssociatedObject(self, @selector(tt_recordedBottomLayoutSupportConstraints), constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (void)setTt_observer:(id)tt_observer
    {
    objc_setAssociatedObject(self, @selector(tt_observer), tt_observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (id)tt_observer
    {
    return objc_getAssociatedObject(self, @selector(tt_observer));
    }

    //
    // TTLayoutSupportConstraint.h
    //
    // Created by Steffen on 17.09.14.
    //
    #import <UIKit/UIKit.h>
    @interface TTLayoutSupportConstraint : NSLayoutConstraint
    + (NSArray *)layoutSupportConstraintsWithView:(UIView *)view topLayoutGuide:(id<UILayoutSupport>)topLayoutGuide;
    + (NSArray *)layoutSupportConstraintsWithView:(UIView *)view bottomLayoutGuide:(id<UILayoutSupport>)bottomLayoutGuide;
    @end

    //
    // TTLayoutSupportConstraint.m
    //
    // Created by Steffen on 17.09.14.
    //
    #import "TTLayoutSupportConstraint.h"
    @implementation TTLayoutSupportConstraint
    + (NSArray *)layoutSupportConstraintsWithView:(UIView *)view topLayoutGuide:(id<UILayoutSupport>)topLayoutGuide
    {
    return @[
    [TTLayoutSupportConstraint constraintWithItem:topLayoutGuide
    attribute:NSLayoutAttributeHeight
    relatedBy:NSLayoutRelationEqual
    toItem:nil
    attribute:NSLayoutAttributeNotAnAttribute
    multiplier:1.0
    constant:0.0],
    [TTLayoutSupportConstraint constraintWithItem:topLayoutGuide
    attribute:NSLayoutAttributeTop
    relatedBy:NSLayoutRelationEqual
    toItem:view
    attribute:NSLayoutAttributeTop
    multiplier:1.0
    constant:0.0],
    ];
    }
    + (NSArray *)layoutSupportConstraintsWithView:(UIView *)view bottomLayoutGuide:(id<UILayoutSupport>)bottomLayoutGuide
    {
    return @[
    [TTLayoutSupportConstraint constraintWithItem:bottomLayoutGuide
    attribute:NSLayoutAttributeHeight
    relatedBy:NSLayoutRelationEqual
    toItem:nil
    attribute:NSLayoutAttributeNotAnAttribute
    multiplier:1.0
    constant:0.0],
    [TTLayoutSupportConstraint constraintWithItem:bottomLayoutGuide
    attribute:NSLayoutAttributeBottom
    relatedBy:NSLayoutRelationEqual
    toItem:view
    attribute:NSLayoutAttributeBottom
    multiplier:1.0
    constant:0.0],
    ];
    }
    @end
    • Kudos para su solución. Me inspiró a hacer un pequeño experimento. Resulta que una guía de diseño de restricción pueden ser fácilmente identificados: constraint.firstItem == childControllerTopLayoutGuide && constraint.secondItem == nil y el cambio constante que parece hacer el trabajo (actualizaciones original id<LayoutSupport> objeto). Tiene que evaluarse a dicho enfoque?
    • esa es una idea genial, deberían simplificar los que la solución de muchos. Voy a tratar de eso y tal vez incorpore en mi respuesta. gracias 🙂
    • es que en realidad el uso privado de las Api o no? El _UILayoutSupportConstraint de la clase es privada, no está prohibido el envío de mensajes?
    • Creo que está bien. Que en realidad es menos invasiva que la modificación de subvistas de los controles del sistema (aplicaciones y haciendo que no están siendo rechazados). Después de todos estos son sólo limitaciones dentro de su vistas. A continuación, de nuevo, iOS9 podría traer cambios a la forma en que estas guías de diseño se implementan y esto va a romper :]
    • de hecho, me adoptó a su solución ahora, que hace que sea mucho más simple. cool idea!
    • Un mejor enfoque sería la configuración de la parte superior e inferior de las restricciones en las guías, en lugar de su altura. Configuración de la prioridad de los de nueva creación superior e inferior de las limitaciones de, digamos, @900 permitirá que el contenedor controlador de realidad ‘restringir’ a los guías por la adición requerida restricciones. Por ejemplo, un contenedor personalizado controlador podría colocar la parte superior del hijo del controlador de guía superior a la parte inferior de la barra de navegación. También funciona bien si el contenedor controlador decide ocultar la barra. Con la implementación actual tendrá que actualizar la longitud de la propiedad.
    • Aún mejor, para imitar el UINavigationController comportamiento, un contenedor personalizado controlador puede establecer dos limitaciones: contentController.topLayoutGuide.Top = auto.navigationBar.Parte inferior (@999) y contentController.topLayoutGuide.Top <= auto.topLayoutGuide.Parte superior (@1000). De este modo, el contenido no ir más allá del contenedor del controlador superior guía de diseño, que es generalmente de 20pt fuera de la parte superior (pero puede ser diferente si nuestro controlador está anidado dentro de otro contenedor controlador, es decir, la navegación controlador).
    • ¿prueba eso? ¿el layoutGuide de la longitud de la propiedad todavía trabajo para los no-diseño automático de código?
    • Hice la prueba y funcionó, aunque para ser sincero me han olvidado por completo de la no-autodiseño lado de las cosas.
    • Interesante enfoque. Por desgracia, UITableViewController no toma en cuenta estas limitaciones para gestionar contentInset. Pensé que esto podría ser una solución para fijar el margen inferior después de añadir banners de vista en la parte inferior, pero por desgracia no ayuda.
    • ¿estás seguro de que el cambio de la _UILayoutSupportConstraint constantes actualizaciones de la _UILayoutGuide longitud o marco? En mis pruebas no funcionó. De hecho, la actualización de la constante no era aún persistían en la restricción.
    • AFAIR terminé de observación (KVO) el diseño de la guía y el ajuste de valor cada vez que se cambia a una indeseada de valor. Chapucero, pero funcionó como se esperaba 🙂
    • Usted puede cambiar el diseño de la guía de valor/posición acotando que? Si es así, se debe trabajar para Ortwin Gentz así. Esa sería una solución perfecta. Tengo que darle una oportunidad! Gracias por la inspiración 🙂
    • Cuando lo probé hace algún tiempo yo no lo consigue trabajo. Pero quizá ha cambiado con alguna versión de iOS. mientras tanto estoy mejorando y simplificando TTLayoutSupport gran medida al apoyo de restricción de los guías de diseño en lugar de sólo la configuración de la longitud: github.com/stefreak/TTLayoutSupport/pull/6
    • Esto ya no es necesario, válido guía de diseño de clase para volver de topLayoutGuide y bottomLayoutGuide ha sido resuelto y funciona en iOS 9: stackoverflow.com/a/33215299/259521

  3. 1

    Creo que significa que usted debe limitar el diseño de guías de uso de diseño automático, es decir, un NSLayoutConstraint objeto, en lugar de ajustar manualmente la configuración de la propiedad de longitud. La longitud de la propiedad está disponible para las clases que eligen no usar autodiseño, pero parece que con contenedor personalizado de vista de los controladores no tienen esta opción.

    Supongo que la mejor práctica es hacer que la prioridad de la restricción en el contenedor controlador de vista de que «establece» el valor de la propiedad length para UILayoutPriorityRequired.

    No estoy seguro de qué atributo de diseño que se unen, ya sea NSLayoutAttributeHeight o NSLayoutAttributeBottom probablemente.

    • Esto parece perfectamente razonable; por desgracia, no funciona, como la guía de diseño ya está completamente restringido.
    • En realidad, esto funciona en iOS 8 Beta 3! ¡Hurra!
    • ¿cómo estás de la configuración de sus limitaciones, de manera que esto funciona en el iOS 8 beta? Todos mis experimentos producido advertencias sobre ambigua restricciones.
    • Acabo de crear una única restricción en el que se especifica el NSLayoutAttributeBottom de niño.topLayoutConstraint a algo en mi contenedor de vista. Yo no he hecho nada de fantasía; si no funciona, tal vez hacer un ejemplo mínimo y publicar una pregunta? (Por favor, poke me si usted.)
    • No podía conseguir que esto funcione bien. Supongo que si usted agregar una restricción en realidad estás limitando tu «algo en mi contenedor de vista» a la misma topLayoutGuide. No a la inversa.
    • Las restricciones son siempre de dos vías, por lo que no estoy seguro de lo que quieres decir. Como he sugerido anteriormente, si usted tiene un caso donde esto no funciona, me animo a publicar una nueva pregunta y el enlace aquí.
    • se sugiere que usted puede cambiar el layoutGuides mediante la adición de personalizado restricciones. Que no parece ser el caso. Se la voy a dar hasta en el intento de cambiar el layoutGuides. Es la lucha contra los marcos de referencia, parece.

  4. 0

    En la vista principal controlador de

    - (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    for (UIViewController * childViewController in self.childViewControllers) {
    //Pass the layouts to the child
    if ([childViewController isKindOfClass:[MyCustomViewController class]]) {
    [(MyCustomViewController *)childViewController parentTopLayoutGuideLength:self.topLayoutGuide.length parentBottomLayoutGuideLength:self.bottomLayoutGuide.length];
    }
    }
    }

    y de pasar los valores a los niños, se puede tener una clase personalizada como en mi ejemplo, un protocolo, o tal vez puede tener acceso a la desplácese a la vista de la jerarquía de

Dejar respuesta

Please enter your comment!
Please enter your name here