Table of Contents

FlexCalc (iOS Native) (C# / Mobile / iOS-Native)

Note

This demo is available in your FlexCel installation at <FlexCel Install Folder>\samples\csharp\VS2026\Mobile\iOS-Native\FlexCalc and also at https:​//​github.​com/​tmssoftware/​TMS-​FlexCel.​NET-​demos/​tree/​master/​csharp/​VS2026/​Mobile/​iOS-​Native/​FlexCalc

Overview

This example shows how to use the FlexCel calculating engine as an interactive calculator on iOS.

How It Works

Under the hood, every text field in the app is mapped to a cell in a spreadsheet (A1, A2, A3...). Whenever a text field changes, the entire sheet is recalculated and the results are shown in the column on the right. You can use the full range of Excel functions in this demo and reference cells in the usual way (for example, you can write =A2 + 1 in the text field for A3).

As in Excel, any text that starts with = will be considered a formula.

The backing spreadsheet is saved when you exit the app and reloaded when you open it, so your formulas persist between sessions. The file is stored as config.xlsx in the app's private storage directory.

FlexCel Features Demonstrated

  • XlsFile: Creating and opening Excel files programmatically
  • SetCellFromString / GetCellValue: Reading and writing cell values
  • TFormula: Parsing formulas entered as text
  • Recalc(): Real-time recalculation of the entire workbook when any cell changes
  • Save with TExcelFileFormat.v2023: Saving workbooks in modern Excel format

Implementation Details

  • CalcEngine.cs: Shared business logic wrapping the FlexCel API (same logic as the Android version).
  • SceneDelegate.cs: Builds the UI programmatically (no storyboard) using a UITableView with custom CalcCell rows. Each row contains a label (A1, A2...), a formula input field, and a calculated value display. Uses monospace font for readability, interactive keyboard dismiss, and Safe Area layout guides for proper display on devices with a notch.

Files

AppDelegate.cs

namespace FlexCalc
{
    [Register("AppDelegate")]
    public class AppDelegate : UIApplicationDelegate
    {
        public override bool FinishedLaunching(UIApplication application, NSDictionary? launchOptions)
        {
            // Override point for customization after application launch.
            return true;
        }

        public override UISceneConfiguration GetConfiguration(UIApplication application, UISceneSession connectingSceneSession, UISceneConnectionOptions options)
        {
            // Called when a new scene session is being created.
            // Use this method to select a configuration to create the new scene with.
            // "Default Configuration" is defined in the Info.plist's 'UISceneConfigurationName' key.
            return new UISceneConfiguration("Default Configuration", connectingSceneSession.Role);
        }

        public override void DidDiscardSceneSessions(UIApplication application, NSSet<UISceneSession> sceneSessions)
        {
            // Called when the user discards a scene session.
            // If any sessions were discarded while the application was not running, this will be called shortly after 'FinishedLaunching'.
            // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
        }
    }
}

CalcEngine.cs

using FlexCel.Core;
using FlexCel.XlsAdapter;

namespace FlexCalc
{
    public class CalcEngine
    {
        readonly XlsFile xls = new(1, FlexCel.Core.TExcelFileFormat.v2023, true);
        readonly string ConfigFile = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "config.xlsx");

        readonly string[] Predefined =
        [
            "5", "=A1 * 3 + 7", "=Sum(A1, A2) * 9", "=Sin(a1) + cos(a2)", "=Average(a1:a4)",
            "", "", "", "", "", "", "", "", "", "", ""
        ];

        public CalcEngine()
        {
            bool Restoring = false;
            if (File.Exists(ConfigFile))
            {
                try
                {
                    xls.Open(ConfigFile);
                    Restoring = true;
                }
                catch
                {
                    //if the file is corrupt, we'll just ignore it.
                    //Restoring will be false, and we will create a new file.
                }
            }

            if (!Restoring)
            {
                xls.NewFile(1, TExcelFileFormat.v2023);
                for (int k = 0; k < Predefined.Length; k++)
                {
                    xls.SetCellFromString(k + 1, 1, Predefined[k]); //Initialize the grid with something so users know what they have to do.
                }
            }

            xls.Recalc();
        }

        public string GetRowValue(int row)
        {
            object cell = xls.GetCellValue(row, 1);
            if (cell == null)
                return "";
            if (cell is TFormula fmla)
            {
                return Convert.ToString(fmla.Result) ?? "";
            }

            return Convert.ToString(cell) ?? "";
        }

        public string GetRowFormulaText(int row)
        {
            object cell = xls.GetCellValue(row, 1);
            if (cell == null)
                return "";
            if (cell is TFormula fmla)
            {
                return fmla.Text ?? "";
            }

            return Convert.ToString(cell) ?? "";
        }

        public string SetRowFormula(int row, string value)
        {
            xls.SetCellFromString(row, 1, value);
            xls.Recalc();
            xls.Save(ConfigFile);
            return GetRowValue(row);
        }

        public int GetRowCount()
        {
            return Math.Min(xls.RowCount, 100); //ensure it isn't too big, we only want to display 100 rows.
        }



    }
}

