Composing messages

For the final piece of our application, we need to implement custom functionality that Apple doesn't provide with their APIs. We need to add a text field with a button that appears to be attached to the bottom of the table view. Most of this will require writing code and wiring up a lot of events.

Let's begin by adding some new member variables to our MessagesController class as follows:

UIToolbar toolbar;
UITextField message;
UIBarButtonItem send;
NSObject willShowObserver, willHideObserver;

We will place the text field and bar buttons inside the toolbar, as in the following code. The NSObject fields will be an example of iOS's event system called notifications. We'll see how those are used shortly.

public override void ViewDidLoad()
{
  base.ViewDidLoad();
  //Text Field
  message = new UITextField(new RectangleF(0, 0, 240, 32))
  {
    BorderStyle = UITextBorderStyle.RoundedRect,ReturnKeyType = UIReturnKeyType.Send,ShouldReturn = _ =>
    {
      Send();
      return false;
    },
  };

  //Bar button item
  send = new UIBarButtonItem("Send", UIBarButtonItemStyle.Plain,(sender, e) => Send());
  //Toolbar
  toolbar = new UIToolbar(new RectangleF(0, TableView.Frame.Height - 44,TableView.Frame.Width, 44));
  toolbar.Items = new UIBarButtonItem[]
  {
    new UIBarButtonItem(message),
    send
  };
  NavigationController.View.AddSubview(toolbar);
  TableView.Source = new TableSource();
  TableView.TableFooterView = new UIView(new RectangleF(0, 0, TableView.Frame.Width, 44))
  {
    BackgroundColor = UIColor.Clear,
  };
}

Much of this work is basic UI setup. It is not something we can do inside Xcode, because it is a custom UI in this case. We create a text field, bar button item, and toolbar from C# and add them to our navigation controller's view. This will display the toolbar at the top of the table view, no matter where it is scrolled to. Another trick we used was to add a footer view to the table view, which is of the same height as the toolbar. This will simplify some animations that we'll set up later.

Now we will need to modify ViewWillAppear as follows:

public async override void ViewWillAppear(bool animated)
{
  base.ViewWillAppear(animated);
  Title = messageViewModel.Conversation.Username;
  //Keyboard notifications
  willShowObserver = UIKeyboard.Notifications.ObserveWillShow((sender, e) => OnKeyboardNotification(e));
  willHideObserver = UIKeyboard.Notifications.ObserveWillHide((sender, e) => OnKeyboardNotification(e));
  //IsBusy
  messageViewModel.IsBusyChanged += OnIsBusyChanged;
  try
  {
    await messageViewModel.GetMessages();
    TableView.ReloadData();
    message.BecomeFirstResponder();
  }
  catch (Exception exc)
  {
    new UIAlertView("Oops!", exc.Message, null, "Ok").Show();
  }
}

Most of these changes are straightforward, but notice our use of iOS notifications. Xamarin has provided a C# friendly way to subscribe to notifications. There is a static nested class named Notifications inside various UIKit classes that provide notifications. Otherwise, you would have to use the NSNotificationCenter class, which is not as easy to use. To unsubscribe from these events, we merely need to dispose NSObject that is returned.

So let's add an override for ViewWillDisapper to clean up these events as follows:

public override void ViewWillDisappear(bool animated)
{
  base.ViewWillDisappear(animated);
  //Unsubcribe notifications
  if (willShowObserver != null)
  {
    willShowObserver.Dispose();
    willShowObserver = null;
  }
  if (willHideObserver != null)
  {
    willHideObserver.Dispose();
    willHideObserver = null;
  }
  //IsBusy
  messageViewModel.IsBusyChanged -= OnIsBusyChanged;
}

Next, let's set up our methods for these events as follows:

void OnIsBusyChanged (object sender, EventArgs e)
{
  message.Enabled =send.Enabled = !messageViewModel.IsBusy;
}

void ScrollToEnd()
{
  TableView.ContentOffset = new PointF(0, TableView.ContentSize.Height –TableView.Frame.Height);
}

void OnKeyboardNotification (UIKeyboardEventArgs e)
{
  //Check if the keyboard is becoming visible
  bool willShow = e.Notification.Name ==UIKeyboard.WillShowNotification;
  //Start an animation, using values from the keyboard
  UIView.BeginAnimations("AnimateForKeyboard");
  UIView.SetAnimationDuration(e.AnimationDuration);
  UIView.SetAnimationCurve(e.AnimationCurve);
  //Calculate keyboard height, etc.
  if (willShow)
  {
    var keyboardFrame = e.FrameEnd;
    var frame = TableView.Frame;
    frame.Height -= keyboardFrame.Height;
    TableView.Frame = frame;
    frame = toolbar.Frame;
    frame.Y -= keyboardFrame.Height;
    toolbar.Frame = frame;
  }
  else
  {
    var keyboardFrame = e.FrameBegin;
    var frame = TableView.Frame;
    frame.Height += keyboardFrame.Height;
    TableView.Frame = frame;
    frame = toolbar.Frame;
    frame.Y += keyboardFrame.Height;
    toolbar.Frame = frame;
  }
  //Commit the animation
  UIView.CommitAnimations();
  ScrollToEnd();
}

That is quite a bit of code, but not too difficult. OnIsBusyChanged is used to disable some of our views while it is loading. ScrollToEnd is a quick method to scroll the table view to the end. We need this for the sake of usability. Some math is required because Apple does not provide a built-in method for this.

On the other hand, OnKeyboardNotification has quite a lot going on. We used the built-in animation system for iOS to set up an animation when the keyboard appears or hides. We use this to move views around for the on-screen keyboard. Using the animation system is quite easy; call UIView.BeginAnimations, modify some views, and then finish up with UIView.CommitAnimations. We also used a few more values from the keyboard to time our animation identically with the keyboard's animations.

Last but not least, we need to implement a function for sending a new message as follows:

async void Send()
{
  //Just hide the keyboard if they didn't type anything
  if (string.IsNullOrEmpty(message.Text))
  {
    message.ResignFirstResponder();
    return;
  }
  //Set the text, send the message
  messageViewModel.Text = message.Text;
  await messageViewModel.SendMessage();
  //Clear the text field & view model
  message.Text =messageViewModel.Text = string.Empty;
  //Reload the table
  TableView.ReloadData();
  //Hide the keyboard
  message.ResignFirstResponder();
  //Scroll to end, to see the new message
  ScrollToEnd();
}

This code is also fairly straightforward. After sending the message, we merely need to reload the table, hide the keyboard, and then make sure we scroll to the bottom to see the new message, as shown in the following screenshot. Using the async keyword makes this easy.

Composing messages
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.31.240