left-icon

Writing Native Mobile Apps in a Functional Language Succinctly®
by Vassili Kaplan

Previous
Chapter

of
A
A
A

CHAPTER 3

Placing Widgets on the Screen

Placing Widgets on the Screen


“Any sufficiently advanced technology is indistinguishable from magic.”

Arthur C. Clarke

Layout concepts in CSCS

Different frameworks use different layout methods for placing widgets on the screen—most allow you to place widgets on the screen programmatically. Many also allow you to visually drag-and-drop a widget anywhere on the screen.

For developing iOS apps with XCode, Apple created the Auto Layout concept and the Storyboard. The widgets are placed on the screen according to the constraints among themselves. When you run your app on a device or in a simulator, the layout may look different from what you see on a Storyboard, where you dropped your widgets. This is due to the different sizes of the Storyboard for different devices.

For Android development, the layout is often created defining the placement rules in the XML layout files. Xamarin.Forms uses XAML files.

For CSCS development, we won’t have a familiar drag-and-drop functionality. All the placements of widgets are done programmatically. It is the same code for Android and for iOS.

In exchange, we will have more control over where to place the widgets. The learning curve is quicker—there are no XML schemas, no XAML files, and no Auto Layouts (I personally think anyone who understands the workable concept of Auto Layouts in under a week is a genius of our time).

For the layout definition, I used a mixture of the iOS and Android approaches. From the Auto Layout, I applied the following—for the unique widget location, I need to have three concepts:

  • Widget horizontal placement (relative to other widgets or to the main window).
  • Widget vertical placement (relative to other widgets or to the main Window).
  • Widget size.

The widget size can be absolute or relative. In the latter case, it will be automatically extended or contracted, depending on the device size.

That’s it! I am not sure why the horizontal and vertical placements are allowed to be defined multiple times, and inconsistently, in both iOS and Android, leading to conflicts; these conflicts may be resolved with unexpected results during runtime. In CSCS, no conflicts are possible by its layout design (how it works).

For the implementation, I took an approach similar to the concept of the Relative Layout in Android. The difference with RelativeLayout is that it’s not possible to have multiple definitions of the widget placement parameters—in Android, you can use the ApplyRule() method for setting the widget placement rules an unlimited number of times, and Android won’t complain about any contradicting rules.

Let’s now start with the implementation. Some parts of what follows have been published in CODE Magazine and MSDN Magazine.

Locations in CSCS

A widget definition in CSCS has either one or two statements. The first statement is the definition of a location—where to place the widget—and the second one is the actual widget definition, including its size.

Tip: The same location can be used for different widgets.

One scenario is to show the user multiple widgets, one at a time, located in the same place—an example of that is shown in sfscript.cscs.

We already saw an example of placing widgets in Code Listing 1:

locSizeLabel = GetLocation(buttonClickme, "CENTER",

                         buttonClickme, "BOTTOM");
AddLabel(locSizeLabel, "sizeLabel"""36060);

The general syntax of the location command is the following:

GetLocation(HorizontalReference, PlacementX,

            VerticalReference,   PlacementY,

            AdjustmentX = 0,     AdjustmentY = 0,

            UseScale = true,     ScaleValue = 0.0,

            Parent = null);

Here is the meaning of the parameters:

  • HorizontalReference: The name of another widget for the horizontal placement. It can be the string ROOT, meaning the parent widget or the main screen.
  • PlacementX: A horizontal placement relative to the widget in the HorizontalReference. We’ll discuss possible values below.
  • VerticalReference: The name of another widget for the vertical placement. It can be the string ROOT, meaning the parent widget or the main screen.
  • PlacementY: A vertical placement relative to the widget in the VerticalReference.
  • AdjustmentX: The number of pixels the widget must be moved horizontally (this number can be relative, depending on the AutoScale option which we will discuss below). It can also be negative: the positive direction goes from left to right.
  • AdjustmentY: The number of pixels the widget must be moved vertically. It can also be negative: the positive direction goes from top to bottom. See also AutoScale below.
  • UseScale: Whether to apply a scaling option to the widget. If true, the adjustment specified in ScaleValue, discussed next, will be used.
  • ScaleValue: The measure used for adjusting the size of this widget. It overrides the value provided as an argument for the AutoScale() function.
  • Parent: The parent of this widget. If null, the widget will be added to the Main Layout on Android or to the Root View Controller View on iOS.

