topanchor create constraint code objective-c autolayout nsview

objective c - create - Reemplazando NSView mientras se mantienen las restricciones de autolayout



programmatically constraints swift 4 (4)

Quiero reemplazar un NSView a otra vista mientras NSView las restricciones.

Tengo una superview , una subview ya que es un elemento secundario y un placeholder que planeo mover al lugar de la subvista. Pero parece que el código

[[superview] replaceSubview:subview with:placeholder];

descarta todas las restricciones relacionadas con la subview y los resultados al eliminar la subview .

¿Cómo se pueden "copiar" las restricciones de una vista a otra?


Básicamente terminé lo que Brad Allred sugirió, construyendo sobre su código. La siguiente categoría hace lo que hizo la pregunta original. Solo probado en un caso de uso hasta el momento :) Asume ARC.

@interface NSView (SSYAutoLayout) /*! @brief Replaces a given subview of the receiver with another given view, without changing the layout of the receiver (superview) @details This method is handy for replacing placeholder views with real views. It will transfer both the frame and the Auto Layout constraints, so it works whether or not Auto Layout is in use. It is a wrapper around -[NSView replaceSubview:with:]. @param newView The view to replace the old view. It is assumed that this view currently has no constraints. @param oldView The view to be replaced. All we do with this is remove it from the superview. We do not remove any of its constraints. That should be fine if you are going to discard this view. */ - (void)replaceKeepingLayoutSubview:(NSView *)oldView with:(NSView *)newView ; @end @implementation NSView (SSYAutoLayout) - (void)replaceKeepingLayoutSubview:(NSView *)oldView with:(NSView *)newView { /* Remember Auto Layout constraints. There are two objects which may be "holding" relevant constraints. First, the superview of the old view may hold constraints that refer to old view. We call these "relevant superview constraints". Second, the old view can hold constraints upon itself. We call these the "self constraints". The following code remembers each in turn. */ NSMutableArray* oldRelevantSuperviewConstraints = [NSMutableArray new] ; NSMutableArray* newRelevantSuperviewConstraints = [NSMutableArray new] ; for (NSLayoutConstraint* constraint in self.constraints) { BOOL isRelevant = NO ; NSView* new1stItem ; NSView* new2ndItem ; if (constraint.firstItem == oldView) { isRelevant = YES ; new1stItem = newView ; } if (constraint.secondItem == oldView) { isRelevant = YES ; new2ndItem = newView ; } if (isRelevant) { NSLayoutConstraint* newConstraint = [NSLayoutConstraint constraintWithItem:(new1stItem ? new1stItem : constraint.firstItem) attribute:constraint.firstAttribute relatedBy:constraint.relation toItem:(new2ndItem ? new2ndItem : constraint.secondItem) attribute:constraint.secondAttribute multiplier:constraint.multiplier constant:constraint.constant] ; newConstraint.shouldBeArchived = constraint.shouldBeArchived ; newConstraint.priority = constraint.priority ; [oldRelevantSuperviewConstraints addObject:constraint] ; [newRelevantSuperviewConstraints addObject:newConstraint] ; } } NSMutableArray* newSelfConstraints = [NSMutableArray new] ; for (NSLayoutConstraint* constraint in oldView.constraints) { // WARNING: do not tamper with intrinsic layout constraints if ([constraint class] == [NSLayoutConstraint class] && constraint.firstItem == oldView) { NSView* new1stItem ; NSView* new2ndItem ; if (constraint.firstItem == oldView) { new1stItem = newView ; } if (constraint.secondItem == oldView) { new2ndItem = newView ; } NSLayoutConstraint* newConstraint = [NSLayoutConstraint constraintWithItem:(new1stItem ? new1stItem : constraint.firstItem) attribute:constraint.firstAttribute relatedBy:constraint.relation toItem:(new2ndItem ? new2ndItem : constraint.secondItem) attribute:constraint.secondAttribute multiplier:constraint.multiplier constant:constraint.constant] ; newConstraint.shouldBeArchived = constraint.shouldBeArchived ; newConstraint.priority = constraint.priority ; [newSelfConstraints addObject:newConstraint] ; } } /* Remember the old frame, in case Auto Layout is not being used. */ NSRect frame = oldView.frame ; /* Do the replacement. */ [self replaceSubview:oldView with:newView] ; /* Replace frame and constraints. */ newView.frame = frame ; [newView addConstraints:newSelfConstraints] ; [self removeConstraints:oldRelevantSuperviewConstraints] ; [self addConstraints:newRelevantSuperviewConstraints] ; } @end


Aquí hay un código que escribí hace mucho tiempo para hacer lo que me pides.

Mi código es para intercambiar dos NSViews dentro de la misma supervista, pero puedes adaptarlo fácilmente para reemplazarlo eliminando los bits innecesarios y haciendo la adición / eliminación de la vista / restricción en un orden cuidadoso. De hecho, tengo una versión más corta de este código en una clase de controlador de vista "proxy" que hace exactamente lo que usted, pero no puedo compartir porque es un proyecto propietario que no me pertenece.

Le diré que lo que necesita hacer es copiar las restricciones de la vista de proxy a la nueva vista y luego agregar la nueva vista a la supervista. Después de eso, copie las restricciones de superview para el proxy a la nueva vista y solo después de hacerlo elimine la vista de proxy de la supervista.

- (void)swapView:(NSView*) source withView:(NSView*) dest persist:(BOOL) persist { NSLog(@"swapping %@ with %@", source.identifier, dest.identifier); // !!!: adjust the "Auto Layout" constraints for the superview. // otherwise changing the frames is impossible. (instant reversion) // we could disable "Auto Layout", but let''s try for compatibility // TODO: we need to either enforce that the 2 controls have the same superview // before accepting the drag operation // or modify this code to take two diffrent superviews into account // we are altering the constraints so iterate a copy! NSArray* constraints = [dest.superview.constraints copy]; for (NSLayoutConstraint* constraint in constraints) { id first = constraint.firstItem; id second = constraint.secondItem; id newFirst = first; id newSecond = second; BOOL match = NO; if (first == dest) { newFirst = source; match = YES; } if (second == dest) { newSecond = source; match = YES; } if (first == source) { newFirst = dest; match = YES; } if (second == source) { newSecond = dest; match = YES; } if (match && newFirst) { [dest.superview removeConstraint:constraint]; @try { NSLayoutConstraint* newConstraint = nil; newConstraint = [NSLayoutConstraint constraintWithItem:newFirst attribute:constraint.firstAttribute relatedBy:constraint.relation toItem:newSecond attribute:constraint.secondAttribute multiplier:constraint.multiplier constant:constraint.constant]; newConstraint.shouldBeArchived = constraint.shouldBeArchived; newConstraint.priority = NSLayoutPriorityWindowSizeStayPut; [dest.superview addConstraint:newConstraint]; } @catch (NSException *exception) { NSLog(@"Constraint exception: %@/nFor constraint: %@", exception, constraint); } } } [constraints release]; NSMutableArray* newSourceConstraints = [NSMutableArray array]; NSMutableArray* newDestConstraints = [NSMutableArray array]; // again we need a copy since we will be altering the original constraints = [source.constraints copy]; for (NSLayoutConstraint* constraint in constraints) { // WARNING: do not tamper with intrinsic layout constraints if ([constraint class] == [NSLayoutConstraint class] && constraint.firstItem == source) { // this is a source constraint. we need to copy it to the destination. NSLayoutConstraint* newConstraint = nil; newConstraint = [NSLayoutConstraint constraintWithItem:dest attribute:constraint.firstAttribute relatedBy:constraint.relation toItem:constraint.secondItem attribute:constraint.secondAttribute multiplier:constraint.multiplier constant:constraint.constant]; newConstraint.shouldBeArchived = constraint.shouldBeArchived; [newDestConstraints addObject:newConstraint]; [source removeConstraint:constraint]; } } [constraints release]; // again we need a copy since we will be altering the original constraints = [dest.constraints copy]; for (NSLayoutConstraint* constraint in constraints) { // WARNING: do not tamper with intrinsic layout constraints if ([constraint class] == [NSLayoutConstraint class] && constraint.firstItem == dest) { // this is a destination constraint. we need to copy it to the source. NSLayoutConstraint* newConstraint = nil; newConstraint = [NSLayoutConstraint constraintWithItem:source attribute:constraint.firstAttribute relatedBy:constraint.relation toItem:constraint.secondItem attribute:constraint.secondAttribute multiplier:constraint.multiplier constant:constraint.constant]; newConstraint.shouldBeArchived = constraint.shouldBeArchived; [newSourceConstraints addObject:newConstraint]; [dest removeConstraint:constraint]; } } [constraints release]; [dest addConstraints:newDestConstraints]; [source addConstraints:newSourceConstraints]; // auto layout makes setting the frame unnecissary, but // we do it because its possible that a module is not using auto layout NSRect srcRect = source.frame; NSRect dstRect = dest.frame; // round the coordinates!!! // otherwise we will have problems with persistant values srcRect.origin.x = round(srcRect.origin.x); srcRect.origin.y = round(srcRect.origin.y); dstRect.origin.x = round(dstRect.origin.x); dstRect.origin.y = round(dstRect.origin.y); source.frame = dstRect; dest.frame = srcRect; if (persist) { NSString* rectString = NSStringFromRect(srcRect); [[_theme prefrences] setObject:rectString forKey:dest.identifier]; rectString = NSStringFromRect(dstRect); [[_theme prefrences] setObject:rectString forKey:source.identifier]; } }

puedes ignorar los bits sobre la persistencia en tu caso, imagino. En mi caso, quería implementar la funcionalidad del trampolín de iOS (poder pulsar y mantener presionado un botón, se agita, dejar que lo arrastre a otro botón y cambiar lugares mientras persista entre los lanzamientos)


En algunos casos, el método de subvista es más simple de implementar. Especialmente si tiene una vista detallada que cambia dependiendo de algunos datos.

En la ubicación donde planea mostrar las diferentes vistas de detalles, agregue una vista personalizada vacía y agregue restricciones para mantenerla en la ubicación correcta.

Crear controladores de vista para todas las vistas de detalle. Para cambiar la vista, use este código:

id displayedObject = ...; NSView *newDetailView = nil; if ([displayedObject isKindOfClass:[ClassA class]]) { _viewControllerA.representedObject = displayedObject newDetailView = _viewControllerA.view; } else { _viewControllerB.representedObject = displayedObject; newDetailView = _viewControllerB.view; } if (_currentDetailView != newDetailView) { _currentDetailView = newDetailView; for (NSView *subview in self.detailViewPlaceholder.subviews) { [subview removeFromSuperview]; } newDetailView.frame = self.detailViewPlaceholder.frame; [self.detailViewPlaceholder addSubview:newDetailView]; [self.detailViewPlaceholder addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[newDetailView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(newDetailView)]]; [self.detailViewPlaceholder addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[newDetailView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(newDetailView)]]; }

Utiliza una sola subvista como marcador de posición que llena la vista del marcador de posición de borde a borde.


Otro enfoque es reemplazar la vista en una vista de contenedor (y no estoy hablando necesariamente de la vista de inserción de contenedor incrustado que ve en IB, pero podría ser simplemente una NSView que contendrá la vista que se reemplaza si querer), y luego le da a ese contenedor la vista de todas las ricas restricciones que dictan la ubicación con respecto a todas las otras vistas en la supervista. De esta forma, no está lidiando con ninguna restricción complicada para la vista que se reemplaza.

A continuación, puede eliminar la antigua subvista del contenedor, agregar la nueva subvista y dar a la subvista las restricciones trivialmente simples para que aparezca en la vista del contenedor de forma adecuada:

// remove existing subview [[[self.containerView subviews] firstObject] removeFromSuperview]; // add new subview NSView *subview = [self viewTwo]; [subview setTranslatesAutoresizingMaskIntoConstraints:false]; [self.containerView addSubview:subview]; // setup constraints for new subview NSDictionary *views = NSDictionaryOfVariableBindings(subview); [self.containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[subview]|" options:0 metrics:nil views:views]]; [self.containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[subview]|" options:0 metrics:nil views:views]];

Con este proceso, evita reconstruir cualquier restricción complicada que haya dictado previamente la relación de la vista reemplazada con todos sus pares anteriores en la jerarquía de vistas.