web-dev-qa-db-ja.com

iMessageアプリと同様のキーボード上のUIView

現在、私は基本的にApples iMessageAppの正確なコピーを実装しようとしています。

つまり、画面の下部にドッキングされ、firstResponderになると上に移動するUITextViewが必要です。 -実はとても簡単です。これを行うには何億もの方法があり、最も一般的な2つの方法は、通知を受信した場合に、ビューを上向きまたは下向きにアニメーション化することです。もう1つは、inputAccessoryViewを介して実行することです。悲しいことに、一方にはある機能とそうでない機能があります。そして、それらは相互に排他的であるように見えます。

大きな問題は回転です。

私は少なくとも7つの異なるgithubプロジェクトを掘り下げました。それらはすべて、私が達成しようとしているのと同じ機能/動作を再実装していますが、文字通りすべてが惨めに失敗しています。

たとえば、公式のFacebook/FacebookMes​​senger /(および場合によってはWhatsApp)アプリが使用するHPGrowingTextViewは、ジャンクコードの1つの大きな部分です。 iDeviceを持って、Facebookアプリを開き、チャットに移動し、キーボードをポップして、デバイスを回転させます。注意を払うと、入力バーがわずかにジャンプし、キーボードのフレームとフレームの間に空白が残っていることに気付くでしょう。次に、キーボードが表示されたら、iMessageでのAppleの実装を見てください。パーフェクトだ。

それ以外に、HPGrowingTextViewライブラリが利用するcontentOffsetとEdgeInset-hackingは、私に悪夢を与えます。

だから私は自分でそれをやり、ゼロから始めたかったのです。

現在、成長しているUITextViewの非常に洗練された、エレガントでハックのない実装がありますが、一部が欠落しています。

エレガントなローテーション。

WillRotateToInterfaceOrientation:duration:メソッドでフレームをそれぞれの新しい位置に調整するだけで、すべてが完全に機能するようになりますが、HPGrowingTextView(Facebookアプリを参照)と同じ問題があります。回転が行われている間、inputviewとキーボードの間にわずかなスペースがあります。

デバイスを横向きに回転させると、現在表示されている縦向きキーボードは「変形」せず、消えて(「willHide」通知を送信)、横向きバージョンが再び表示される(「willShow」通知を送信)ことがわかりました。トランジションは非常に微妙なフェードであり、場合によってはサイズ変更が行われます。

InputAccessoryViewを使用してプロジェクトを再実装し、その後何が起こるかを確認しましたが、嬉しい驚きでした。 inputAccessoryViewは、キーボードと完全に同期して回転します。 2つの間にスペース/ギャップはありません。