Main.cs

using FlexCalc;

// This is the main entry point of the application.
// If you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));

SceneDelegate.cs

namespace FlexCalc
{
    [Register("SceneDelegate")]
    public class SceneDelegate : UIResponder, IUIWindowSceneDelegate
    {
        static CalcEngine? _engine;
        public static CalcEngine Engine => _engine ??= new CalcEngine();

        UITableView? tableView;

        [Export("window")]
        public UIWindow? Window { get; set; }

        [Export("scene:willConnectToSession:options:")]
        public void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
        {
            if (scene is UIWindowScene windowScene)
            {
                Window ??= new UIWindow(windowScene);

                var vc = new UIViewController();
                vc.Title = "FlexCalc";
                vc.View!.BackgroundColor = UIColor.SystemBackground;

                tableView = new UITableView(CGRect.Empty, UITableViewStyle.Plain);
                tableView.TranslatesAutoresizingMaskIntoConstraints = false;
                tableView.RegisterClassForCellReuse(typeof(CalcCell), CalcCell.CellId);
                tableView.KeyboardDismissMode = UIScrollViewKeyboardDismissMode.Interactive;

                var source = new CalcTableSource(this);
                tableView.Source = source;

                vc.View.AddSubview(tableView);

                NSLayoutConstraint.ActivateConstraints(new[]
                {
                    tableView.TopAnchor.ConstraintEqualTo(vc.View.SafeAreaLayoutGuide.TopAnchor),
                    tableView.LeadingAnchor.ConstraintEqualTo(vc.View.LeadingAnchor),
                    tableView.TrailingAnchor.ConstraintEqualTo(vc.View.TrailingAnchor),
                    tableView.BottomAnchor.ConstraintEqualTo(vc.View.BottomAnchor),
                });

                var nav = new UINavigationController(vc);
                Window.RootViewController = nav;
                Window.MakeKeyAndVisible();
            }
        }

        public void ReloadValues()
        {
            if (tableView == null) return;
            foreach (var cell in tableView.VisibleCells)
            {
                if (cell is CalcCell cc)
                    cc.UpdateValue();
            }
        }

        [Export("sceneDidDisconnect:")]
        public void DidDisconnect(UIScene scene) { }

        [Export("sceneDidBecomeActive:")]
        public void DidBecomeActive(UIScene scene) { }

        [Export("sceneWillResignActive:")]
        public void WillResignActive(UIScene scene) { }

        [Export("sceneWillEnterForeground:")]
        public void WillEnterForeground(UIScene scene) { }

        [Export("sceneDidEnterBackground:")]
        public void DidEnterBackground(UIScene scene) { }
    }

    class CalcTableSource : UITableViewSource
    {
        readonly SceneDelegate sceneDelegate;

        public CalcTableSource(SceneDelegate sceneDelegate)
        {
            this.sceneDelegate = sceneDelegate;
        }

