0

I'm using this sample code a create a simple chat.

Chat

This example is without a NavigationController and everything works fine. The toolbar moves up when when keyboard appears. But when I put it in a NavigationController, the keyboard doesn't move (but View does move). I tried setting textview inputAccessoryView to the keyboard, but not working. Other solutions in stack overflow involve when using the navigation controller's Toolbar. but this is a custom created toolbar...

Any help is appreciated...

public class ChatViewController : UIViewController
{
    public MessageContacts chatWith;

    NSObject willShowToken;
    NSObject willHideToken;

    List<Message> messages;
    ChatSource chatSource;

    UITableView tableView;
    UIToolbar toolbar;

    NSLayoutConstraint toolbarBottomConstraint;
    NSLayoutConstraint toolbarHeightConstraint;

    ChatInputView chatInputView;

    UIButton SendButton
    {
        get
        {
            return chatInputView.SendButton;
        }
    }

    UITextView TextView
    {
        get
        {
            return chatInputView.TextView;
        }
    }

    bool IsInputToolbarHasReachedMaximumHeight
    {
        get
        {
            return toolbar.Frame.GetMinY() == TopLayoutGuide.Length;
        }
    }

    public ChatViewController(IntPtr handle)
        : base(handle)
    {
        messages = new List<Message>() {
            new Message { Type = MessageType.Incoming, Text = "Hello!" },
            new Message { Type = MessageType.Outgoing, Text = "Hi!" },
            new Message { Type = MessageType.Incoming, Text = "Do you know about Xamarin?" },
            new Message { Type = MessageType.Outgoing, Text = "Yes! Sure!" },
            new Message { Type = MessageType.Incoming, Text = "And what do you think?" },
            new Message { Type = MessageType.Outgoing, Text = "I think it is the best way to develop mobile applications." },
            new Message { Type = MessageType.Incoming, Text = "Wow :-)" },
            new Message { Type = MessageType.Outgoing, Text = "Yep. Check it out\nhttp://Xamarin.com" },
            new Message { Type = MessageType.Incoming, Text = "Will do. Thanks" },
        };
    }

    #region Life cycle

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();
        Title = chatWith.name;
        SetUpTableView();
        SetUpToolbar();

        SendButton.TouchUpInside += OnSendClicked;
        TextView.Started += OnTextViewStarted;
        TextView.Changed += OnTextChanged;

