Now we move on to the user interface design and demonstrate how we set up the link between our presenters. Developing the user interface is no different to developing natively for iOS and Android; the only difference with MVP is that we initialize a view with its related presenter in the constructor.
Let's start by adding a new folder to the Chat.iOS
project called Views
, add in a new file called LoginViewController.cs
, and implement the following:
public class LoginViewController : UIViewController, LoginPresenter.ILoginView { #region Private Properties private bool _isInProgress = false; private LoginPresenter _presenter; private UITextField _loginTextField; private UITextField _passwordTextField; private UIActivityIndicatorView _activityIndicatorView; #endregion #region Constructors public LoginViewController(LoginPresenter presenter) { _presenter = presenter; } #endregion }
We start off simply with the private properties and the constructor, where we are passing a new LoginPresenter
object that we create from the AppDelegate
as the starting presenter. The two text fields are used for the username and password entries. We have these as a local variable as we will need to access one instance of each from multiple functions. We also have a UIActivityIndicatorView
for displaying the progress when we login and register.
Let's go ahead and add the ViewDidLoad
function. We will implement this in a few parts. First we are going to set the view of the presenter and initialize all the UI elements and add them into the View
:
#region Public Methods public override void ViewDidLoad() { base.ViewDidLoad(); View.BackgroundColor = UIColor.White; _presenter.SetView(this); var width = View.Bounds.Width; var height = View.Bounds.Height; Title = "Welcome"; var titleLabel = new UILabel() { TranslatesAutoresizingMaskIntoConstraints = false, Text = "Chat", Font = UIFont.FromName("Helvetica-Bold", 22), TextAlignment = UITextAlignment.Center }; _activityIndicatorView = new UIActivityIndicatorView() { TranslatesAutoresizingMaskIntoConstraints = false, Color = UIColor.Black }; var descriptionLabel = new UILabel() { TranslatesAutoresizingMaskIntoConstraints = false, Text = "Enter your login name to join the chat room.", Font = UIFont.FromName("Helvetica", 18), TextAlignment = UITextAlignment.Center }; _loginTextField = new UITextField() { TranslatesAutoresizingMaskIntoConstraints = false, Placeholder = "Username", Font = UIFont.FromName("Helvetica", 18), BackgroundColor = UIColor.Clear.FromHex("#DFE4E6"), TextAlignment = UITextAlignment.Center }; _passwordTextField = new UITextField() { TranslatesAutoresizingMaskIntoConstraints = false, Placeholder = "Password", Font = UIFont.FromName("Helvetica", 18), BackgroundColor = UIColor.Clear.FromHex("#DFE4E6"), TextAlignment = UITextAlignment.Center }; var buttonView = new UIView() { TranslatesAutoresizingMaskIntoConstraints = false }; var loginButton = new UIButton(UIButtonType.RoundedRect) { TranslatesAutoresizingMaskIntoConstraints = false }; loginButton.SetTitle("Login", UIControlState.Normal); loginButton.TouchUpInside += (sender, e) => Login(this, new Tuple<string, string>(_loginTextField.Text, _passwordTextField.Text)); var registerButton = new UIButton(UIButtonType.RoundedRect) { TranslatesAutoresizingMaskIntoConstraints = false }; registerButton.SetTitle("Register", UIControlState.Normal); registerButton.TouchUpInside += (sender, e) => Register(this, new Tuple<string, string>(_loginTextField?.Text, _passwordTextField?.Text)); Add(titleLabel); Add(descriptionLabel); Add(_activityIndicatorView); Add(_loginTextField); Add(_passwordTextField); Add(buttonView); buttonView.Add(loginButton); buttonView.Add(registerButton); } #endregion
This is a large block of code, but we are creating quite a few UI elements. All have the TranslatesAutoresizingMaskIntoConstraints
set to false
ready for
NSLayout
. Have a look at how we integrate the ILoginView
implementation with the Login and RegisterEventHandlers
as they are wired to the TouchUpInside
event of each button.
Now let's start building the NSLayoutConstraints
. Add the following to the bottom of the ViewDidLoad
function:
var views = new DictionaryViews() { {"titleLabel", titleLabel}, {"descriptionLabel", descriptionLabel}, {"loginTextField", _loginTextField}, {"passwordTextField", _passwordTextField}, {"loginButton", loginButton}, {"registerButton", registerButton}, {"activityIndicatorView", _activityIndicatorView}, {"buttonView", buttonView} }; buttonView.AddConstraints( NSLayoutConstraint.FromVisualFormat("V:|-[registerButton]-|", NSLayoutFormatOptions.DirectionLeftToRight, null, views) .Concat(NSLayoutConstraint.FromVisualFormat("V:|-[loginButton]-|", NSLayoutFormatOptions.DirectionLeftToRight, null, views)) .Concat(NSLayoutConstraint.FromVisualFormat("H:|-[registerButton]-30-[loginButton]-|", NSLayoutFormatOptions.DirectionLeftToRight, null, views)) .ToArray()); View.AddConstraints( NSLayoutConstraint.FromVisualFormat("V:|-100-[titleLabel(50)]-[descriptionLabel(30)]-10-[loginTextField(30)]-10-[passwordTextField(30)]-10-[buttonView]", NSLayoutFormatOptions.DirectionLeftToRight, null, views) .Concat(NSLayoutConstraint.FromVisualFormat("V:|-100-[activityIndicatorView(50)]-[descriptionLabel(30)]-10-[loginTextField(30)]-10-[passwordTextField(30)]-10-[buttonView]", NSLayoutFormatOptions.DirectionLeftToRight, null, views)) .Concat(NSLayoutConstraint.FromVisualFormat("H:|-10-[titleLabel]-10-|", NSLayoutFormatOptions.AlignAllTop, null, views)) .Concat(NSLayoutConstraint.FromVisualFormat("H:[activityIndicatorView(30)]-10-|", NSLayoutFormatOptions.AlignAllTop, null, views)) .Concat(NSLayoutConstraint.FromVisualFormat("H:|-10-[descriptionLabel]-10-|", NSLayoutFormatOptions.AlignAllTop, null, views)) .Concat(NSLayoutConstraint.FromVisualFormat("H:|-30-[loginTextField]-30-|", NSLayoutFormatOptions.AlignAllTop, null, views)) .Concat(NSLayoutConstraint.FromVisualFormat("H:|-30-[passwordTextField]-30-|", NSLayoutFormatOptions.AlignAllTop, null, views)) .Concat(new[] { NSLayoutConstraint.Create(buttonView, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, View, NSLayoutAttribute.CenterX, 1, 1) }) .ToArray());
The constraints will position the buttonView
to the center of the screen horizontally; each buttons inside will be positioned horizontally next to each other. The rest of the layout is very self-explanatory. We are simply stacking the remaining element vertically down the page. The UIActivityIndicatorView
will be positioned to the top right of the screen next to the TitleLabel
. The rest of the layout will make more sense when we try running the application.
Finally, we add the remaining interface implementations; we require both Login and Register
for the ILoginView
interface. We also require IsInProgress
bool and the SetErrorMessage
function; this will create a new UIAlertView
showing the error message. We also override the get and set of IsInProgress
to control the start and stop animation of the UIActivityIndicatorView
:
#region ILoginView implementation public event EventHandler<Tuple<string, string>> Login; public event EventHandler<Tuple<string, string>> Register; #endregion #region IView implementation public void SetErrorMessage(string message) { var alert = new UIAlertView() { Title = "Chat", Message = message }; alert.AddButton("OK"); alert.Show(); } public bool IsInProgress { get { return _isInProgress; } set { if (value == _isInProgress) { return; } // we control the activity view when we set 'IsInProgress' if (value) { _activityIndicatorView.StartAnimating(); } else { _activityIndicatorView.StopAnimating(); } _isInProgress = value; } } #endregion
The link between our first view and presenter is not as clean as an MVVM BindingContext with Xamarin.Forms
, but the advantage is having no middle layer of rendering between the native user interface and the data to be displayed.
18.118.2.15