The possible values for the PlacementX and PlacementY parameters are very similar to the constants of the Android RelativeLayout.LayoutParams class.

They can be any of the following: CENTER, LEFT, RIGHT, TOP, BOTTOM, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_TOP, ALIGN_BOTTOM, ALIGN_PARENT_LEFT, ALIGN_PARENT_RIGHT, ALIGN_PARENT_TOP, or ALIGN_PARENT_BOTTOM.

The GetLocation() function is used for both iOS and Android. Check out its implementation for iOS in Code Listing 11. The Android implementation is similar.

Code Listing 11: Implementation of the GetLocationFunction Class on iOS

public class GetLocationFunction : ParserFunction
{
  protected override Variable Evaluate(ParsingScript script)
  {
    List<Variable> args = script.GetFunctionArgs();

    Utils.CheckArgs(args.Count, 4, m_name);

    string viewNameX = args[0].AsString();
    string ruleStrX  = args[1].AsString();
    string viewNameY = args[2].AsString();
    string ruleStrY  = args[3].AsString();

    int leftMargin   = Utils.GetSafeInt(args, 4);
    int topMargin    = Utils.GetSafeInt(args, 5);

    bool autoResize  = Utils.GetSafeInt(args, 61) == 1;
    if (autoResize) {
      double multiplier = Utils.GetSafeDouble(args, 7);
      AutoScaleFunction.TransformSizes(ref leftMargin, ref topMargin,
                   (int)UtilsiOS.GetRealScreenWidth(), multiplier);
    }

    Variable parentView = Utils.GetSafeVariable(args, 8null);

    UIView referenceViewX = iOSVariable.GetView(viewNameX, script);
    UIView referenceViewY = iOSVariable.GetView(viewNameY, script);
    iOSVariable location = new iOSVariable(UIVariable.UIType.LOCATION,
                viewNameX + viewNameY, referenceViewX, referenceViewY);

    location.SetRules(ruleStrX, ruleStrY);
    location.ParentView = parentView as UIVariable;

    double screenRatio = UtilsiOS.GetScreenRatio();
    location.TranslationX = (int)(leftMargin / screenRatio);
    location.TranslationY = (int)(topMargin / screenRatio);

    return location;
  }
}

To register the GetLocation() function with the parser, the following command must be added in the initialization phase (see Code Listing 4):

ParserFunction.RegisterFunction("GetLocation"new GetLocationFunction());

Adding widgets

Once we have a location, we can create a widget. The general structure of this function is:

AddWidget(widgetType, location, widgetName, initString, width, height);

There are shortcuts of this command for each widget type, for example:

AddLabel(location,     widgetName, initString, width, height);
AddButton(location,    widgetName, initString, width, height);
AddImageView(location, widgetName, initString, width, height);

And so on. Calling the functions above is equivalent to calling the following:

AddWidget("Label",     location, widgetName, initString, width, height);
AddWidget("Button",    location, widgetName, initString, width, height);
AddWidget("ImageView", location, widgetName, initString, width, height);

Here is the meaning of the parameters:

  • location: The location of the widget, defined in the previous section.
  • widgetName: the name of the widget. It will be always a global variable, even if used inside of a function.
  • initString: The initialization parameter of the widget. For example, it will be a text on a label or a title on a button.
  • width: The width of the widget.
  • height: The height of the widget.

