cocoa - usar - ¿Hay una forma adecuada de manejar superposición de hermanos NSView?
componentes de la cocoa (5)
Estoy trabajando en una aplicación Cocoa y me he encontrado con una situación en la que me gustaría tener dos objetos NSView superpuestos. Tengo un NSView padre que contiene dos subvistas (NSView A y NSView B), cada una de las cuales puede tener varias subvistas propias.
¿Hay una forma adecuada de manejar este tipo de superposición? NSView B siempre estaría "por encima" de NSView A, por lo que quiero que las porciones superpuestas de NSView A queden enmascaradas.
Chris, la única solución es usar CALayers. Esa es definitivamente la única solución.
Los NSView se rompen en OSX (septiembre de 2010): los hermanos no funcionan correctamente. Uno u otro aparecerán aleatoriamente en la parte superior.
Solo para repetir, el problema es con los hermanos .
Para probar esto: usando NSViews y / o nsimageviews. Cree una aplicación con una vista que sea una imagen grande (1000x1000 decir). En la vista, coloque tres o cuatro imágenes pequeñas / NSViews aquí y allá. Ahora coloque otra imagen grande de 1000x1000 en la parte superior. Cree y ejecute la aplicación repetidamente; verá que está completamente rota. A menudo, las capas inferiores (pequeñas) aparecerán en la parte superior de la capa de cobertura grande. si activa la capa de respaldo en NSViews, no ayuda, sin importar qué combo intente. Entonces esa es la prueba definitiva.
Tienes que abandonar NSViews y usar CALayers y eso es todo.
La única molestia con CALayers es que no puedes usar el IB para configurar tus cosas. Tienes que establecer todas las posiciones de capa en el código,
yy = [CALayer layer];
yy.frame = CGRectMake(300,300, 300,300);
Haga un solo NSView, cuyo único propósito es retener su primer CALayer (quizás llamado ''trasero''), y luego simplemente coloque todos sus CALayers en la parte trasera.
rear = [CALayer layer];
rear.backgroundColor = CGColorCreateGenericRGB( 0.75, 0.75, 0.75, 1.0 );
[yourOnlyNsView setLayer:rear]; // these two lines must be in this order
[yourOnlyNsView setWantsLayer:YES]; // these two lines must be in this order
[rear addSublayer:rr];
[rear addSublayer:yy];
[yy addSublayer:s1];
[yy addSublayer:s2];
[yy addSublayer:s3];
[yy addSublayer:s4];
[rear addSublayer:tt];
[rear addSublayer:ff];
todo funciona a la perfección, puedes anidar y agrupar todo lo que desees y todo funciona perfectamente, con todo lo que aparece correctamente encima / debajo de todo lo que debería aparecer arriba / abajo, sin importar cuán compleja sea tu estructura. Luego puedes hacer cualquier cosa con las capas, o barajar las cosas de la manera típica,
-(void) shuff
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:0.0f]
forKey:kCATransactionAnimationDuration];
if ..
[rear insertSublayer:ff below:yy];
else
[rear insertSublayer:ff above:yy];
[CATransaction commit];
}
(La única razón del molesto contenedor ''en cero segundos'' para todo lo que haces es evitar la animación que te dan de forma gratuita, ¡a menos que quieras la animación!)
Por cierto en esta cita de Apple,
Por razones de rendimiento, Cocoa no impone el recorte entre las vistas de hermanos o garantiza la correcta invalidación y el comportamiento de dibujo cuando las vistas de hermanos se superponen.
Su siguiente oración ...
Si desea que una vista se dibuje frente a otra vista, debe hacer que la vista frontal sea una subvista (o un descendiente) de la vista posterior.
Es en gran medida absurdo (no se puede reemplazar necesariamente a los hermanos con substitutos, y el error obvio descrito en la prueba anterior aún existe).
¡Así que son CALayers! ¡Disfrutar!
Como Nate escribió, uno puede usar:
self.addSubview(btn2, positioned: NSWindowOrderingMode.Above, relativeTo: btn1)
Sin embargo, el orden de las vistas no se respeta tan pronto como solicite una de las vistas para volver a dibujarse a través de la llamada "needDisplay = true"
Los hermanos no recibirán la llamada drawRect, solo las vistas dirigen la jerarquía.
Actualización 1
Para resolver este problema, tuve que cavar profundo, muy profundo. Probablemente una semana de investigación y he difundido mis hallazgos en unos pocos artículos. El avance final está en este artículo: http://stylekit.org/blog/2015/12/24/The-odd-case-of-luck/
Actualización 2
Tenga en cuenta que el concepto es difícil de entender, pero funciona y funciona muy bien. Aquí está el resultado final y el código para sustentarlo, enlaces al repositorio de github, etc .: http://stylekit.org/blog/2015/12/30/Graphic-framework-for-OSX/
Hay una manera de hacerlo sin usar CALayers
y una aplicación en la que he estado trabajando puede probarlo. Crea dos ventanas y usa esto:
[mainWindow addChildWindow:otherWindow ordered:NSWindowAbove];
Para eliminar " otherWindow " use:
[mainWindow removeChildWindow:otherWindow];
[otherWindow orderOut:nil];
Y es probable que desee tomar la barra de título de la ventana con:
[otherWindow setStyleMask:NSBorderlessWindowMask];
Para asegurarse de que NSView B
siempre superponga NSView A
, asegúrese de utilizar el NSWindowOrderingMode
correcto cuando agregue la subview:
[parentView addSubview:B positioned:NSWindowAbove relativeTo:A];
También debe tener en cuenta que a las partes ocultas de A no se les pedirá que vuelvan a dibujarse si la vista B es 100% opaca.
Si mueve las subvistas, también debe asegurarse de llamar a -setNeedsDisplayInRect:
para las áreas de la vista que está descubriendo.
Si su aplicación es solo 10.5, active las capas para las vistas y debería funcionar.
Si quiere dar soporte a 10.4 y siguientes, necesitará encontrar una manera de no solapar las vistas, ya que la superposición de vistas entre hermanos es un comportamiento indefinido. Como dice View Programming Guide:
Por razones de rendimiento, Cocoa no impone el recorte entre las vistas de hermanos o garantiza la correcta invalidación y el comportamiento de dibujo cuando se superponen las vistas de hermanos. Si desea que una vista se dibuje frente a otra vista, debe hacer que la vista frontal sea una subvista (o un descendiente) de la vista posterior.
He visto algunos hacks que pueden hacer que funcione un poco a veces, pero no es algo en lo que puedas confiar. Tendrá que hacer que View A sea una subvista de View B o hacer una vista gigante que maneje sus dos funciones.