objective c - Haz que NSView NO subte las subvistas fuera de sus límites
objective-c cocoa (7)
Después de 5 horas de lucha acabo de lograrlo. Simplemente cambie la clase de cualquier NSView en .storyboard o .xib a NoClippingView y NO recortará ninguna de sus subvistas.
class NoClippingLayer: CALayer {
override var masksToBounds: Bool {
set {
}
get {
return false
}
}
}
class NoClippingView: OSView {
override var wantsDefaultClipping: Bool {
return false
}
override func awakeFromNib() {
super.awakeFromNib()
wantsLayer = true
layer = NoClippingLayer()
}
}
¿Por qué sobrescribo masksToBounds en NoClippingLayer? Debido a que algunas clases nativas de AppKit cambian esta propiedad de todas las subcapas en tiempo de ejecución sin advertencias. Por ejemplo, NSCollectionView hace esto para las vistas de sus celdas.
¿Es posible hacer que un NSView
no recorte sus subvistas que están fuera de los límites? En iOS, simplemente configuraría clipsToBounds
de mi UIView
no NO
. Pero NSView
no tiene tal propiedad. Intenté experimentar con wantsLayer
, masksToBounds
, wantsDefaultClipping
, pero parece que todo esto solo cambia el recorte del método drawRect
, no las subvistas.
El comportamiento en torno a esto parece haber cambiado. Solo necesitas configurar la capa de la vista para que no se enmascare a los límites.
view.wantsLayer = true
view.layer?.masksToBounds = false
El método wantsDefaultClipping funciona ... tanto el padre como el hijo necesitan anular wantsDefaultClipping.
Sin embargo, una vez que recorres esa ruta, limpiar después de ti mismo es engorroso.
Aquí está mi solución para adjuntar texto a un NSProgressBar:
Gracias a BGHUDAppKit y
@implementation BGHUDOverlayTextField
- (BOOL) wantsDefaultClipping { return NO; } // thanks .com
@end
@interface BGHUDProgressIndicator : NSProgressIndicator {
NSBezierPath *progressPath;
NSString *themeKey;
NSString *LayerKey;
BGHUDOverlayTextField *customTitleField;
BOOL hiding;
}
@implementation BGHUDProgressIndicator
- (BOOL) wantsDefaultClipping { return (customTitleField == nil); } // thanks .com
- (void) setCustomTitle: (NSMutableAttributedString *)iTitle
{
if ( !customTitleField )
{
NSRect r = [self bounds];
r.size.height += 10;
customTitleField = [[BGHUDOverlayTextField alloc] initWithFrame:r];
[self addSubview: customTitleField];
}
[customTitleField setAttributedStringValue:iTitle];
}
- (void)setHidden:(BOOL)flag
{
if ( customTitleField && flag && !hiding )
{
hiding = YES;
NSRect fr = [self bounds];
NSRect eraseFr = fr;
eraseFr.size = [customTitleField frame].size;
[self displayRectIgnoringOpacity:fr];
}
else if ( !flag )
hiding = NO;
[super setHidden:flag];
}
- (void) drawRect: (NSRect)fr
{
if ( customTitleField )
{
fr = [self bounds];
NSRect eraseFr = fr;
eraseFr.size = [customTitleField frame].size;
[[NSColor normalSolidFill] set];
NSRectFill( eraseFr );
if ( hiding )
{
[customTitleField setAttributedStringValue:nil];
NSRectFill( eraseFr );
return;
}
[NSGraphicsContext saveGraphicsState];
NSRectClip(fr);
}
[super drawRect:fr];
if ( customTitleField )
{
[NSGraphicsContext restoreGraphicsState];
}
}
No pude conseguir que ninguna de estas soluciones funcionara. Creo que es posible que solo tengas que cambiar la jerarquía de vistas para que las vistas no se dibujen fuera de su marco. Puede crear una vista intermedia que no haga ningún dibujo pero tenga un marco más grande para permitir un área más grande.
Para las personas que buscan un código swift equivalente:
Agregue este código a una subclase de NSView:
override var wantsDefaultClipping:Bool{return false}//avoids clipping the view
No tienes que cambiar el padre. siempre que el padre tenga un área de recorte lo suficientemente grande. Pero también es conveniente agregar esta variable al padre.
Actualización 1:
Este no es un tema fácil, he pasado semanas investigando esto y mi mejor solución se puede encontrar aquí: http://stylekit.org/blog/2015/12/24/The-odd-case-of-luck/
Actualización 2:
Para ver el concepto anterior en acción, consulte este artículo: http://stylekit.org/blog/2015/12/30/Graphic-framework-for-OSX/
Si está utilizando la reproducción automática, invalide los ajustes de alignmentRectInsets
de alignmentRectInsets
para expandir el área de recorte, haciéndolo más grande que el rectángulo de alineación. Este ejemplo da un espacio de 50 en todos los lados:
override var alignmentRectInsets: NSEdgeInsets {
return NSEdgeInsets(top: 50.0, left: 50.0, bottom: 50.0, right: 50.0)
}
Tuve la wantsDefaultClipping
de resolver esto al anular el wantsDefaultClipping
de wantsDefaultClipping
de las subvistas para devolver NO
.