添加停靠在超级视图底部的子视图(自定义键盘)

Add subview that's docked to the bottom of superview (custom keyboard)

我正在尝试创建一个看起来与键盘完全一样的自定义视图,但我不知道如何使用框架操作and/or 程序化自动布局来解决我的问题。

一些上下文:我有一个消息传递应用程序样式的视图控制器,带有停靠在屏幕底部的文本视图和按钮。所有视图都被包装到一个漂亮的单一内容视图中,并设置了自动布局,这样当键盘出现时,它会将整个视图向上推,而当它消失时,它将整个视图向下推。这是我要重现的行为。

我试图手动调整框架的大小,类似于我的键盘代码,但最终放弃了它,转而采用基于自动布局的解决方案。这是我目前拥有的:

    StickersCollectionViewController *stickerController = [self.storyboard instantiateViewControllerWithIdentifier:@"StickersCollectionViewController"];

[self addChildViewController:stickerController];
[self.view addSubview:stickerController.view];
[stickerController didMoveToParentViewController:self];

NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
[self.view addConstraint:constraint];

NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeWidth
                             relatedBy:NSLayoutRelationEqual toItem:self.view
                             attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0];

NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:stickerController.view
                                                   attribute:NSLayoutAttributeHeight
                                                   relatedBy:NSLayoutRelationEqual
                                                      toItem:nil
                                                   attribute:NSLayoutAttributeNotAnAttribute
                                                  multiplier:1.0
                                                    constant:240.0];

[self.view addConstraint:width];
[self.view addConstraint:height];


double delayInSeconds = 0.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    constraint.constant = 240.0;
    [UIView animateWithDuration:0.3 
                     animations:^{
                         [self.view layoutIfNeeded];
                     }];
});

到目前为止,这看起来很棒:视图被实例化并添加到屏幕外,然后以动画形式进入视图。但是,我也希望我的超级视图(上面提到的)也能用这个视图进行动画处理。这是我需要帮助的部分。

任何人都可以在这方面提供帮助吗?或者就我可以走的不同路线提出建议?谢谢

试试这个,区别在于使用“[self.view layoutIfNeeded];”动画之前和动画之后,然后将这个 "constraint.constant = 240.0;" 放在动画中。

double delayInSeconds = 0.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
  [self.view layoutIfNeeded];
        [UIView animateWithDuration:0.3 
                         animations:^{
                             constraint.constant = 240.0;
                             [self.view layoutIfNeeded];
                         }];
    });

所以,也试试这个:

这个:

NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];

应该是这样的:

NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];

因为你约束到视图的顶部而不是约束到视图的顶部+ self.view.bounds.size.height,不确定这是否有帮助,但这是一个想法。

第二种可能的解决方案是,将所有 UI 元素包装到一个 UIView 并将此 UIView 设置为 stickerController.view 的视图,这是如此直接,因为从技术上讲,您应该在 stickerController 实现文件中的加载视图方法中将此视图指定为 stickerController.view 的视图,然后键入 stickerController.view,例如

在 stickerController 的实现文件中打字

- (void)loadView
{
    [self setView:[ContainerViewThatHasSubViews new]];
}

- (ContainerViewThatHasSubViews*)contentView
{
    return (id)[self view];
}

然后,像这样创建一个 ContainerViewThatHasSubViews 的子class

ContainerViewThatHasSubViews.h

@interface ContainerViewThatHasSubViews : UIView
@property (nonatomic) UIView *subContainerOfContainerViewThatHasSubViews;
@property (nonatomic) NSLayoutConstraint *tester;
@end

ContainerViewThatHasSubViews.m

@interface ContainerViewThatHasSubViews ()
@end

@implementation ContainerViewThatHasSubViews {
}
    self = [super initWithFrame:frame];
    if (self) {

        _subContainerOfContainerViewThatHasSubViews = [UIView new];
        [_subContainerOfContainerViewThatHasSubViews setTranslatesAutoresizingMaskIntoConstraints:false];
        [self addSubview:subContainerOfContainerViewThatHasSubViews];

        /// PSUEDO CODE HERE NOW
        ALL OTHER UI ELEMENTS ARE ADDED TO THE UIVIEW "subContainerOfContainerViewThatHasSubViews"

        like this *** [subContainerOfContainerViewThatHasSubViews addSubView:**another ui element***];***

        etc. etc. etc.

        then use layout constraints like this:

        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
        [subContainerOfContainerViewThatHasSubViews.view addConstraint:constraint];

        etc. etc., so the layout constraints are added to "subContainerOfContainerViewThatHasSubViews"

        add separate constraints to this UIView for the items inside this view, and you can then animate these cosntraitns as well by declaring the constraints in your header file as propertyies like i did with the _tester constraint, you can animate these when you press a button in your UIViewController impelmentation file by addding a gesture recognizer or whatever you have to show the keyboard

        then end with this:

        NSDictionary* views = NSDictionaryOfVariableBindings(subContainerOfContainerViewThatHasSubViews);
        NSDictionary* metrics = @{@"sp" : @(heightResizer(10)), @"sh" : @40}; //include sizing metrics here

        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_subContainerOfContainerViewThatHasSubViews]|" options:0 metrics:metrics views:views]];

        and this:

        _tester =[NSLayoutConstraint constraintWithItem:_subContainerOfContainerViewThatHasSubViews attribute:NSLayoutAttributeCenterTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterTop multiplier:1.0f constant:0.0f];


        [self addConstraint:_tester];
    }
    return self;
}
@end