Check out a fragment of the AddWidget() function implementation in Code Listing 12. The implementation is for iOS. The Android implementation is very similar.

Code Listing 12: A Fragment of the AddWidgetFunction Class

public class AddWidgetFunction : ParserFunction
{
  public AddWidgetFunction(string widgetType = ""string extras = "")
  {
    m_widgetType = widgetType;
    m_extras = extras;
  }
  protected override Variable Evaluate(ParsingScript script)
  {
    string widgetType = m_widgetType;
    int start = string.IsNullOrEmpty(widgetType) ? 1 : 0;
    List<Variable> args = script.GetFunctionArgs();
    Utils.CheckArgs(args.Count, 2 + start, m_name);

    if (start == 1) {
      widgetType = args[0].AsString();
      Utils.CheckNotEmpty(script, widgetType, m_name);
    }

    iOSVariable location = args[start] as iOSVariable;
    Utils.CheckNotNull(location, m_name);

    double screenRatio = UtilsiOS.GetScreenRatio();

    string varName = args[start + 1].AsString();
    string config = Utils.GetSafeString(args, start + 2);
    int width = (int)(Utils.GetSafeInt(args, start + 3) / screenRatio);
    int height = (int)(Utils.GetSafeInt(args, start + 4) / screenRatio);

    bool autoResize = Utils.GetSafeInt(args, start + 51) == 1;
    if (autoResize) {
      double multiplier = Utils.GetSafeDouble(args, start + 6);
      AutoScaleFunction.TransformSizes(ref width, ref height,
      (int)UtilsiOS.GetRealScreenWidth(), multiplier);
    }

    CGSize parentSize = location.GetParentSize();

    location.X = UtilsiOS.String2Position(location.RuleX,
                    location.ViewX, location, parentSize, true);
    location.Y = UtilsiOS.String2Position(location.RuleY,
                    location.ViewY, location, parentSize, false);

    location.X += location.TranslationX;
    location.Y += location.TranslationY;

    CGRect rect = new CGRect(location.X, location.Y, width, height);

    iOSVariable widgetFunc = GetWidget(widgetType, varName,config, rect);

    var currView = location.GetParentView();
    currView.Add(widgetFunc.ViewX);

    iOSApp.AddView(widgetFunc);

    ParserFunction.AddGlobal(varName, new GetVarFunction(widgetFunc));
    return widgetFunc;
  }

  public static iOSVariable GetWidget(string widgetType,
                 string widgetName, string initArg, CGRect rect)
  {
    for (int i = 0; i < UIVariable.WidgetTypes.Count; i++) {
      iOSVariable var = UIVariable.WidgetTypes[i] as iOSVariable;
      var widget = var.GetWidget(widgetType, widgetName, initArg, rect);
      if (widget != null) {
        return widget;
      }
    }
    return null;
  }
}

The code responsible for the actual creation of the widgets is in the iOSVariable class. Its fragment is shown in Code Listing 13.

For complete implementation, including the implementation of the function UtilsiOS.String2Position(), which returns an actual point on the screen, refer to the accompanying source code.

Code Listing 13: A Fragment of the iOSVariable.GetWidget Method

public virtual iOSVariable GetWidget(string widgetType, string widgetName,                                       string initArg, CGRect rect)
{
  UIVariable.UIType type = UIVariable.UIType.NONE;
  UIView widget = null;

  iOSVariable widgetFunc = null;
  switch (widgetType) {

    case "Button":
      type = UIVariable.UIType.BUTTON;
      widget = new UIButton(rect);      

      ((UIButton)widget).SetTitle(initArg, UIControlState.Normal);
      AddBorderFunction.AddBorder(widget);
      break;
    case "Label":
      type = UIVariable.UIType.LABEL;
      widget = new UILabel(rect);

      ((UILabel)widget).Text = initArg;
      break;
    case "TextEdit":

    //All other widgets go here ...
  }
  if (widgetFunc == null) {

    widgetFunc = new iOSVariable(type, widgetName, widget);

  }

  return widgetFunc;
}

