简体   繁体   English

键盘处理就像iOS 7中的Messages app一样

[英]Keyboard handling just like in Messages app in iOS 7

I am implementing a view that is in some way similar to what happens in Messages app, so there is a view with UITextView attached to the bottom of the screen and there is also UITableView showing the main content. 我正在实现一个类似于Messages应用程序中发生的视图,因此有一个视图,UITextView附加到屏幕的底部,还有UITableView显示主要内容。 When it is tapped it slides up with the keyboard and when keyboard is dismissed it slides back to the bottom of the screen. 当它被轻敲时,它会随着键盘向上滑动,当键盘被解除时,它会滑回到屏幕的底部。

That part I have and it is working perfectly - I just subscribed to keyboard notifications - will hide and wil show. 我有这个部分并且它完美地工作 - 我只是订阅键盘通知 - 将隐藏和显示。

The problem is that I have set keyboard dismiss mode on UITableView to interactive and I cannot capture changes to keyboard when it is panning. 问题是我已经在UITableView上将键盘解除模式设置为交互式,并且我无法在平移时捕获对键盘的更改。

The second problem is that this bar with uitextview is covering some part of uitableview. 第二个问题是这个uitextview栏覆盖了uitableview的某些部分。 How to fix this? 如何解决这个问题? I still want the uitableview to be "under" this bar just like in messages app. 我仍然希望uitableview像在消息应用程序中一样“在”这个栏下面。

I am using AutoLayout in all places. 我在所有地方都使用AutoLayout。

Any help will be appreciated! 任何帮助将不胜感激!

============ ============

EDIT1: Here is some code: EDIT1:这是一些代码:

View Hierarchy is as follows: 查看层次结构如下:

View - UITableView (this one will contain "messages") - UIView (this one will slide) 视图 - UITableView(这个将包含“消息”) - UIView(这个将滑动)

UITableView is has constraints to top, left, right and bottom of parent view so it fills whole screen. UITableView对父视图的顶部,左侧,右侧和底部有约束,因此它填满整个屏幕。 UIView has constraints to left, right and bottom of parent view so it is glued to the bottom - I moved it by adjusting constant on constraint. UIView对父视图的左侧,右侧和底部有约束,因此它粘在底部 - 我通过调整约束的常量来移动它。

In ViewWillAppear method: 在ViewWillAppear方法中:

NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.DidShowNotification, OnKeyboardDidShowNotification);
NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillChangeFrameNotification, OnKeyboardDidShowNotification);
NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillHideNotification, OnKeyboardWillHideNotification);

And here are methods: 以下是方法:

void OnKeyboardDidShowNotification (NSNotification notification)
{
    AdjustViewToKeyboard (Ui.KeyboardHeightFromNotification (notification), notification);
}

void OnKeyboardWillHideNotification (NSNotification notification)
{   
    AdjustViewToKeyboard (0.0f, notification);
}

void AdjustViewToKeyboard (float offset, NSNotification notification = null)
{
    commentEditViewBottomConstraint.Constant = -offset;

    if (notification != null) {
        UIView.BeginAnimations (null, IntPtr.Zero);
        UIView.SetAnimationDuration (Ui.KeyboardAnimationDurationFromNotification (notification));
        UIView.SetAnimationCurve ((UIViewAnimationCurve)Ui.KeyboardAnimationCurveFromNotification (notification));
        UIView.SetAnimationBeginsFromCurrentState (true);
    }

    View.LayoutIfNeeded ();
    commentEditView.LayoutIfNeeded ();

    var insets = commentsListView.ContentInset;
    insets.Bottom = offset;
    commentsListView.ContentInset = insets;

    if (notification != null) {
        UIView.CommitAnimations ();
    }
}

I'd recommend you to override -inputAccessoryView property of your view controller and have your editable UITextView as its subview. 我建议您覆盖视图控制器的-inputAccessoryView属性,并将可编辑的UITextView作为其子视图。 Also, don't forget to override -canBecomeFirstResponder method to return YES. 另外,不要忘记覆盖-canBecomeFirstResponder方法以返回YES。

- (BOOL)canBecomeFirstResponder
{
if (!RUNNING_ON_IOS7 && !RUNNING_ON_IPAD)
{
    //Workaround for iOS6-specific bug
    return !(self.viewDisappearing) && (!self.viewAppearing);
}

return !(self.viewDisappearing);
}

With this approach system manages everything. 通过这种方法,系统管理一切。

