ios - example - swift page indicator
topLayoutGuide en el controlador de vista infantil (11)
Creo que las guías están destinadas a ser configuradas para controladores secundarios anidados. Por ejemplo, supongamos que tienes:
- Una pantalla de 100x50, con una barra de estado de 20 píxeles en la parte superior.
- Un controlador de vista de nivel superior, que cubre toda la ventana. Su topLayoutGuide es 20.
- Un controlador de vista anidado dentro de la vista superior que cubre los 95 píxeles inferiores, p. Ej. 5 píxeles hacia abajo desde la parte superior de la pantalla. Esta vista debe tener una topLayoutGuide de 15, ya que sus 15 píxeles superiores están cubiertos por la barra de estado.
Eso tendría sentido: significa que el controlador de vista anidado puede establecer restricciones para evitar la superposición no deseada, al igual que una de nivel superior. No tiene que importar que esté anidado, o en qué parte de la pantalla lo muestra su padre, y el controlador de vista principal no necesita saber cómo desea el niño interactuar con la barra de estado.
Eso también parece ser lo que la documentación, o al menos parte de la documentación, dice:
La guía de diseño superior indica la distancia, en puntos, entre la parte superior de la vista de un controlador de vista y la parte inferior de la barra inferior que se superpone a la vista
Eso no dice nada acerca de solo trabajar para controladores de vista de alto nivel.
Pero, no sé si esto es lo que realmente sucede. Definitivamente he visto controladores de vista infantil con topLayoutGuides que no son cero, pero todavía estoy descifrando las peculiaridades. (En mi caso, la guía superior debería ser cero, ya que la vista no está en la parte superior de la pantalla, que es en lo que me estoy golpeando en este momento ...)
Tengo un UIPageViewController
con barra de estado translúcida y barra de navegación. Su topLayoutGuide
tiene 64 píxeles, como se esperaba.
Sin embargo, los controladores de vista secundarios de UIPageViewController
informan una topLayoutGuide
de 0 píxeles, incluso si se muestran debajo de la barra de estado y la barra de navegación.
¿Es este el comportamiento esperado? Si es así, ¿cuál es la mejor manera de posicionar una vista de un controlador de vista infantil bajo la real topLayoutGuide
?
(menos que usar parentViewController.topLayoutGuide
, que consideraría un hack)
En caso de que tenga UIPageViewController
como lo hace OP y tiene, por ejemplo, controladores de vista de colección como UIPageViewController
. Resulta que la solución para la inserción de contenido es simple y funciona en iOS 8:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
UIEdgeInsets insets = self.collectionView.contentInset;
insets.top = self.parentViewController.topLayoutGuide.length;
self.collectionView.contentInset = insets;
self.collectionView.scrollIndicatorInsets = insets;
}
Este es el enfoque para la longitud de guía conocida. Cree restricciones no para guías, sino para ver la parte superior con constantes fijas asumiendo que la distancia de guía será.
Este es un comportamiento desafortunado que parece haberse rectificado en iOS 11 con la renovación de la API del área segura. Dicho esto, siempre obtendrá el valor correcto del controlador de vista raíz. Por ejemplo, si desea la altura del área segura superior antes de iOS 11:
Swift 4
let root = UIApplication.shared.keyWindow!.rootViewController!
let topLayoutGuideLength = root.topLayoutGuide.length
Esto ha sido abordado en iOS 8.
Cómo establecer la posición topLayoutGuide para el controlador de vista infantil
Esencialmente, el controlador de vista de contenedor debe restringir el (top|bottom|left|right)LayoutGuide
del controlador de vista hijo (top|bottom|left|right)LayoutGuide
como lo haría con cualquier otra vista. (En iOS 7, ya estaba totalmente restringido con una prioridad requerida, por lo que esto no funcionó).
Implementación rápida de @NachoSoto respuesta:
extension UIViewController {
func navigationBarTopLayoutGuide() -> UILayoutSupport {
if let parentViewController = self.parentViewController {
if !parentViewController.isKindOfClass(UINavigationController) {
return parentViewController.navigationBarTopLayoutGuide()
}
}
return self.topLayoutGuide
}
func navigationBarBottomLayoutGuide() -> UILayoutSupport {
if let parentViewController = self.parentViewController {
if !parentViewController.isKindOfClass(UINavigationController) {
return parentViewController.navigationBarBottomLayoutGuide()
}
}
return self.bottomLayoutGuide
}
}
La documentación dice que use topLayoutGuide en viewDidLayoutSubviews si está utilizando una subclase UIViewController o layoutSubviews si está utilizando una subclase UIView.
Si lo usa en esos métodos, debe obtener un valor apropiado distinto de cero.
Enlace de documentación: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instp/UIViewController/topLayoutGuide
No estoy seguro si alguien todavía tiene problemas con esto, como lo hice hace unos minutos.
Mi problema es así (fuente gif de https://knuspermagier.de/2014-fixing-uipageviewcontrollers-top-layout-guide-problems.html ).
Para abreviar, mi pageViewController tiene 3 controles de vista secundarios. El primer controlador de vista está bien, pero cuando me deslizo al siguiente, la vista completa se compensa incorrectamente con la parte superior (~ 20 píxeles, supongo), pero volverá a la normalidad después de que mi dedo esté fuera de la pantalla.
Me quedé despierto toda la noche buscando una solución para esto, pero todavía no encontré suerte para encontrar uno. Entonces, de repente, se me ocurrió esta loca idea:
[pageViewController setViewControllers:@[listViewControllers[1]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {
}];
[pageViewController setViewControllers:@[listViewControllers[0]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) {
}];
Mi listViewControllers tiene 3 controles de vista secundarios. El que está en el índice 0 tiene un problema, así que primero lo configuré como root del pageviewcontroller, y luego lo configuré de nuevo en el controlador de la primera vista (como esperaba). Voila, funcionó!
¡Espero eso ayude!
Podría estar equivocado, pero en mi opinión el comportamiento es correcto. El controlador de vista de contenedor puede usar el valor topLayout para diseñar las subvistas de su vista.
La referencia dice:
Para usar una guía de diseño superior sin usar restricciones, obtenga la posición de la guía relativa al límite superior de la vista que lo contiene .
En el elemento primario, relativo a la vista que lo contiene, el valor será 64.
En el elemento secundario, relativo a la vista que lo contiene (el elemento principal), el valor será 0.
En el contenedor View Controller, puede usar la propiedad de esta manera:
- (void) viewWillLayoutSubviews {
CGRect viewBounds = self.view.bounds;
CGFloat topBarOffset = self.topLayoutGuide.length;
for (UIView *view in [self.view subviews]){
view.frame = CGRectMake(viewBounds.origin.x, viewBounds.origin.y+topBarOffset, viewBounds.size.width, viewBounds.size.height-topBarOffset);
}
}
El controlador de vista infantil no necesita saber que hay una barra de navegación y una barra de estado: su padre ya habrá presentado sus subvistas teniendo esto en cuenta.
Si creo un nuevo proyecto basado en página, lo incrusto en un controlador de navegación y agrego este código a los controladores de vista principales, parece que funciona bien:
Si bien esta respuesta podría ser correcta, todavía me encontré teniendo que desplazarme hasta el árbol de contención para encontrar el controlador de vista padre correcto y obtener lo que describió como la "verdadera topLayoutGuide
". De esta forma puedo implementar manualmente AdjustsScrollViewInsets de forma manual.
Así es como lo estoy haciendo:
En mi controlador de vista de tabla (una subclase de UIViewController
realidad), tengo esto:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
_tableView.frame = self.view.bounds;
const UIEdgeInsets insets = (self.automaticallyAdjustsScrollViewInsets) ? UIEdgeInsetsMake(self.ms_navigationBarTopLayoutGuide.length,
0.0,
self.ms_navigationBarBottomLayoutGuide.length,
0.0) : UIEdgeInsetsZero;
_tableView.contentInset = _tableView.scrollIndicatorInsets = insets;
}
Observe los métodos de categoría en UIViewController
, así es como los implementé:
@implementation UIViewController (MSLayoutSupport)
- (id<UILayoutSupport>)ms_navigationBarTopLayoutGuide {
if (self.parentViewController &&
![self.parentViewController isKindOfClass:UINavigationController.class]) {
return self.parentViewController.ms_navigationBarTopLayoutGuide;
} else {
return self.topLayoutGuide;
}
}
- (id<UILayoutSupport>)ms_navigationBarBottomLayoutGuide {
if (self.parentViewController &&
![self.parentViewController isKindOfClass:UINavigationController.class]) {
return self.parentViewController.ms_navigationBarBottomLayoutGuide;
} else {
return self.bottomLayoutGuide;
}
}
@end
Espero que esto ayude :)
puede agregar una restricción en el guión gráfico y cambiarla en viewWillLayoutSubviews
algo como esto:
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.topGuideConstraint.constant = [self.parentViewController.topLayoutGuide length];
}