To register the functionality of adding new widgets with the parser, the following commands must be added in the initialization phase (see Code Listing 4):

ParserFunction.RegisterFunction("AddWidget"new AddWidgetFunction());
ParserFunction.RegisterFunction("AddButton",

                                new AddWidgetFunction("Button"));
ParserFunction.RegisterFunction("AddLabel",

                                new AddWidgetFunction("Label"));

And so on, for each widget.

Implementing device orientation changes

It is not so straightforward to write an app that has different layouts on different device orientations. Even the heavyweights, like LinkedIn, Facebook, Amazon, Uber, The Weather Channel, and many others, have implemented their apps only in the Portrait mode (at least for iOS, at the time of this writing); nothing happens with these apps when you change your device orientation.

In this section our goal is to simplify changing the layouts when the device orientation changes.

Here we are going to see the Android implementation (you can find the iOS implementation in the accompanying source code).

First of all, we must add the following in the MainActivity class definition:

ConfigurationChanges = ConfigChanges.ScreenSize |
                       ConfigChanges.Orientation |

                       ConfigChanges.KeyboardHidden

Basically, it means that the app will handle the orientation changes by itself. Without this change, the MainActivity.OnCreate() method would be called every time there is a change in the orientation (there are also some other cases when it would be called, in particular when the app comes back from the background to the foreground).

Next, the MainActivity.OnConfigurationChange() method is overridden:

public override void OnConfigurationChanged(Configuration newConfig)
{
  OnOrientationChange?.Invoke(newConfig.Orientation == 

                       Orientation.Portrait ? "Portrait" : "Landscape");
  base.OnConfigurationChanged(newConfig);

}

It uses the OnOrientationChange event handler, defined as follows:

public delegate void OrientationChange(string newOrientation);

public static OrientationChange OnOrientationChange;

Now we are ready to register the CSCS functions to be triggered on orientation changes:

ParserFunction.RegisterFunction("RegisterOrientationChange",

                             new RegisterOrientationChangeFunction());

Code Listing 14 shows the implementation of the RegisterOrientationChangeFunction class.

Code Listing 14: Implementation of the RegisterOrientationChangeFunction Class

public class RegisterOrientationChangeFunction : ParserFunction
{
  static string m_actionPortrait;
  static string m_actionLandscape;
  static string m_currentOrientation;

  protected override Variable Evaluate(ParsingScript script)
  {
    List<Variable> args = script.GetFunctionArgs();

    Utils.CheckArgs(args.Count, 2, m_name);

    m_actionPortrait  = Utils.GetSafeString(args, 0);
    m_actionLandscape = Utils.GetSafeString(args, 1);
    bool startNow     = Utils.GetSafeInt(args, 21) != 0;

    if (startNow) {
      PerformAction(MainActivity.Orientation, true);
    }

    MainActivity.OnOrientationChange += (newOrientation) => {

      DeviceRotated();
    };
    return Variable.EmptyInstance;
  }
  static void DeviceRotated()
  {
    string currentOrientation = MainActivity.Orientation;
    if (m_currentOrientation == currentOrientation) {

      return;
    }

    PerformAction(currentOrientation);
  }
  static void PerformAction(string orientation, bool isInit = false)
  {
    m_currentOrientation = orientation;
    int currentTab = MainActivity.CurrentTabId;

    if (!isInit) {
      MainActivity.RemoveAll();
    }

    string action = orientation.Contains("Portrait") ? 
                                m_actionPortrait: m_actionLandscape;

    UIVariable.GetAction(action, "\"ROOT\"",

            "\"" + (isInit ? "init" : m_currentOrientation) + "\"");

    if (!isInit && currentTab >= 0) {
      MainActivity.TheView.ChangeTab(currentTab);
    }
  }
}