        TextView.InputAccessoryView = toolbar;
    }

    public override void ViewWillAppear(bool animated)
    {
        base.ViewWillAppear(animated);

        willShowToken = UIKeyboard.Notifications.ObserveWillShow(KeyboardWillShowHandler);
        willHideToken = UIKeyboard.Notifications.ObserveWillHide(KeyboardWillHideHandler);

        UpdateTableInsets();
        UpdateButtonState();
        ScrollToBottom(false);
    }

    public override void ViewDidAppear(bool animated)
    {
        base.ViewDidAppear(animated);
        AddObservers();
    }

    #endregion

    #region Initialization

    void SetUpTableView()
    {
        tableView = new UITableView
        {
            TranslatesAutoresizingMaskIntoConstraints = false,
            AllowsSelection = false,
            SeparatorStyle = UITableViewCellSeparatorStyle.None,
        };
        tableView.RegisterClassForCellReuse(typeof(IncomingCell), IncomingCell.CellId);
        tableView.RegisterClassForCellReuse(typeof(OutgoingCell), OutgoingCell.CellId);
        View.AddSubview(tableView);

        var pinLeft = NSLayoutConstraint.Create(tableView, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, View, NSLayoutAttribute.Leading, 1f, 0f);
        View.AddConstraint(pinLeft);

        var pinRight = NSLayoutConstraint.Create(tableView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, View, NSLayoutAttribute.Trailing, 1f, 0f);
        View.AddConstraint(pinRight);

        var pinTop = NSLayoutConstraint.Create(tableView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, TopLayoutGuide, NSLayoutAttribute.Bottom, 1f, 0f);
        View.AddConstraint(pinTop);

        var pinBottom = NSLayoutConstraint.Create(tableView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, View, NSLayoutAttribute.Bottom, 1f, 0f);
        View.AddConstraint(pinBottom);

        chatSource = new ChatSource(messages);
        tableView.Source = chatSource;
    }

    void SetUpToolbar()
    {
        toolbar = new UIToolbar
        {
            TranslatesAutoresizingMaskIntoConstraints = false
        };
        chatInputView = new ChatInputView
        {
            TranslatesAutoresizingMaskIntoConstraints = false
        };

        View.AddSubview(toolbar);

        var pinLeft = NSLayoutConstraint.Create(toolbar, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, View, NSLayoutAttribute.Leading, 1f, 0f);
        View.AddConstraint(pinLeft);

        var pinRight = NSLayoutConstraint.Create(toolbar, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, View, NSLayoutAttribute.Trailing, 1f, 0f);
        View.AddConstraint(pinRight);

        toolbarBottomConstraint = NSLayoutConstraint.Create(View, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, toolbar, NSLayoutAttribute.Bottom, 1f, 0f);
        View.AddConstraint(toolbarBottomConstraint);

        toolbarHeightConstraint = NSLayoutConstraint.Create(toolbar, NSLayoutAttribute.Height, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 0f, 44f);
        View.AddConstraint(toolbarHeightConstraint);

        toolbar.AddSubview(chatInputView);
        var c1 = NSLayoutConstraint.FromVisualFormat("H:|[chat_container_view]|",
            (NSLayoutFormatOptions)0,
            "chat_container_view", chatInputView
        );
        var c2 = NSLayoutConstraint.FromVisualFormat("V:|[chat_container_view]|",
            (NSLayoutFormatOptions)0,
            "chat_container_view", chatInputView
        );
        toolbar.AddConstraints(c1);
        toolbar.AddConstraints(c2);


    }

    #endregion

    void AddObservers()
    {
        TextView.AddObserver(this, "contentSize", NSKeyValueObservingOptions.OldNew, IntPtr.Zero);
    }

    public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
    {
        if (keyPath == "contentSize")
            OnSizeChanged(new NSObservedChange(change));
        else
            base.ObserveValue(keyPath, ofObject, change, context);
    }

    void OnSizeChanged(NSObservedChange change)
    {
        CGSize oldValue = ((NSValue)change.OldValue).CGSizeValue;
        CGSize newValue = ((NSValue)change.NewValue).CGSizeValue;

        var dy = newValue.Height - oldValue.Height;
        AdjustInputToolbarOnTextViewSizeChanged(dy);
    }

    void AdjustInputToolbarOnTextViewSizeChanged(nfloat dy)
    {
        bool isIncreasing = dy > 0;
        if (IsInputToolbarHasReachedMaximumHeight && isIncreasing)
        {
            // TODO: scroll to bottom
            return;
        }

        nfloat oldY = toolbar.Frame.GetMinY();
        nfloat newY = oldY - dy;
        if (newY < TopLayoutGuide.Length)
            dy = oldY - TopLayoutGuide.Length;

        AdjustInputToolbar(dy);
    }

    void AdjustInputToolbar(nfloat change)
    {
        toolbarHeightConstraint.Constant += change;

        if (toolbarHeightConstraint.Constant < ChatInputView.ToolbarMinHeight)
            toolbarHeightConstraint.Constant = ChatInputView.ToolbarMinHeight;

        View.SetNeedsUpdateConstraints();
        View.LayoutIfNeeded();
    }

    void KeyboardWillShowHandler(object sender, UIKeyboardEventArgs e)
    {
        UpdateButtomLayoutConstraint(e);
    }

    void KeyboardWillHideHandler(object sender, UIKeyboardEventArgs e)
    {
        UpdateButtomLayoutConstraint(e);
    }

    void UpdateButtomLayoutConstraint(UIKeyboardEventArgs e)
    {
        UIViewAnimationCurve curve = e.AnimationCurve;
        UIView.Animate(e.AnimationDuration, 0, ConvertToAnimationOptions(e.AnimationCurve), () =>
        {
            nfloat offsetFromBottom = tableView.Frame.GetMaxY() - e.FrameEnd.GetMinY();
            offsetFromBottom = NMath.Max(0, offsetFromBottom);
            SetToolbarContstraint(offsetFromBottom);
        }, null);
    }

    void SetToolbarContstraint(nfloat constant)
    {
        toolbarBottomConstraint.Constant = constant;
        View.SetNeedsUpdateConstraints();
        View.LayoutIfNeeded();

        UpdateTableInsets();
    }

    void UpdateTableInsets()
    {
        nfloat bottom = tableView.Frame.GetMaxY() - toolbar.Frame.GetMinY();
        UIEdgeInsets insets = new UIEdgeInsets(0f, 0f, bottom, 0f);
        tableView.ContentInset = insets;
        tableView.ScrollIndicatorInsets = insets;
    }

    UIViewAnimationOptions ConvertToAnimationOptions(UIViewAnimationCurve curve)
    {
        // Looks like a hack. But it is correct.
        // UIViewAnimationCurve and UIViewAnimationOptions are shifted by 16 bits
        // http://stackoverflow.com/questions/18870447/how-to-use-the-default-ios7-uianimation-curve/18873820#18873820
        return (UIViewAnimationOptions)((int)curve << 16);
    }

    void OnSendClicked(object sender, EventArgs e)
    {
        var text = TextView.Text;
        TextView.Text = string.Empty; // this will not generate change text event
        UpdateButtonState();

        if (string.IsNullOrWhiteSpace(text))
            return;

        var msg = new Message
        {
            Type = MessageType.Outgoing,
            Text = text.Trim()
        };

        messages.Add(msg);
        tableView.InsertRows(new NSIndexPath[] { NSIndexPath.FromRowSection(messages.Count - 1, 0) }, UITableViewRowAnimation.None);
        ScrollToBottom(true);
    }

    void OnTextViewStarted(object sender, EventArgs e)
    {
        ScrollToBottom(true);
    }

    void OnTextChanged(object sender, EventArgs e)
    {
        UpdateButtonState();
    }

    void UpdateButtonState()
    {
        SendButton.Enabled = !string.IsNullOrWhiteSpace(TextView.Text);
    }

    public override void ViewWillDisappear(bool animated)
    {
        base.ViewWillDisappear(animated);

        willShowToken.Dispose();
        willHideToken.Dispose();
    }

    void ScrollToBottom(bool animated)
    {
        if (tableView.NumberOfSections() == 0)
            return;

        int items = (int)tableView.NumberOfRowsInSection(0);
        if (items == 0)
            return;

        int finalRow = (int)NMath.Max(0, tableView.NumberOfRowsInSection(0) - 1);
        NSIndexPath finalIndexPath = NSIndexPath.FromRowSection(finalRow, 0);
        tableView.ScrollToRow(finalIndexPath, UITableViewScrollPosition.Top, animated);
    }
}
Drunken Daddy
  • 7,326
  • 14
  • 70
  • 104

