我想通过在故事板中设计两个不同的界面来查看动画。当事件触发时,它会将自动布局约束(或帧)更改为目标布局。
有没有图书馆可以完成这项工作?如果不是,请阅读以下内容。
使用autolayout进行视图动画是一件容易的事。例如:
[self.view layoutIfNeeded];
[UIView animateWithDuration:5.0 animations:^{
self.heightConstraint.constant = 200
[self.view layoutIfNeeded];
}];
但是如果你想通过重置所有子视图的所有约束来改变整个布局,那么通过编程就不容易了。使用Interface Builder是使用相同视图对象构建完全不同的布局的更好选择。因为,
这是我的想法:
keypath
或viewTag
keypath
或viewTag
)替换呈现视图对象的约束。这是我的代码:
// 1. Add the keypath `view` for the viewController. Use NSSet to remove duplicated keypaths or view tags.
NSSet *keySet = [NSSet setWithArray:[keypaths arrayByAddingObject:@"view"]];
// 2. Build the dictionaries to fetch corresponding view objects in the source and destination view controllers (presenting view and template view) by a specific key (a keypath or a view tag).
NSMutableDictionary *presentingViewsForKeypaths = [NSMutableDictionary dictionaryWithCapacity:[keypaths count]];
NSMutableDictionary *templateViewsForKeypaths = [NSMutableDictionary dictionaryWithCapacity:[keypaths count]];
[keySet enumerateObjectsUsingBlock:^(id key, BOOL *stop) {
UIView *presentingViewForKeypath = nil;
UIView *templateViewForKeypath = nil;
// Fetch the view objects by the keypath or view tag.
if ([key isKindOfClass:[NSString class]]) {
presentingViewForKeypath = [self valueForKeyPath:key];
templateViewForKeypath = [viewController valueForKeyPath:key];
} else if ([key isKindOfClass:[NSNumber class]]) {
presentingViewForKeypath = [self.view viewWithTag:[key integerValue]];
templateViewForKeypath = [viewController.view viewWithTag:[key integerValue]];
} else {
NSString *errorMessage = [NSString stringWithFormat:@"keypath (%@) is not kind of NSString or NSNumber.", key];
NSAssert(nil, errorMessage);
NSLog(@"%@", errorMessage);
return;
}
// The view objects should be found in both view controllers.
if (!presentingViewForKeypath || !templateViewForKeypath) {
NSString *errorMessage = [NSString stringWithFormat:@"KeynoteAnimation warning: keypath (%@) is not found.", key];
NSAssert(nil, errorMessage);
NSLog(@"%@", errorMessage);
return;
} else if (!templateViewForKeypath) {
NSString *errorMessage = [NSString stringWithFormat:@"KeynoteAnimation warning: keypath (%@) is not found.", key];
NSAssert(nil, errorMessage);
NSLog(@"%@", errorMessage);
return;
}
// Add the view objects to the corresponding dictionaries.
presentingViewsForKeypaths[key] = presentingViewForKeypath;
templateViewsForKeypaths[key] = templateViewForKeypath;
}];
// 3. Change the constraints of the view objects for the keys.
NSSet *presentingViewsForKeypathsSet = [NSSet setWithArray:[presentingViewsForKeypaths allValues]];
NSSet *templateViewsForKeypathsSet = [NSSet setWithArray:[templateViewsForKeypaths allValues]];
NSMutableArray *keysShouldUpdateItsViewFrame = [NSMutableArray array];
[keySet enumerateObjectsUsingBlock:^(id key, BOOL *stop) {
NSLog(@"========= Remove constraints for Key: %@ ==========", key);
__block BOOL didChangeAnyConstraint = NO;
UIView *presentingView = presentingViewsForKeypaths[key];
UIView *templateView = templateViewsForKeypaths[key];
// Remove the original constraints for the view objects.
[presentingView.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
if ([presentingViewsForKeypathsSet containsObject:constraint.firstItem] &&
([presentingViewsForKeypathsSet containsObject:constraint.secondItem] || !constraint.secondItem)) {
NSLog(@"detect constraint: %@\nFind: %@ (%x) -- %@ (%x)", constraint, NSStringFromClass([constraint.firstItem class]), [constraint.firstItem hash], NSStringFromClass([constraint.secondItem class]), [constraint.secondItem hash]);
[presentingView removeConstraint:constraint];
didChangeAnyConstraint = YES;
}
}];
NSLog(@"----------- Add constraints for Key: %@ -----------", key);
// Add the new constraints for the view objects.
[templateView.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
if ([templateViewsForKeypathsSet containsObject:constraint.firstItem] &&
([templateViewsForKeypathsSet containsObject:constraint.secondItem] || !constraint.secondItem)) {
NSLog(@"detect constraint: %@\nFind: %@ (%x) -- %@ (%x)", constraint, NSStringFromClass([constraint.firstItem class]), [constraint.firstItem hash], NSStringFromClass([constraint.secondItem class]), [constraint.secondItem hash]);
if ([constraint isKindOfClass:NSClassFromString(@"NSIBPrototypingLayoutConstraint")]) {
NSLog(@"|||||||||||||||||||| WARNING ||||||||||||||||");
//return; // Return here to filter out this constraint.
}
// Create the new NSLayoutConstraint for the presenting view object.
NSArray *firstItemKeys = [templateViewsForKeypaths allKeysForObject:constraint.firstItem];
NSArray *secondItemKeys = [templateViewsForKeypaths allKeysForObject:constraint.secondItem];
id firstItemKey = [firstItemKeys count]? firstItemKeys[0]:nil;
id secondItemKey = [secondItemKeys count]? secondItemKeys[0]:nil;
id firstItem = firstItemKey? presentingViewsForKeypaths[firstItemKey]:nil;
id secondItem = secondItemKey? presentingViewsForKeypaths[secondItemKey]:nil;
NSLog(@"params for new constraints: %@:%@(%x) => %@:%@(%x) for view: %@(%x)", firstItemKey, NSStringFromClass([firstItem class]), [firstItem hash], secondItemKey, NSStringFromClass([secondItem class]), [secondItem hash], NSStringFromClass([presentingView class]), [presentingView hash]);
NSLayoutConstraint *newLayoutConstraint = [NSLayoutConstraint constraintWithItem:firstItem attribute:constraint.firstAttribute relatedBy:constraint.relation toItem:secondItem attribute:constraint.secondAttribute multiplier:constraint.multiplier constant:constraint.constant];
[presentingView addConstraint:newLayoutConstraint];
didChangeAnyConstraint = YES;
}
}];
// Record the view object which doesn't have any layout constraint. It may change its frame set in the storyboard.
if (!didChangeAnyConstraint) {
[keysShouldUpdateItsViewFrame addObject:key];
}
}];
// Start Animation
[UIView animateWithDuration:1 delay:0 usingSpringWithDamping:1 initialSpringVelocity:1 options:0 animations:^{
// Update the frame of the view objects which did not change its constraints (no constraint).
[keysShouldUpdateItsViewFrame enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) {
UIView *presentingView = presentingViewsForKeypaths[key];
UIView *templateView = templateViewsForKeypaths[key];
presentingView.frame = templateView.frame;
}];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
NSLog(@"finish.");
}];
添加新约束时,如果约束的类为NSIBPrototypingLayoutConstraint
,它将崩溃并显示消息:当添加到视图时,约束的项必须是该视图的后代(或者视图本身)。如果在组装视图层次结构之前需要解析约束,则会崩溃。
我认为视图对象已经在视图层次结构中。我不知道如何解决这个问题。
如果过滤掉这些约束,它将不会更新布局,我不确定是否满足约束。