The UIVariable.GetAction() function executes the passed action. The action is usually the name of a CSCS function. The next section shows an example of changing the device layout on orientation changes.

Example: Currency Convertor

In this section, we are going to create the Currency Convertor app in CSCS from scratch. As the source of the currency rates, we’ll be using the currencylayer.com service. Their site provides an easy-to-use web service, with the first 1,000 requests per month provided for free. It also provides the exchange rates for Bitcoin, which will be the theme of this app. To get data, you need a unique key, which you must supply with every request. You get that key upon registering at their website.

Let’s first see how the app looks in portrait mode in Figure 3, and in landscape mode in Figure 4, for both iPhone and Android phones.

Currency Convertor in the Portrait Mode for iOS (left) and Android (right)

Figure 3: Currency Convertor in the Portrait Mode for iOS (left) and Android (right)

Currency Convertor in the Landscape Mode for iOS (top) and Android (bottom)

Figure 4: Currency Convertor in the Landscape Mode for iOS (top) and Android (bottom)

Code Listing 15 contains the complete app implementation. The functions on_about and on_refresh are the callbacks registered with the parser via the AddAction function.

The on_about function will be executed when the user clicks on the Powered by button in the Settings tab (this tab is not shown on the screenshots). The on_refresh function is called when the user clicks on the Convert button—the actual rate calculation will be done then.

Code Listing 15: The Implementation of the Currency App in CSCS

function on_about(sender, arg)
{
  OpenUrl("http://www.currencylayer.com");
}

function on_refresh(sender, arg)
{
  currency1 = GetText(cbCurrency);
  currency2 = GetText(cbCurrency2);

  currency_request(currency1, currency2);
}

function currency_request(currency1, currency2)
{
  if (currency1 == currency2) {
    // A shortcut for the same currency:
    time = Now("HH:mm:ss");
    date = Now("yyyy/MM/dd");
    rate = 1;
  } else {
    url = apiUrl + currency1 + "," + currency2;
    try {
      data = WebRequest(url);
    } catch (exception) {
      WriteConsole(exception.Stack);
      ShowToast("Couldn't get rates. " + exception);
      SetText(labelRateValue, "Error");
      return;
    }

    // To parse: {"success":true,"timestamp":1515937446,"source":"USD","quotes":{"USDEUR":0.819304,"USDCHF":0.967604}}
    WriteConsole(data);

    rate = -1;
    try {
      timestamp = StrBetween(data, "\"timestamp\":"",");
      time = Timestamp(timestamp, "HH:mm:ss");
      date = Timestamp(timestamp, "yyyy/MM/dd");
      rate1 = double(StrBetweenAny(data, "USD" + currency1 + "\":"",}"));
      rate2 = double(StrBetweenAny(data, "USD" + currency2 + "\":"",}"));
      WriteConsole("Extracted " + rate1 + "," + rate2);
      if (rate1 > 0) {
        rate = Substring(decimal(rate2 / rate1), 010);
      }
    } catch (exception) {
      WriteConsole(exception.Stack);
      ShowToast("Couldn't get rates. " + exception);
      SetText(labelRateValue, "Error");
      return;
    }
  }

  SetText(labelRateValue, rate);
  SetText(labelDateValue, date);
  SetText(labelTimeValue, time);
}

