Ответ 1
Вот код, который я написал давно, чтобы сделать то, что вы просите.
Мой код предназначен для замены двух NSView в пределах одного супервизора, но вы можете легко адаптировать его для замены, удалив ненужные биты и сделав добавление и удаление вида/ограничения в тщательном порядке. На самом деле у меня есть более короткая версия этого кода в классе контроллера "прокси", который делает именно то, что вы, но я не могу поделиться им, потому что это собственный проект, который не принадлежит мне.
Я расскажу вам, что вам нужно скопировать ограничения с прокси-представления на новое представление, а затем добавить новое представление в супервизор. После этого скопируйте ограничения супервизора для прокси на новое представление и только после этого удалите прокси-представление из супервизора.
- (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 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];
}
}
вы можете смело игнорировать бит о настойчивости в своем случае, я думаю. В моем случае я хотел реализовать функциональность трамплина iOS (возможность нажать и удерживать кнопку, она смещается, позвольте мне перетащить ее на другую кнопку и поменять местами, сохраняя при этом между запусками)