moving a bar with UIScrollViewKeyboardDismissModeI

2019-01-31 23:10发布

问题:

I have a text field that I anchor to the top of the keyboard. I can't use inputAccessoryView since it's always shown. I'm able to observe keyboard hidden/shown notifications to move it up and down with the keyboard, but this doesn't appear to work with UIScrollViewKeyboardDismissModeInteractive. Is there a way to get constant feedback on the position of the keyboard to sync the animation?

回答1:

Edit: Looks like this does not work in iOS 8, guys -- Sorry! I'm also searching for a new solution

I solved this by creating a non-visible inputAccessoryView.

textView.inputAccessoryView = [[MJXObservingInputAccessoryView alloc] init];

The accessoryView observes its superview's frame and posts out a notification you can match.

static NSString * const MJXObservingInputAccessoryViewSuperviewFrameDidChangeNotification = @"MJXObservingInputAccessoryViewSuperviewFrameDidChangeNotification";

@interface MJXObservingInputAccessoryView : UIView @end

@implementation MJXObservingInputAccessoryView

- (void)willMoveToSuperview:(UIView *)newSuperview
{
    if (self.superview)
    {
        [self.superview removeObserver:self
                            forKeyPath:@"frame"];
    }

    [newSuperview addObserver:self
                   forKeyPath:@"frame"
                      options:0
                      context:NULL];

    [super willMoveToSuperview:newSuperview];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (object == self.superview && [keyPath isEqualToString:@"frame"])
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:MJXObservingInputAccessoryViewSuperviewFrameDidChangeNotification
                                                            object:self];
    }
}

@end


回答2:

I found a solution (albeit somewhat of a hack) where I implement scrollViewDidScroll to listen to the panGestureRecognizer built into the UITableView. It turns out that the top of the keyboard stays perfectly even with the swiping finger throughout the gesture, so you can just continuously update your textfield to stay just above the panning finger.

-(void)scrollViewDidScroll:(UIScrollView *)scrollView {

    CGPoint fingerLocation = [scrollView.panGestureRecognizer locationInView:scrollView];
    CGPoint absoluteFingerLocation = [scrollView convertPoint:fingerLocation toView:self.view];

    if (_keyboardIsOpen && scrollView.panGestureRecognizer.state == UIGestureRecognizerStateChanged && absoluteFingerLocation.y >= (self.view.frame.size.height - _keyboardFrame.size.height)) {

        [UIView animateWithDuration:.05 animations:^{
            //This is an autolayout constraint that needs to be set to the distance between the top of the keyboard and the bottom of the screen (with a buffer of 3)
            _bottomViewVerticalSpacingConstraint.constant = [[UIScreen mainScreen] bounds].size.height - absoluteFingerLocation.y - 3;
            [self.view layoutIfNeeded];
        }];
    }
}

Then I also register for Notifications

 [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShown:)
                                             name:UIKeyboardWillShowNotification object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillBeHidden:)
                                             name:UIKeyboardWillHideNotification object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillChangeFrame:)
                                             name:UIKeyboardWillChangeFrame object:nil];

And handle them like so

-(void)keyboardWillShown:(NSNotification*)aNotification
{
    _keyboardIsOpen = YES;
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    [UIView animateWithDuration:.05 animations:^{
        _bottomViewVerticalSpacingConstraint.constant = kbSize.height;
        [self.view layoutIfNeeded];
    }];
}


-(void)keyboardWillBeHidden:(NSNotification*)aNotification
{
    _keyboardIsOpen = NO;
    [UIView animateWithDuration:.3 animations:^{
        _bottomViewVerticalSpacingConstraint.constant = 0;
        [self.view layoutIfNeeded];
    }];
}

-(void)keyboardWillChangeFrame:(NSNotification*)aNotification {
    NSDictionary* info = [aNotification userInfo];
    _keyboardFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
}


回答3:

Malcolm's answer will work for iOS 8 and 7 with only a minor tweak. I do not have enough reputation to comment on his post so this is added as a community wiki for people needing a solution that works for iOS 7 and 8.

Header

#import <UIKit/UIKit.h>

static NSString *const SSObservingInputAccessoryViewFrameDidChangeNotification = @"SSObservingInputAccessoryViewFrameDidChangeNotification";

@interface SSObservingInputAccessoryView : UIView

@end

Implementation

#import "SSObservingInputAccessoryView.h"

@implementation SSObservingInputAccessoryView

- (void)willMoveToSuperview:(UIView *)newSuperview {
    if (self.superview) {
        [self.superview removeObserver:self
                            forKeyPath:@"center"];

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

    [newSuperview addObserver:self
                   forKeyPath:@"center"
                      options:0
                      context:nil];

    [newSuperview addObserver:self
                   forKeyPath:@"frame"
                      options:0
                      context:nil];

    [super willMoveToSuperview:newSuperview];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (object == self.superview
        && ([keyPath isEqualToString:@"center"] || [keyPath isEqualToString:@"frame"])) {
        [[NSNotificationCenter defaultCenter] postNotificationName:SSObservingInputAccessoryViewFrameDidChangeNotification
                                                            object:self];
    }
}

@end


回答4:

Only CALayer position is updated interactively for Swift 4 on iPhone X iOS 11.2.2:

class MyValueObservingView: UIView {
    static let CALayerPositionChangeNotification = Notification.Name("CALayerPositionChangeNotification")
    static let CALayerPositionUserInfoKey = "position"

    override func willMove(toSuperview newSuperview: UIView?) {
        superview?.layer.removeObserver(self, forKeyPath: type(of: self).CALayerPositionUserInfoKey)
        newSuperview?.layer.addObserver(self, forKeyPath: type(of: self).CALayerPositionUserInfoKey, options: [.initial, .new], context: nil)
        super.willMove(toSuperview: newSuperview)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == type(of: self).CALayerPositionUserInfoKey, let position = change?[.newKey] as? CGPoint {
//            print("MyValueObservingView layer position changed to \(position)")
            NotificationCenter.default.post(name: type(of: self).CALayerPositionChangeNotification, object: self, userInfo: [type(of: self).CALayerPositionUserInfoKey: position])
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}


回答5:

Have you tried DAKeyboardControl?

   UIView *addCommentContainer = self.addCommentContainer;
   [self.view addKeyboardPanningWithActionHandler:^(CGRect keyboardFrameInView) {
      [addCommentContainer setY:keyboardFrameInView.origin.y - addCommentContainer.frame.size.height];
   }];

You can see source code for handling keyboard frame on this control.



回答6:

There's a much simpler way to anchor something to the keyboard. You just need to implement these methods and iOS will handle it for you.

- (UIView *) inputAccessoryView {
     // Return your textfield, buttons, etc
}

- (BOOL) canBecomeFirstResponder {
    return YES;
}

Here's a good tutorial breaking it down more



回答7:

This works for me:

Register for the keyboard did hide notification: UIKeyboardDidHideNotification.

In viewDidLoad add the toolbar to the bottom of the view, using addSubview.

I use a textView so in textViewShouldBeginEditing I set the inputAccessoryView.

Then in the keyboard did hide method, adjust the frame of the toolbar, set the inputAccessoryView to nil, and IMPORTANT, add the toolbar as a subview of the view again.