要点:

https://gist.github.com/anonymous/c601481d24ad1b98b219

https://gist.github.com/anonymous/b22b68d4bf8d7fa51d66

https://gist.github.com/anonymous/a9aaf922e0f5383256b6

https://gist.github.com/anonymous/fc6655ea8200cda9c0dd

这个有很多内容,但这就是程序化视图的工作方式。我从来没有使用故事板,因为我觉得我可以更好地控制以编程方式做所有事情,如果你能处理你需要编写多少代码来实现这一点,那么你就可以开始了,我知道这一点,因为我是 b ui开发一个像您一样的应用程序,它可以执行您希望您的应用程序执行的相同操作,这对我有用,因为我正在深入挖掘视图。这是完全控制,只需确保将其他 NSLayoutContraints 设置为 subclassed UIView 的属性,然后就可以在 subclassed UI 中放置方法]View 的 header 文件指向 UIView subclass 的实现文件,这些方法可以是您可以从视图控制器调用的动画方法,如下所示:

[[self contentView] CALLYOURFUNCTION];

访问其他布局约束和 ui 子class 视图控制器中的 UI 视图中的元素,执行此操作以调用它们:

[[self contentView] tester]; //this is the constraint from9 the header file of tbhe custom UIView

要在 UIViewController 中设置常量,请执行以下操作:

[[[self contentView] tester] setConstant:A_NUMBER];

重点是,您将所有视图封装到 UIView subclass 而不是访问视图控制器本身的视图并尝试为其视图设置动画。我从未见过有人尝试为 UIViewController 本身的 "view" 属性 制作动画,我只看过 UIView 的动画那是 UIViewController 视图的子视图。重点还在于,您强制此 UIView 成为 UIViewControllers 视图的视图,然后您在子类 [=] 中使用另一个 UIView 80=]View 为视图的整个 Contents 设置动画,所以它是这样的:

 -UIViewController's view
     Sub Class of UIView
        UIView <=== this is the view that contains ALL THE OTHER UIElements of the View controller

向包含所有其他 UI 元素的 UIView 添加约束,此约束是我添加到自定义 Sub header 的测试器约束 class UI查看。

然后您可以向我提到的 UI 视图的子视图添加额外的约束,其中包含 UIViewController 的所有其他 UI 元素

当您对这些子项添加约束时,您也可以为它们设置动画。这个方法比较高级,有问题就问吧,我的回答有点乱。

daddy warbucks 的回答确实让我接近了我正在寻找的解决方案。对于那些已经使用故事板构建了大部分 UI 骨架的人,我最终在我的视图控制器底部添加了一个容器视图,并在按下按钮时修改了主要内容视图和容器视图的中心值.

  1. 像往常一样布局内容视图。为内容视图设置 Leading、Trailing 和 Top 约束,为容器视图设置 Bottom 约束(在步骤 2 中描述)。

  2. 创建容器视图并将其停靠在内容视图下方。将内容视图的宽度设置为相等,将顶部约束设置为内容视图的底部,并将高度设置为 240(或您希望的任何值)。注意:视图将位于视图控制器之外。如果你像我一样有轻微的强迫症,这会让你有点困扰,直到你意识到它是 k.

  3. 这是我用来将容器视图移动到位并让内容视图随之移动的方法。请注意,我可能会进一步清理它,但这是一般的想法:

    if (_isStickersViewOpen) {
        self.tableView.contentInset = UIEdgeInsetsZero;
        self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
    
        [UIView animateWithDuration:0.3
                          delay:0.0
                        options:UIViewAnimationOptionCurveEaseOut
                     animations:^{
                         self.contentView.center = CGPointMake(self.scrollView.center.x, self.scrollView.center.y);
                         self.containerView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height + self.containerView.bounds.size.height / 2);
                     }
                     completion:^(BOOL finished){
                         _isStickersViewOpen = NO;
                     }];
    } else {
       UIEdgeInsets contentInsets = UIEdgeInsetsMake(self.containerView.frame.size.height, 0.0, 0.0, 0.0);
    
       self.tableView.contentInset = contentInsets;
       self.tableView.scrollIndicatorInsets = contentInsets;
    
       [UIView animateWithDuration:0.3
                          delay:0.0
                        options:UIViewAnimationOptionCurveEaseOut
                     animations:^{
                         self.contentView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height / 2 - self.containerView.bounds.size.height);
                         self.containerView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height - self.containerView.bounds.size.height / 2);
                     }
                     completion:^(BOOL finished){
                         _isStickersViewOpen = YES;
                     }];
    }
    

我并不是说这是最有效或最干净的解决方案,但它适合我的目的。