function init()
{
  currencies = { "EUR",   "USD",   "GBP",   "CHF",   "BTC""JPY",

                 "CNY",   "MXN",   "RUB",   "BRL",   "SAR"};
  flags      = { "eu_EU""en_US""en_GB""de_CH""BTC""ja_JP",

                 "zh_CN""es_MX""ru_RU""pt_BR""ar_SA"};

  AddWidgetData(cbCurrency, currencies);
  AddWidgetImages(cbCurrency, flags);
  SetSize(cbCurrency, 8040);
  SetText(cbCurrency, "BTC");

  AddWidgetData(cbCurrency2, currencies);
  AddWidgetImages(cbCurrency2, flags);
  SetSize(cbCurrency2, 8040);
  SetText(cbCurrency2, "CHF");

  SetImage(buttonRefresh, "coins");
  AddAction(buttonRefresh, "on_refresh");
  SetFontColor(buttonRefresh, "white");
  SetFontSize(buttonRefresh, 20);

  AddAction(aboutButton, "on_about");
  SetText(aboutButton, "Powered by currencylayer.com");

  labelsFontSize = 17;
  SetFontSize(aboutButton,    labelsFontSize);
  SetFontSize(labelRate,      labelsFontSize);
  SetFontSize(labelDate,      labelsFontSize);
  SetFontSize(labelTime,      labelsFontSize);
  SetFontSize(labelRateValue, labelsFontSize);
  SetFontSize(labelDateValue, labelsFontSize);
  SetFontSize(labelTimeValue, labelsFontSize);
}

function on_portrait(sender, arg)
{
  AddOrSelectTab("Rates""rates_active.png""rates_inactive.png");
  SetBackground("bitcoin_portrait.png");

  locCurrency = GetLocation("ROOT""LEFT""ROOT""TOP"1080);
  AddSfPicker(locCurrency, "cbCurrency""75"200380);
  SetBackgroundColor(cbCurrency, "white"0);
  SetFontColor(cbCurrency, "black");

  locCurrency2 = GetLocation("ROOT""RIGHT", cbCurrency, "CENTER", -20);
  AddSfPicker(locCurrency2, "cbCurrency2""75"200380);
  SetBackgroundColor(cbCurrency2, "white"0);
  SetFontColor(cbCurrency2, "black");

  locRateLabel = GetLocation("ROOT""CENTER", cbCurrency, "BOTTOM",

                             -8020);
  AddLabel(locRateLabel, "labelRate""Rate:"20080);
  SetFontColor(labelRate, "black");

  locRateValue = GetLocation("ROOT""CENTER", labelRate, "CENTER"100);
  AddLabel(locRateValue, "labelRateValue"""24080);
  SetFontColor(labelRateValue, "black");

  locDateLabel = GetLocation("ROOT""CENTER", labelRate, "BOTTOM", -80);
  AddLabel(locDateLabel, "labelDate""Date:"20080);
  SetFontColor(labelDate, "black");

  locDateValue = GetLocation("ROOT""CENTER", labelDate, "CENTER"100);
  AddLabel(locDateValue, "labelDateValue"""24080);
  SetFontColor(labelDateValue, "black");

  locTimeLabel = GetLocation("ROOT""CENTER", labelDate, "BOTTOM", -80);
  AddLabel(locTimeLabel, "labelTime""Time:"20080);
  SetFontColor(labelTime, "black");

  locTimeValue = GetLocation("ROOT""CENTER", labelTime, "CENTER"100);
  AddLabel(locTimeValue, "labelTimeValue"""24080);
  SetFontColor(labelTimeValue, "black");

  locRefresh = GetLocation("ROOT""CENTER""ROOT""BOTTOM"0, -4);
  AddButton(locRefresh, "buttonRefresh""Convert"200100);

  AddOrSelectTab("Settings""settings_active.png""settings_inactive.png");
  locAbout = GetLocation("ROOT""CENTER""ROOT""BOTTOM"0, -4);
  AddButton(locAbout, "aboutButton"""360100);
  AddBorder(aboutButton, 15"black");
  SetFontColor(aboutButton, "black");
}