残念ながら、inputAccessoryViewを画面の下部にドッキングして[〜#〜] not [〜#〜]にする方法については、まだ考えていません。 -)キーボードと一緒に消える/そこから移動する...

私が望まないのは、「toInterfaceOrientationのCoordinateSystemでフレームを少し下げてから、didRotateFrom ...が呼び出されたときにフレームを上に戻す」などのハック的なソリューションです。

私はそのような振る舞いを実装することに成功したもう1つのアプリを知っています。それは「KikMessenger」です。

誰かが私がまだそのトピックをカバーしているのを見たことがないアイデア、アドバイス、またはリンクを持っていますか?

本当にありがとう!

注:この問題が解決したら、過去数日間に見つけたほとんどすべての実装が混乱しているため、誰もが利益を得られるようにプロジェクトをオープンソース化します。

26
pkluz

私は非常にエレガントな方法で問題を解決することに成功しました(私は思う、...)。

コードは来週Githubでリリースされ、この回答にリンクされます。

-

方法:inputAccessoryViewを選択して回転を機能させました-方法。

命名法:

  1. 'MessageInputView'は私の'GrowingUITextView'を含むUIViewです(「送信」ボタンと背景画像も含まれています)。
  2. 'ChatView'は、すべてのChatbubblesを表示するChatViewControllerに属するビューであり、'MessageInputView'が下部にドッキングされています。
  3. 'keyboardAccessoryView'は、サイズが空のUIViewです:CGRect(0,0,0,0)。

キーボードが閉じられたときにMessageInputViewを画面に貼り付ける方法を理解する必要がありました。それは難しい部分でした。これを行うには、別のビュー(keyboardAccessoryView)を作成し、GrowingUITextViewをinputAccessoryViewとして使用しました。後で参照する必要があるため、keyboardAccessoryViewを保持しました。

次に、他の試みで行ったことのいくつかを思い出しました(キーボード通知が到着するたびに、画面の周りのMessageInputViewのフレームをアニメーション化します)。

MessageInputViewをサブビューとしてChatView(一番下)に追加しました。それがアクティブになり、willShow:メソッドがキーボード通知によって呼び出されるたびに、MessageInputViewのフレームを指定された上部の位置に手動でアニメーション化します。アニメーションが終了し、完了ブロックが実行されたら、サブビューをChatViewから削除し、keyboardAccessoryViewに追加します。これにより、inputAccessoryViewのフレーム/境界が変更されるたびにキーボードが再ロードされるため、別の通知が発生します。あなたはそれを認識し、それを適切に扱う必要があります!

キーボードが閉じられようとしているときに、MessageInputViewのフレームをChatViewの座標系に変換し、サブビューとして追加します。したがって、それは私のkeyboardAccessoryViewから削除されます。次に、keyboardAccessoryViewのフレームのサイズをCGRect(0,0,0,0)に戻します。そうしないと、UIViewAnimationDurationが一致しないためです。次に、キーボードを閉じることを許可し、MessageInputViewを上からフォローして、最終的に画面の下部にドッキングします。

ただし、これは非常に多くの作業であり、利益はほとんどありません。

-

気を付けて。

PS:誰かがそれを(完全に)行うためのより簡単な方法を見つけたら、私に知らせてください。

5
pkluz

最近、同じ問題が発生し、利用可能なサードパーティライブラリに完全に満足していなかったため、カスタムソリューションを構築する必要がありました。この実装を独自のGitHubプロジェクトに分割しました。

MessageComposerView

IOSでのいくつかの簡単なテストから 6.1 7&8シミュレーターは、回転がキーボードに正しく追従しているように見えます。ビューもテキストとともに拡大し、回転すると自動的にサイズが変更されます。

MessageComposerView

このような非常に基本的なinit関数を使用して、画面の幅とデフォルトの高さで作成できます。例:

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

フレーム、キーボードオフセット、テキストビューの最大高さをカスタマイズできるイニシャライザーは他にもいくつかあります。詳細については、readmeを参照してください。

11
alexgophermix

これは、iOS 9.3.1および8.3.1で正しく機能しているUITextViewサブクラスです。キャレットを常に適切な場所に保ち、スムーズにアニメーション化しながら、制限付きで拡大と縮小を処理します。

キーボードの上にビューを貼り付けるのは簡単で、多くの解決策を簡単に見つけることができるので、カバーされていません...

本番環境に対応したソリューションが見つからなかったため、最初からこれに取り組むことになりました。私は途中でたくさんの小さな問題を解決しなければなりませんでした。

コードコメントは、何が起こっているのかを理解するのに役立ちます。

私はこれを my Github で共有しました、貢献は大歓迎です。

メモ

  • ランドスケープをサポートするためのテストは行われていません
  • I6 +ではテストされていません

デモ

enter image description here

(最大高さ要素がスクロール可能になった後。デモをドラッグするのを忘れましたが、これも期待どおりに機能しています...)

サブクラス

class ruuiDynamicTextView: UITextView {
    var dynamicDelegate: ruuiDynamicTextViewDelegate?
    var minHeight: CGFloat!
    var maxHeight: CGFloat?
    private var contentOffsetCenterY: CGFloat!

    init(frame: CGRect, offset: CGFloat = 0.0) {
        super.init(frame: frame, textContainer: nil)
        minHeight = frame.size.height

        //center first line
        let size = self.sizeThatFits(CGSizeMake(self.bounds.size.width, CGFloat.max))
        contentOffsetCenterY = (-(frame.size.height - size.height * self.zoomScale) / 2.0) + offset

        //listen for text changes
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(textChanged), name: UITextViewTextDidChangeNotification, object: nil)

        //update offsets
        layoutSubviews()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        //Use content size if more than min size, compensate for Y offset
        var height = max(self.contentSize.height - (contentOffsetCenterY * 2.0), minHeight)
        var updateContentOffsetY: CGFloat?
        //Max Height
        if maxHeight != nil && height > maxHeight {
            //Cap at maxHeight
            height = maxHeight!
        } else {
            //constrain Y to prevent odd skip and center content to view.
            updateContentOffsetY = contentOffsetCenterY
        }
        //update frame if needed & notify delegate
        if self.frame.size.height != height {
            self.frame.size.height = height
            dynamicDelegate?.dynamicTextViewDidResizeHeight(self, height: height)
        }
        //constrain Y must be done after setting frame
        if updateContentOffsetY != nil {
            self.contentOffset.y = updateContentOffsetY!
        }
    }

    func textChanged() {
        let caretRect = self.caretRectForPosition(self.selectedTextRange!.start)
        let overflow = caretRect.size.height + caretRect.Origin.y - (self.contentOffset.y + self.bounds.size.height - self.contentInset.bottom - self.contentInset.top)
        if overflow > 0 {
            //Fix wrong offset when cursor jumps to next line un explisitly
            let seekEndY = self.contentSize.height - self.bounds.size.height
            if self.contentOffset.y != seekEndY {
                self.contentOffset.y = seekEndY
            }
        }
    }
}