        public override nint RowsInSection(UITableView tableview, nint section)
        {
            return SceneDelegate.Engine.GetRowCount();
        }

        public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
        {
            var cell = (CalcCell)tableView.DequeueReusableCell(CalcCell.CellId, indexPath);
            cell.Configure(indexPath.Row + 1, sceneDelegate);
            return cell;
        }

        public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
        {
            return 44;
        }
    }

    class CalcCell : UITableViewCell
    {
        public const string CellId = "CalcCell";

        readonly UILabel rowLabel;
        readonly UITextField formulaField;
        readonly UILabel valueLabel;

        int row;
        SceneDelegate? sceneDelegate;

        [Export("initWithStyle:reuseIdentifier:")]
        public CalcCell(UITableViewCellStyle style, string reuseIdentifier) : base(style, reuseIdentifier)
        {
            SelectionStyle = UITableViewCellSelectionStyle.None;

            rowLabel = new UILabel
            {
                TranslatesAutoresizingMaskIntoConstraints = false,
                Font = UIFont.GetMonospacedSystemFont(14, UIFontWeight.Medium),
                TextColor = UIColor.SecondaryLabel,
                TextAlignment = UITextAlignment.Center,
            };

            formulaField = new UITextField
            {
                TranslatesAutoresizingMaskIntoConstraints = false,
                Font = UIFont.GetMonospacedSystemFont(14, UIFontWeight.Regular),
                BorderStyle = UITextBorderStyle.RoundedRect,
                AutocorrectionType = UITextAutocorrectionType.No,
                AutocapitalizationType = UITextAutocapitalizationType.None,
                ReturnKeyType = UIReturnKeyType.Done,
                ClearButtonMode = UITextFieldViewMode.WhileEditing,
            };

            valueLabel = new UILabel
            {
                TranslatesAutoresizingMaskIntoConstraints = false,
                Font = UIFont.GetMonospacedSystemFont(14, UIFontWeight.Regular),
                TextAlignment = UITextAlignment.Right,
                TextColor = UIColor.Label,
            };

            ContentView.AddSubview(rowLabel);
            ContentView.AddSubview(formulaField);
            ContentView.AddSubview(valueLabel);

            NSLayoutConstraint.ActivateConstraints(new[]
            {
                rowLabel.LeadingAnchor.ConstraintEqualTo(ContentView.LeadingAnchor, 8),
                rowLabel.CenterYAnchor.ConstraintEqualTo(ContentView.CenterYAnchor),
                rowLabel.WidthAnchor.ConstraintEqualTo(40),

                formulaField.LeadingAnchor.ConstraintEqualTo(rowLabel.TrailingAnchor, 4),
                formulaField.CenterYAnchor.ConstraintEqualTo(ContentView.CenterYAnchor),

                valueLabel.LeadingAnchor.ConstraintEqualTo(formulaField.TrailingAnchor, 4),
                valueLabel.TrailingAnchor.ConstraintEqualTo(ContentView.TrailingAnchor, -8),
                valueLabel.CenterYAnchor.ConstraintEqualTo(ContentView.CenterYAnchor),
                valueLabel.WidthAnchor.ConstraintEqualTo(formulaField.WidthAnchor),
            });

            formulaField.EditingChanged += OnTextChanged;
            formulaField.ShouldReturn = tf => { tf.ResignFirstResponder(); return true; };
        }

        public void Configure(int row, SceneDelegate sd)
        {
            this.row = row;
            this.sceneDelegate = sd;
            rowLabel.Text = $"A{row}";
            formulaField.Text = SceneDelegate.Engine.GetRowFormulaText(row);
            UpdateValue();
        }

        public void UpdateValue()
        {
            valueLabel.Text = SceneDelegate.Engine.GetRowValue(row);
        }

        void OnTextChanged(object? sender, EventArgs e)
        {
            SceneDelegate.Engine.SetRowFormula(row, formulaField.Text ?? "");
            sceneDelegate?.ReloadValues();
        }
    }
}