1 Answers1

0

This sample is really shit bro, there is lots of bug.

For the keyboard problem, I suggest you using those code:

At first register event in ViewDidLoad:

public override void ViewDidLoad ()
    {
        //Smaples codes
        base.ViewDidLoad ();

        SetUpTableView ();
        SetUpToolbar ();

        SendButton.TouchUpInside += OnSendClicked;
        TextView.Started += OnTextViewStarted;
        TextView.Changed += OnTextChanged;

        //Create by me
        this.View.AddGestureRecognizer(new UITapGestureRecognizer(()=>{
            this.View.EndEditing(true);
        }));

        NSNotificationCenter.DefaultCenter.AddObserver(this,new ObjCRuntime.Selector("keyboardWillShow:"),UIKeyboard.WillShowNotification,null);
        NSNotificationCenter.DefaultCenter.AddObserver(this,new ObjCRuntime.Selector("keyboardWillHide:"),UIKeyboard.WillHideNotification,null);
    }

Then use those to handle your keyboard event:

[Action("keyboardWillShow:")]
    private void KeyboardWillShow(NSNotification notification)
    {
        NSDictionary info = notification.UserInfo;
        CGSize kbSize = (info.ObjectForKey(UIKeyboard.FrameEndUserInfoKey) as NSValue).CGRectValue.Size;
        nfloat keyboardHeight = kbSize.Height;

        CGRect tmpR = this.View.Frame;
        tmpR.Y -= keyboardHeight;
        this.View.Frame = tmpR;
    }   

    [Action("keyboardWillHide:")]
    private void KeyboardWillHide(NSNotification notification)
    {
        this.View.Frame = UIScreen.MainScreen.Bounds;
    }   

And there is a very simple model for chat view I wrote before for another, is made by Objective-C, you can take a look, very simple.

simple model for chat view

Hope it can help you, if you still need some help, just leave it here, I will check it latter.

Community
  • 1
  • 1
Alanc Liu
  • 1,294
  • 10
  • 15