function on_landscape(sender, arg)
{
  AddOrSelectTab("Rates""rates_active.png""rates_inactive.png");
  SetBackground("bitcoin_landscape.png");

  locCurrency = GetLocation("ROOT""LEFT""ROOT""CENTER"20);
  AddSfPicker(locCurrency, "cbCurrency""75"200380);
  SetBackgroundColor(cbCurrency, "black"0);
  SetFontColor(cbCurrency, "white");

  locCurren2 = GetLocation(cbCurrency, "RIGHT", cbCurrency, "CENTER"20);
  AddSfPicker(locCurren2, "cbCurrency2""75"200380);
  SetBackgroundColor(cbCurrency2, "black"0);
  SetFontColor(cbCurrency2, "white");

  locDateLabel = GetLocation("ROOT""CENTER""ROOT""CENTER"200);
  AddLabel(locDateLabel, "labelDate""Date:"10080);
  SetFontColor(labelDate, "white");

  locDateValue = GetLocation(labelDate, "RIGHT", labelDate, "CENTER"10);
  AddLabel(locDateValue, "labelDateValue"""22080);
  SetFontColor(labelDateValue, "white");

  locRateLabel = GetLocation("ROOT""CENTER", labelDate, "TOP"200);
  AddLabel(locRateLabel, "labelRate""Rate:"10080);
  SetFontColor(labelRate, "white");

  locRateValue = GetLocation(labelRate, "RIGHT", labelRate, "CENTER"10);
  AddLabel(locRateValue, "labelRateValue"""22080);
  SetFontColor(labelRateValue, "white");

  locTimeLabel = GetLocation("ROOT""CENTER", labelDate, "BOTTOM"200);
  AddLabel(locTimeLabel, "labelTime""Time:"10080);
  SetFontColor(labelTime, "white");

  locTimeValue = GetLocation(labelTime, "RIGHT", labelTime, "CENTER"10);
  AddLabel(locTimeValue, "labelTimeValue"""22080);
  SetFontColor(labelTimeValue, "white");

  locRefresh = GetLocation("ROOT""CENTER""ROOT""BOTTOM"0, -4);
  AddButton(locRefresh, "buttonRefresh""Convert"18090);

  AddOrSelectTab("Settings""settings_active.png",

                             "settings_inactive.png");
  locAbout = GetLocation("ROOT""CENTER""ROOT""BOTTOM"0, -8);
  AddButton(locAbout, "aboutButton"""360100);
  AddBorder(aboutButton, 15"white");
  SetFontColor(aboutButton, "white");
}

AutoScale();

key = "<Place Your CurrencyLayer.com Key Here>";
apiUrl = "http://apilayer.net/api/live?access_key=" + key + "&currencies=";

RegisterOrientationChange("on_portrait""on_landscape");
init();

if (Orientation == "Portrait") {
  on_portrait("""");
else {
  on_landscape("""");
}

SelectTab(0);

Here is the CSCS function that registers the on_portrait function when the device is in portrait mode, and the on_landscape function in landscape mode:

RegisterOrientationChange("on_portrait""on_landscape");

Note that with CSCS, you can easily configure tabs with different images for cases when the tab is active or inactive. The function to add a new tab is:

AddTab(Tab_Name, Picture_Active, Picture_Inactive);

Using the following function:

AddOrSelectTab(Tab_Name, Picture_Active, Picture_Inactive);

When the tab already exists, it won’t be added, but just selected to place widgets in it. Figure 5 shows active and inactive tabs for an iOS device.

Active and Inactive Tabs on iOS

Figure 5: Active and Inactive Tabs on iOS

You probably noticed the funny currency pickers that we used with the AddSfPicker function, especially on Android. 

These are not the standard pickers, but the ones provided by Syncfusion. I will explain how to add Syncfusion widgets later on, but for now, note that they are treated in CSCS code as any other widgets.

Summary

In this chapter, we saw how to place widgets on the screen in a platform-independent manner. The key concept is a location, where we place a widget. The same location can be reused for placing different widgets. We also saw how this method of widget placement can be used for the device orientation changes.

So far we have seen mostly standard iOS and Android widgets. In the next chapter, we are going to see how to implement and use custom widgets in CSCS.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.