There are also some workarounds you must know about: for UISplitViewController ( UISplitViewController detail-only inputAccessoryView ), for deallocation bugs ( UIViewController with inputAccessoryView is not deallocated ) and so on. 还有一些你必须了解的解决方法:对于UISplitViewController( UISplitViewController仅详细信息的inputAccessoryView ),用于释放错误( UIViewController,而inputAccessoryView没有被释放 )等等。

This solution is based on a lot of different answers on SO. 该解决方案基于SO的许多不同答案。 It have a lot of benefits: 它有很多好处:

  • Compose bar stays on bottom when keyboard is hidden 键盘隐藏时,撰写栏保持在底部
  • Compose bas follows keyboard while interactive gesture on UITableView UITableView上进行交互式手势时,键盘跟随键盘
  • UITableViewCell s are going from bottom to top, like in Messages app UITableViewCell从下到上,就像在消息应用程序中一样
  • Keyboard do not prevent to see all UITableViewCell s 键盘不会阻止查看所有UITableViewCell
  • Should work for iOS6, iOS7 and iOS8 适用于iOS6,iOS7和iOS8

This code just works: 这段代码正常工作:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = // . . .

    // . . .

    cell.contentView.transform = CGAffineTransformMakeScale(1,-1);
    cell.accessoryView.transform = CGAffineTransformMakeScale(1,-1);
    return cell;
}

- (UIView *)inputAccessoryView {
    return self.composeBar;
}

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.tableView.transform = CGAffineTransformMakeScale(1,-1);

    // This code prevent bottom inset animation while appearing view
    UIEdgeInsets newEdgeInsets = self.tableView.contentInset;
    newEdgeInsets.top = CGRectGetMaxY(self.navigationController.navigationBar.frame);
    newEdgeInsets.bottom = self.view.bounds.size.height - self.composeBar.frame.origin.y;
    self.tableView.contentInset = newEdgeInsets;
    self.tableView.scrollIndicatorInsets = newEdgeInsets;
    self.tableView.contentOffset = CGPointMake(0, -newEdgeInsets.bottom);

    // This code need to be done if you added compose bar via IB
    self.composeBar.delegate = self;
    [self.composeBar removeFromSuperview];

    [[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillChangeFrameNotification object:nil queue:nil usingBlock:^(NSNotification *note)
    {
        NSNumber *duration = note.userInfo[UIKeyboardAnimationDurationUserInfoKey];
        NSNumber *options = note.userInfo[UIKeyboardAnimationCurveUserInfoKey];
        CGRect beginFrame = [note.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
        CGRect endFrame = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];

        UIEdgeInsets newEdgeInsets = self.tableView.contentInset;
        newEdgeInsets.bottom = self.view.bounds.size.height - endFrame.origin.y;
        CGPoint newContentOffset = self.tableView.contentOffset;
        newContentOffset.y += endFrame.origin.y - beginFrame.origin.y;

        [UIView animateWithDuration:duration.doubleValue
                              delay:0.0
                            options:options.integerValue << 16
                         animations:^{
                             self.tableView.contentInset = newEdgeInsets;
                             self.tableView.scrollIndicatorInsets = newEdgeInsets;
                             self.tableView.contentOffset = newContentOffset;
                         } completion:^(BOOL finished) {
                             ;
                         }];
    }];
}

Use for example pod 'PHFComposeBarView' compose bar: 例如使用pod 'PHFComposeBarView'撰写栏:

@property (nonatomic, strong) IBOutlet PHFComposeBarView *composeBar;

And use this class for your table view: 并将此类用于表视图:

@interface InverseTableView : UITableView
@end
@implementation InverseTableView
void swapCGFLoat(CGFloat *a, CGFloat *b) {
    CGFloat tmp = *a;
    *a = *b;
    *b = tmp;
}
- (UIEdgeInsets)contentInset {
    UIEdgeInsets insets = [super contentInset];
    swapCGFLoat(&insets.top, &insets.bottom);
    return insets;
}
- (void)setContentInset:(UIEdgeInsets)contentInset {
    swapCGFLoat(&contentInset.top, &contentInset.bottom);
    [super setContentInset:contentInset];
}
@end

If you would like keyboard to disappear by tapping on message: 如果您希望通过点击消息来消除键盘:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self.composeBar.textView resignFirstResponder];
}

Do not call this, this will hide composeBar at all: 不要打电话给这个,这将隐藏composeBar

[self resignFirstResponder];

UPDATE 2: 更新2:

NEW SOLUTION for keyboard tracking works much better: 键盘跟踪的新解决方案效果更好:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Compose view height growing tracking
    [self.composeBar addObserver:self forKeyPath:@"frame" options:0 context:nil];
    // iOS 7 keyboard tracking
    [self.composeBar.superview addObserver:self forKeyPath:@"center" options:0 context:nil];
    // iOS 8 keyboard tracking
    [self.composeBar.superview addObserver:self forKeyPath:@"frame" options:0 context:nil];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    [self.composeBar removeObserver:self forKeyPath:@"frame"];
    [self.composeBar.superview removeObserver:self forKeyPath:@"center"];
    [self.composeBar.superview removeObserver:self forKeyPath:@"frame"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (object == self.composeBar.superview || object == self.composeBar)
    {
        // Get all values
        CGPoint newContentOffset = self.tableView.contentOffset;
        UIEdgeInsets newEdgeInsets = self.tableView.contentInset;
        UIEdgeInsets newScrollIndicartorInsets = self.tableView.scrollIndicatorInsets;

        // Update values
        CGFloat bottomInset = self.view.bounds.size.height - [self.composeBar convertPoint:CGPointZero toView:self.view].y;
        CGFloat diff = newEdgeInsets.bottom - (bottomInset + 7);
        newContentOffset.y += diff;
        newEdgeInsets.bottom = bottomInset + 7;
        newScrollIndicartorInsets.bottom = bottomInset;

        // Set all values
        if (diff < 0 || diff > 40)
            self.tableView.contentOffset = CGPointMake(0, newContentOffset.y);
        self.tableView.contentInset = newEdgeInsets;
        self.tableView.scrollIndicatorInsets = newEdgeInsets;
    }
}

OK, the interactive keyboard dismissal will send a notification with name UIKeyboardDidChangeFrameNotification . 好的,交互式键盘解雇将发送名为UIKeyboardDidChangeFrameNotification的通知。

This can be used to move the text view while the keyboard is being dismissed interactively. 这可用于在交互式解除键盘时移动文本视图。

You are already using this but you are sending it to the OnKeyboardDidShow method. 您已经在使用它,但是您将它发送到OnKeyboardDidShow方法。

You need a third method called something like keyboardFramedDidChange . 你需要一个名为keyboardFramedDidChange的第三种方法。 This works for the hide and the show. 这适用于皮革和秀。

For the second problem, you should have your vertical constraints like this... 对于第二个问题,你应该有这样的垂直约束......

|[theTableView][theTextView (==44)]|

This will tie the bottom of the tableview to the top of the text view. 这会将tableview的底部绑定到文本视图的顶部。

This doesn't change how any of the animation works it will just make sure that the table view will show all of its contents whether the keyboard is visible or not. 这不会改变任何动画的工作方式,只是确保表视图将显示其所有内容,无论键盘是否可见。

Don't update the content insets of the table view. 不要更新表视图的内容insets。 Use the constraints to make sure the frames do not overlap. 使用约束确保框架不重叠。

PS sort out your naming conventions. PS整理你的命名约定。 Method names start with a lowercase letter. 方法名称以小写字母开头。

PPS use block based animations. PPS使用基于块的动画。

I'd try to use an empty, zero-height inputAccessoryView . 我尝试使用空的零高度inputAccessoryView The trick is to glue your text field's bottom to it when the keyboard appears, so that they'd move together. 诀窍是当键盘出现时将文本字段的底部粘到它上面,这样它们就可以一起移动。 When the keyboard is gone, you can destroy that constraint and stick to the bottom of the screen once again. 键盘消失后,您可以破坏该约束并再次粘贴到屏幕底部。

I made an open source lib for exactly this purpose. 我为此目的制作了一个开源库。 It works on iOS 7 and 8 and is set up to work as a cocoapod as well. 它适用于iOS 7和8,并且也可以作为cocoapod使用。

https://github.com/oseparovic/MessageComposerView https://github.com/oseparovic/MessageComposerView

Here's a sample of what it looks like: 以下是它的样子:

MessageComposerView

You can use a very basic init function as shown below to create it with screen width and default height eg: 您可以使用如下所示的非常基本的init函数来创建屏幕宽度和默认高度,例如:

self.messageComposerView = [[MessageComposerView alloc] init];
self.messageComposerView.delegate = self;
[self.view addSubview:self.messageComposerView];

There are several other initializers that are also available to allow you to customize the frame, keyboard offset and textview max height as well as some delegates to hook into frame changes and button clicks. 还有一些其他初始化程序可用于自定义框架,键盘偏移和textview最大高度以及一些代表以挂钩框架更改和按钮单击。 See readme for more! 请参阅自述文件了解更多!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM