Creating iOS Applications in Code
This article examines how to create iOS applications entirely in code using Visual Studio. It shows how to start from an empty project template to build an application screen in a controller by creating a hierarchy of views from UIKit. Then, it discusses how to create custom views that can be loaded in a controller.
Overview
iOS user interfaces are built from a combination of views and controllers. Views are used to construct user interfaces in a hierarchical fashion. These view hierachies can be constructed in Xcode using Interface Builder to create Xib or Storyboards. Additionally, the Xamarin Studio designer can create Storyboard based user interfaces on OS X. However, in Visual Studio currently user interfaces can only be created in code. You do have the option of switching over to OS X just for the designer support though. Regardless, whether you wish to work exclusively in Visual Stduio, or you use designer from OS X, it is good to have a fundamental understanding of how to work entirely in code. This article walks through some basic points to get up and running with code-only user interface development.
Creating a Code-Only Project
iOS Empty Project Template
Let's create an iOS project in Visual Studio using the iPhone Empty Project template, shown below, which we'll extend to add controllers and views.
The Empty project template adds 3 files to the project:
- AppDelegate.cs - Contains a
UIApplicationDelegate
subclass,AppDelegate
, which is used to handle application events from iOS. The application window is created in the AppDelegate'sFinishedLaunching
method. - Info.plist - Property list file that contains application configuration information.
- Main.cs - Contains the entry point for the application, which specifies the class for the
AppDelegate
.
Creating a Window
iOS applications are built using the MVC pattern. The first screen that an application displays is created from the windows's root view controller. See the Multi-Screened Applications document for more details on the MVC pattern itself.
The implementation for the AppDelegate
added by the template creates the application window, of which there is only one for every iOS application, and makes it visible with the following code:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
// create a new window instance based on the screen size
window = new UIWindow(UIScreen.MainScreen.Bounds);
// make the window visible
window.MakeKeyAndVisible();
return true;
}
This produces a blank screen, as shown below in the simulator:
Adding a Controller
Let's add a controller to the window by creating a UIViewController
instance and setting it to thewindow.RootViewController
property:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
// create a new window instance based on the screen size
window = new UIWindow(UIScreen.MainScreen.Bounds);
controller = new UIViewController();
controller.View.BackgroundColor = UIColor.White;
window.RootViewController = controller;
// make the window visible
window.MakeKeyAndVisible();
return true;
}
Every controller has an associated view, which is accessible from the View
property. The above code changes the view's BackgroundColor
property to UIColor.White
so that it will be visible, as shown below:
We could set any UIViewController
subclass as the RootViewController
in this way as well, including controllers from UIKit as well as those we write ourselves. For example, the following code adds aUINavigationController
as the RootViewController
:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
// create a new window instance based on the screen size
window = new UIWindow(UIScreen.MainScreen.Bounds);
controller = new UIViewController();
controller.View.BackgroundColor = UIColor.White;
controller.Title = "My Controller";
navController = new UINavigationController(controller);
window.RootViewController = navController;
// make the window visible
window.MakeKeyAndVisible();
return true;
}
This produces the controller nested within the navigation controller as shown below:
Creating a View Controller
Now that we've seen how to add a controller as the RootViewController
of the window, let's see how to create a custom view controller in code.
Add a new class named CustomViewController
as shown below:
The class should inherit from UIViewController
, which is in the MonoTouch.UIKit
namespace, as shown:
using System;
using MonoTouch.UIKit;
namespace CodeOnlyDemo
{
class CustomViewController : UIViewController
{
}
}
Initializing the View
UIViewController
has a method called ViewDidLoad
that is called after the view is in memory. This is an appropriate place to do initialization of the view, such as setting it's properties.
For example, the following code changes the view's background color to gray:
using System;
using MonoTouch.UIKit;
namespace CodeOnlyDemo
{
class CustomViewController : UIViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
View.BackgroundColor = UIColor.Gray;
}
}
}
To load this controller, instantiate it and set it to the window's RootViewController
in the AppDelegate
as before:
controller = new CustomViewController();
window.RootViewController = controller;
Now when the application loads, the CustomViewController
is loaded and its gray view is displayed:
Building the View Hierarchy
With a view controller created, we can now begin to create a user interface in code. iOS user interfaces are comprised of a view hierarchy. To add additional views, such as labels, buttons, sliders, etc. we add those views as subviews of some parent view.
For example let's make a login screen where the user can enter a username and password. The screen will consist of two text fields and a button.
Adding the Text Fields
First we'll add a control for the username by creating and initializing a UITextField
and then adding it to the view hierarchy, as shown below:
class CustomViewController : UIViewController
{
UITextField usernameField;
public override void ViewDidLoad()
{
base.ViewDidLoad();
View.BackgroundColor = UIColor.Gray;
float h = 31.0f;
float w = View.Bounds.Width;
usernameField = new UITextField
{
Placeholder = "Enter your username",
BorderStyle = UITextBorderStyle.RoundedRect,
Frame = new RectangleF(10, 10, w - 20, h)
};
View.AddSubview(usernameField);
}
}
When we create the UITextField
, we set the Frame
property to define its location and size. In iOS the 0,0 coordinate is in the upper left with +x to the right and +y down. After setting the Frame
along with a couple other properties, we call View.AddSubview
to add the UITextField
to the view hierarchy. This makes theusernameField
a subview of the UIView
instance that the View
property references. A subview is added with a z-order that is higher than its parent view, so it appears in front of the parent view on the screen.
The application with the UITextField
included is shown below:
We can add a UITextField
for the password in a similar fashion, only this time we set the SecureTextEntry
property to true, as shown below:
passwordField = new UITextField
{
Placeholder = "Enter your pasword",
BorderStyle = UITextBorderStyle.RoundedRect,
Frame = new RectangleF(10, 45, w - 20, h),
SecureTextEntry = true
};
View.AddSubview(passwordField);
Setting SecureTextEntry = true
hides the text entered in the UITextField
by the user as shown below:
Adding the Button
Next, we'll add a button so the user can submit the username and password. The button is added to the view hierarchy like any other control, by passing it as an argument to the parent view's AddSubview
method again.
The following code adds the button and registers an event handler for the TouchUpInside
event:
submitButton = UIButton.FromType(UIButtonType.RoundedRect);
submitButton.Frame = new RectangleF(10, 90, w - 20, 44);
submitButton.SetTitle("Submit", UIControlState.Normal);
submitButton.TouchUpInside += delegate
{
Console.WriteLine("Submit button clicked");
};
View.AddSubview(submitButton);
With this in place the login screen now looks as shown below:
Handling Rotation
However, if the user rotates the device to landscape, the controls do not resize appropriately, as the following screenshot illustrates:
One way to fix this is by setting the AutoresizingMask
property on each view. In this case we want the controls to stretch horizontally, so we would set each AutoresizingMask
as:
AutoresizingMask = UIViewAutoresizing.FlexibleWidth
Now when we rotate the device or simulator, everything stretches to fill the additional space, as shown below:
Sizing for iPhone 5
Although the application will work fine on iPhone 5, it does not fill the entire screen when run in the iPhone 5 simulator or on a device, as illustrated below:
To fix this, we simply need to include a 640x1136 image named Default-568@2x.png. We can add this from the project properties in Visual Studio. Under the project properties, select iOS Application and scroll to theLaunch Images section:
Under Launch Images select the placeholder for the Retina Display (4-inch) image, and select a 640x1136 image in the Open dialog:
After selecting the image, its preview appears in the Launch Images section:
Also, it is added to the project under the Resources folder:
Now, when we run the app it fills the screen, as shown below:
Creating Custom Views
In addition to using controls that are part of UIKit, custom views can also be used. A custom view can be created by inheriting from UIView
and overriding Draw
. Let's create a custom view and add it to the view hierarchy to demonstrate.
Inheriting from UIView
The first thing we need to do is create a class for the custom view. We'll do this using the Class template in Visual Studio to add an empty class named CircleView
. The base class should be set to UIView, which recall is in the MonoTouch.UIKit
namespace. We'll also need the System.Drawing
namespace as well. The other various System.*
namespaces won't be used in this example, so feel free to remove them.
The class should look like this:
using System;
using System.Drawing;
using MonoTouch.UIKit;
using MonoTouch.CoreGraphics;
namespace CodeOnlyDemo
{
class CircleView : UIView
{
}
}
Drawing in a UIView
Every UIView
has a Draw
method that is called by the system when it needs to be drawn. Draw should never be called directly. It is called by the system during run loop processing. The first time through the run loop after a view is added to the view hierarchy, its Draw
method is called. Subsequent calls to Draw
occur when the view is marked as needing to be drawn by calling either SetNeedsDisplay
or SetNeedsDisplayInRect
on the view.
We can add drawing code to our view by adding such code inside the overridden Draw
method as shown below:
public override void Draw(RectangleF rect)
{
base.Draw(rect);
//get graphics context
using (CGContext g = UIGraphics.GetCurrentContext())
{
//set up drawing attributes
g.SetLineWidth(10);
UIColor.Green.SetFill();
UIColor.Blue.SetStroke();
//create geometry
var path = new CGPath();
path.AddArc(Bounds.GetMidX(), Bounds.GetMidY(), 50, 0, 2.0f * (float)Math.PI, true);
//add geometry to graphics context and draw it
g.AddPath(path);
g.DrawPath(CGPathDrawingMode.FillStroke);
}
}
Since CircleView
is a UIView
, we can also set UIView
properties as well. For example, we can set theBackgroundColor
in the constructor:
public CircleView()
{
BackgroundColor = UIColor.White;
}
To use the CircleView
we just created, we can either add it as a subview to the view hierarchy in an existing controller, like we did with the UILabels
and UIButton
earlier, or we can load it as the view of a new controller. Let's do the latter.
Loading a View
UIViewController
has a method named LoadView
that is called by the controller to create its view. This is an appropriate place to create a view and assign it to the controller's View
property.
First, we need a controller, so create a new empty class named CircleController
.
In CircleController
add the following code to set the View
to a CircleView
:
using MonoTouch.UIKit;
namespace CodeOnlyDemo
{
class CircleController : UIViewController
{
CircleView view;
public override void LoadView()
{
base.LoadView();
view = new CircleView();
View = view;
}
}
}
Finally, we need to present the controller at runtime. Let's do this in the submit button that we added earlier as follows:
submitButton.TouchUpInside += delegate
{
Console.WriteLine("Submit button clicked");
//circleController is declared a s class variable
circleController = new CircleController();
PresentViewController(circleController, true, null);
};
Now, when we run the applicaiton and tap the submit button, the new view with a circle is displayed:
Summary
In this article we discussed how to develop iOS applications using Visual Studio entirely in code. We looked at how to build up a project from an empty project template, discussing how to create and add a root view controller to the window. We then showed how to use controls from UIKit to create a view hierarchy within a controller to develop an application screen. Next we examined how to make the views lay out appropriately in different orientations, as well as how to create a full screen application on iPhone 5. Finally, we saw how to create a custom view by subclassing UIView
, as well as how to load the view within a controller.