protocol ruuiDynamicTextViewDelegate {
    func dynamicTextViewDidResizeHeight(textview: ruuiDynamicTextView, height: CGFloat)
}
2
Andres Canella

この問題を解決する方法:

ChatViewControllerFooterViewControllerUIContainerViewとして持っています。また、FooterViewControllerにcontentViewアウトレットがあります。次に、ChatViewControllerに次のようになります。

override func becomeFirstResponder() -> Bool {
    return true
}

override var inputAccessoryView: UIView? {
    if let childViewController = childViewControllers.first as? FooterViewController {
        childViewController.contentView.removeFromSuperview()
        return childViewController.contentView
    }
    return nil
}

enter image description here

もう1つの方法は、プログラムでビューを作成し、inputAccessoryViewとして返すことです。

1
ChikabuZ

最近、私はあなたが説明したこの正確な問題と、キーボード通知を使用してinputAccessoryViewを使用せずに、短くエレガントな方法で問題を解決する方法についてブログに投稿しました。そして、この質問はかなり古いですが、このトピックはまだ関連しているので、ここに投稿へのリンクがあります: キーボードと接続されたビューの間の回転アニメーションの同期

ブログ投稿で説明されている長い説明に飛び込みたくない場合は、コード例を含む簡単な説明があります。

基本的な原則は、誰もが使用するのと同じ方法を使用することです。つまり、キーボード通知を監視して、添付されたビューを上下にアニメーション化します。ただし、それに加えて、インターフェイスの向きが変更された結果としてキーボード通知が発生した場合は、これらのアニメーションをキャンセルする必要があります。

インターフェイスの向きの変更に関するアニメーションキャンセルカスタムなしの回転例: 

インターフェイスの向きの変更時にアニメーションをキャンセルする回転の例: 

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

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

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

    [[NSNotificationCenter defaultCenter]
            removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter]
            removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    self.animatingRotation = YES;
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    self.animatingRotation = NO;
}

- (void)adjustViewForKeyboardNotification:(NSNotification *)notification {
    NSDictionary *notificationInfo = [notification userInfo];

    // Get the end frame of the keyboard in screen coordinates.
    CGRect finalKeyboardFrame = [[notificationInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];

    // Convert the finalKeyboardFrame to view coordinates to take into account any rotation
    // factors applied to the window’s contents as a result of interface orientation changes.
    finalKeyboardFrame = [self.view convertRect:finalKeyboardFrame fromView:self.view.window];

    // Calculate new position of the commentBar
    CGRect commentBarFrame = self.commentBar.frame;
    commentBarFrame.Origin.y = finalKeyboardFrame.Origin.y - commentBarFrame.size.height;

    // Update tableView height.
    CGRect tableViewFrame = self.tableView.frame;
    tableViewFrame.size.height = commentBarFrame.Origin.y;

    if (!self.animatingRotation) {
        // Get the animation curve and duration
        UIViewAnimationCurve animationCurve = (UIViewAnimationCurve) [[notificationInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
        NSTimeInterval animationDuration = [[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];

        // Animate view size synchronously with the appearance of the keyboard. 
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:animationDuration];
        [UIView setAnimationCurve:animationCurve];
        [UIView setAnimationBeginsFromCurrentState:YES];

        self.commentBar.frame = commentBarFrame;
        self.tableView.frame = tableViewFrame;

        [UIView commitAnimations];
    } else {
        self.commentBar.frame = commentBarFrame;
        self.tableView.frame = tableViewFrame;
    }
}
0
smnh