cocoa - Usando Autolayout con la expansión de NSTextViews
osx-lion (4)
Mi aplicación consta de un NSScrollView
cuya vista de documento contiene varios NSTextViews
apilados verticalmente, cada uno de los cuales cambia de tamaño en la dirección vertical a medida que se agrega texto.
Actualmente, todo esto está gestionado en código. Las NSTextViews
tamaño automáticamente, pero observo que NSViewFrameDidChangeNotification
tamaño con un NSViewFrameDidChangeNotification
, recalculan todos sus orígenes para que no se superpongan y redimensionan su superview (la vista de documentos de la vista de desplazamiento) para que todos se ajusten y puedan desplazarse.
¡Esto parece como si fuera el candidato perfecto para el autolayout! Establecí NSLayoutConstraints
entre la primera vista de texto y su contenedor, la última vista de texto y su contenedor, y cada vista de texto entre sí. Luego, si cualquier vista de texto crece, automáticamente "empuja hacia abajo" los orígenes de las vistas de texto que se encuentran debajo para satisfacer las restricciones, aumentando el tamaño de la vista del documento, ¡y todos están contentos!
¿Excepto que parece que no hay manera de hacer que un NSTextView
crezca automáticamente a medida que se agrega texto en un diseño basado en restricciones? Usando exactamente el mismo NSTextView
que se expandió automáticamente a medida que se ingresaba el texto antes, si no especifico una restricción para su altura, el valor predeterminado es 0 y no se muestra. Si especifico una restricción, incluso una desigualdad como> = 20, permanece atascada en ese tamaño y no crece a medida que se agrega texto.
Sospecho que esto tiene que ver con la implementación de -intrinsicContentSize
de -intrinsicContentSize
, que por defecto devuelve (NSViewNoInstrinsicMetric, NSViewNoInstrinsicMetric)
.
Entonces, mis preguntas: si las subclases NSTextView
devuelven un NSTextView
de contenido intrinsicContentSize
más significativo basado en el diseño de mi texto, ¿mi reproducción automática funcionará como se espera?
¿Algún indicador sobre la implementación de intrinsicContentSize
para un redimensionamiento vertical de NSTextView?
Aquí es cómo hacer un NSTextView en expansión utilizando Auto Layout, en Swift 3
- Anchors para Auto Disposición
- Utilice
textDidChange
deNSTextDelegate
.NSTextViewDelegate
ajusta aNSTextDelegate
La idea es que
textView
tiene restricciones deedges
, lo que significa que cada vez que cambie suscrollView
intrinsicContentSize
, expandirá su padre, que esscrollView
import Cocoa import Anchors class TextView: NSTextView { override var intrinsicContentSize: NSSize { guard let manager = textContainer?.layoutManager else { return .zero } manager.ensureLayout(for: textContainer!) return manager.usedRect(for: textContainer!).size } } class ViewController: NSViewController, NSTextViewDelegate { @IBOutlet var textView: NSTextView! @IBOutlet weak var scrollView: NSScrollView! override func viewDidLoad() { super.viewDidLoad() textView.delegate = self activate( scrollView.anchor.top.constant(100), scrollView.anchor.paddingHorizontally(30) ) activate( textView.anchor.edges ) } // MARK: - NSTextDelegate func textDidChange(_ notification: Notification) { guard let textView = notification.object as? NSTextView else { return } print(textView.intrinsicContentSize) textView.invalidateIntrinsicContentSize() } }
Clase lista para copiar y pegar. Swift 4.2, macOS 10.14
class HuggingTextView: NSTextView, NSTextViewDelegate {
//MARK: - Initialization
override init(frame: NSRect) {
super.init(frame: frame)
delegate = self
}
override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) {
super.init(frame: frameRect, textContainer: container)
delegate = self
}
required init?(coder: NSCoder) {
super.init(coder: coder)
delegate = self
}
//MARK: - Overriden
override var intrinsicContentSize: NSSize {
guard let container = textContainer, let manager = container.layoutManager else {
return super.intrinsicContentSize
}
manager.ensureLayout(for: container)
return manager.usedRect(for: container).size
}
//MARK: - NSTextViewDelegate
func textDidChange(_ notification: Notification) {
invalidateIntrinsicContentSize()
}
}
Estoy trabajando en una configuración muy similar: una pila vertical de vistas que contienen vistas de texto que se expanden para ajustarse a su contenido de texto y usan la reproducción automática.
Hasta ahora he tenido que subclasificar NSTextView
, que no se siente limpio, pero funciona de manera excelente en la práctica:
- (NSSize) intrinsicContentSize {
NSTextContainer* textContainer = [self textContainer];
NSLayoutManager* layoutManager = [self layoutManager];
[layoutManager ensureLayoutForTextContainer: textContainer];
return [layoutManager usedRectForTextContainer: textContainer].size;
}
- (void) didChangeText {
[super didChangeText];
[self invalidateIntrinsicContentSize];
}
El tamaño inicial de la vista de texto cuando se agrega con addSubview
es, curiosamente, no el tamaño intrínseco; Todavía no he descubierto cómo emitir la primera invalidación (enganchar viewDidMoveToSuperview
no ayuda), pero estoy seguro de que lo resolveré con el tiempo.
Tuve un problema similar con un NSTextField, y resultó que se debía a que la vista quería abrazar su contenido de texto firmemente a lo largo de la orientación vertical. Por lo tanto, si establece la prioridad de aceptación de contenido en un valor inferior a las prioridades de sus otras restricciones, puede funcionar. P.ej:
[textView setContentHuggingPriority:NSLayoutPriorityFittingSizeCompression-1.0 forOrientation:NSLayoutConstraintOrientationVertical];
Y en Swift, esto sería:
setContentHuggingPriority(NSLayoutConstraint.Priority.fittingSizeCompression, for:NSLayoutConstraint.Orientation.vertical)