CHAPTER 2
“Design is how it works.”
Steve Jobs
Figure 2 contains a screenshot of a common Xamarin project using CSCS.
Scripting.Shared is a shared project containing the code common to both Android and iOS. In particular, it contains all the files needed for parsing the CSCS code. Parsing.cs contains the implementation of the split-and-merge algorithm.
ParserFunction.cscs contains the implementation of the base class ParserFunction. When adding a new functionality to CSCS, a new class will override the ParserFunction class (see Code Listing 2 in the previous chapter).
Functions.Flow.cs, Functions.Math.cs, and Functions.OS.cs contain the implementation of the basic functions, not related to the mobile devices (operations with strings, loops, exceptions, mathematical functions, etc.). Interpreter.cs is a class that glues together parsing functions with the parsing flow.
The Shared.Resources folder contains resources used by both platforms. In particular, it contains the CSCS scripts—I placed them directly under the Resources folder. For example, the code mentioned in Code Listing 1 is located in the Resources\fhello.cscs file. The Android and iOS projects have links to this file.
The drawable subfolder contains all the images used by both platforms. Each particular project, iOS, or Android, has its own Resources folder with links to the shared projects—therefore, changes in the shared folder will be automatically propagated to the iOS and Android folders.
You can also set up the project structure differently, for example, with each project having its own set of images of different sizes, depending on the screen size.
The functions common to iOS and Android CSCS are in the CommonFunctions.cs file in the shared project folder.
If iOS and Android code is different, the implementations are in the iOSFunctrions.cs file for iOS and in the DroidFunctions.cs file for Android, respectively.
If the code is different on both platforms, but the differences are minimal, you can still have it in the CommonFunctions.cs file using the preprocessor macros. See Code Listing 3 for an example of extracting the version number for an Android and an iOS device.
Code Listing 3: Implementation of the GetVersionNumberFunction Class
class GetVersionNumberFunction: ParserFunction |

Figure 2: General Structure of a CSCS Xamarin Project (On macOS)
The actual execution of the CSCS code happens inside of the CommonFunctions.RunScript() method shown in Code Listing 4. This method also registers all the common functions with the parser.
Code Listing 4: The Implementation of the RunScript Method
public static void RunScript(string fileName) string script = FileToString(fileName); public static void RegisterFunctions() new GetLocationFunction()); } |
CommonFunctions.RunScript() is called from CustomInit.InitAndRunScript(), which initializes project-specific data and registers all project-specific widget types and all custom functions with the parser. See Code Listing 5 for an example of such a method.
Code Listing 5: The Implementation of the CustomInit.InitAndRunScript Method
public static void InitAndRunScript() string fileName = "start.cscs"; // All custom functions go here ... // ... |
In particular, it specifies that the start.cscs script will run. Note that you can import other CSCS script files by calling the ImportFile() CSCS function from start.cscs, for example:
ImportFile("sfhello.cscs");
Tip: In this way you can have different apps with identical C# code but different CSCS code. You just specify a different name when calling the ImportFile() function in start.cscs. There are some examples of this in the accompanying source code.
Where in the workflow do we execute the CSCS script? The answer is different for iOS and Android.
For Android, it’s a bit trickier than for iOS: we have to execute the script after the global layout has been initialized so that the script not only can start adding widgets, but also has access to all of the layout information, like sizes, orientation, main window parameters, and so on.
Unfortunately, it’s too early to run the CSCS script at the end of the MainActivity.OnCreate() method, but fortunately, Android provides a way: we can register a Global Layout listener with a ViewTreeObserver object.
We register it not at the end of the MainActivity.OnCreate() method, but at the end of the MainActivity.OnResume() method, which is the best place according to the Android activity lifecycle. See
Code Listing 6 for details.
Code Listing 6: Running a CSCS Script on Android
// MainActivity bool m_scriptRun = false; protected override void OnResume() public class LayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener // Run CSCS Script here: |
We use the Boolean variable m_scriptRun to make sure that the CSCS script is executed only once, because we do not need to run the script every time the device orientation changes. In this case, we run only specific functions responsible for widget placement—we’ll see how to do that in the next chapter.
Note: Remove the m_scriptRun Boolean check if you want the script to run every time the device orientation changes or the application comes back from the background.
The situation is simpler in iOS—we can execute the CSCS script from the AppDelegate.FinishedLaunching() method.
In iOS, I created a custom view controller, with the method Run(), which actually runs the CSCS script: see Code Listing 7.
Code Listing 7: Running CSCS Script on iOS
public void Run() |
The base class for all of the widgets is UIVariable, which derives from the core CSCS variable class. It has a common functionality for iOS and for Android. A fragment of this class is described in
Code Listing 8: A Fragment of the UIVariable Class
public class UIVariable : Variable PICKER_VIEW, PICKER_IMAGES,LIST_VIEW, COMBOBOX, IMAGE_VIEW, SWITCH, SLIDER, STEPPER, SEGMENTED, CUSTOM UIVariable refViewY = null) public UIType WidgetType { get; set; } public double MinVal { get; set; } |
The iOSVariable class derives from UIVariable, and has a concrete implementation for iOS. Its fragment is shown in Code Listing 9. Its Android equivalent is shown in Code Listing 10.
Code Listing 9: A Fragment of the iOSVariable Class
public class iOSVariable : UIVariable public iOSVariable(UIType type, string name, public virtual iOSVariable GetWidget(string widgetType, string widgetName, string initArg, CGRect rect) public virtual double GetValue() double result = 0; SelectedRow; } } public virtual bool SetText(string text, string alignment = null) public virtual void AddData(List<string> data, string varName, string title, string extra) string varName, string title) public virtual bool SetNormalFont(double size = 0) public virtual bool SetBackgroundColor(string colorStr, double alpha = 1.0) public static UIView GetView(string viewName, ParsingScript script) public UIView GetParentView() UIView m_viewX; } |
Code Listing 10: A Fragment of the DroidVariable Class
public class DroidVariable: UIVariable public DroidVariable(UIType type, string name, View viewx = null, View viewy = null) : base(type, name) public void SetViewLayout(int width, int height) refView?.ViewLayout); string widgetName, string initArg, { /* ... */ } public static Size GetLocation(View view) Variable viewValue = func.GetValue(script); /* Same SetXXX and GetXXX functions as for iOSVariable class... */ View m_viewX; |
In this chapter, we reviewed the general structure of a project using CSCS scripting and where in the project workflow the CSCS code is executed. It is done differently for iOS and for Android. We skipped a few details, since there is really a lot of code. However, since the devil is in the details, for the full picture, check out the accompanying source code.
In the next chapter, we are going to dig into the details of placing widgets on the screen in a platform-independent way.