Here is some code I wrote a long time ago to do what you ask.
My code is for swapping two NSViews within the same superview, but you can easily adapt it for replacement by stripping out the unneeded bits and doing view/constraint addition and removal in a careful order. In fact I have a shorter version of this code in a "proxy" view controller class that does exactly what you, but I cannot share it because it is a proprietary project that doesn't belong to me.
I will tell you that what you need to do is copy the constraints from the proxy view to the new view then add the new view to the superview. After that copy the superview constraints for the proxy to the new view and only after you do that remove the proxy view from the superview.
- (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];
}
}
you can safely ignore the bits about persistence in your case I imagine. In my case I wanted to implement the iOS springboard functionality (being able to tap-and-hold a button, it jiggles, let me drag it to another button and swap places while persisting between launches)