iOS

1. Introduction

The advised way to build is to use the build tool to compile and package your game for iOS. Doing it is a matter of preparing CastleEngineManifest.xml for your project (with game_units or ios_source defined), and then running castle-engine package --target=ios. This gives you a ready project that you can open, run and publish from Xcode.

Almost all engine examples, and all templates for "New Project" created by CGE editor, follow this approach. So you can just recompile them for iOS.

2. Supported iOS Devices

The engine should work with all devices running iOS 4.2 or newer. It was successfully tested with iOS 5.1, 7, 10, 11, 13, 14. The engine runs flawlessly on iPhone Simulator as well.

3. Building the App

3.1. Get macOS and Xcode

You will need any Mac computer with Xcode installed (available in Mac App Store, free).

Note
If you browse the documentation on http://wiki.freepascal.org/iPhone/iPod_development , it will talk about Xcode templates and proprietary iOS SDK headers. For the sake of developing games using Castle Game Engine, this can be ignored — we don’t need these headers or templates (since our entire GUI is drawn using GLES through Castle Game Engine).

Install also CocoaPods by sudo gem install cocoapods in the terminal.

3.2. Install FPC for macOS

You will need to install this before installing the cross-compiler.

  • Go to https://sourceforge.net/projects/freepascal/files/Mac%20OS%20X/

  • Select 3.2.0. (Usually we suggest here using the latest stable version, which is 3.2.2 now, but right now 3.2.2 has no precompiled cross-compiler for iOS.)

  • Download the regular compiler (for macOS) from there (like fpc-3.2.0.intel-macosx.dmg at the time of writing this).

The FPC compiler needs the "Xcode command-line developer tools" installed. In short, open /Applications/Utilities/Terminal and execute xcode-select --install.

Alternative approach to the installation is to use fpcupdeluxe. This works now great with latest stable FPC 3.2.2. You can install the regular FPC/Lazarus, and then the necessary cross-compilers (iOS/arm, iOS/aarch64, i-sim/x86_64).

See macOS for more information about developing desktop applications for macOS using Castle Game Engine.

3.3. Installing FPC cross-compilers for iPhone and iPhoneSimulator

You will need to have FPC cross-compilers able to create these OS/CPU combinations:

  • ios/arm and ios/aarch64 applications (for the actual iPhone device). Note: aarch64 is also called arm64.

  • iphonesim/x86_64 applications (for the iPhoneSimulator on Intel-based macs; it seems iPhoneSimulator is not available for Arm-based macs now).

It’s easiest to get them from the FPC:

3.4. Testing FPC cross-compilers

Before attempting the compilation of a full program, we advise testing that your fpc is installed OK and supports the necessary platforms.

First, test that you can compile for the necessary CPUs. The cross-compiler for each CPU is actually a different FPC executable, so the lines below will make an error immediately if you cannot cross-compile to the given CPU. The desired result is that they should answer No source file name in command line.

fpc -Px86_64  -l
fpc -Parm     -l
fpc -Paarch64 -l

Second, test the actual compilation.

cd /tmp/
echo 'library test_compilation; begin end.' > test_compilation.lpr

SIMULATOR_SDK='/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk'
fpc -Px86_64  -Tiphonesim -WP7.0 -XR${SIMULATOR_SDK} test_compilation.lpr

DEVICE_SDK='/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk'
fpc -Parm     -TiOS    -WP7.0 -Cfvfpv3 -Cparmv7 -XR${DEVICE_SDK} test_compilation.lpr
fpc -Paarch64 -TiOS    -WP7.0                   -XR${DEVICE_SDK} test_compilation.lpr
Note
If you use older FPC 3.2.0: The iOS targets should be called as -Tdarwin. The -TiOS is not available. See FPC 3.2.2 new features, FPC 3.2.2 user changes. For FPC >= 3.2.2, use -TiOS. The change was caused because now macOS is also available for Arm CPUs, so we needed to distinguish between iOS and macOS.

Every fpc invocation should create libtest_compilation.dylib.

The reasons behind some of these compiler options:

  • We test by compiling a library, not a program. Compiling a program may fail at linking with some issues that are not relevant for our task.

  • We use -WP5.1, otherwise the linking fails (error: symbol dyld_stub_binding_helper not found, normally in crt1.o/dylib1.o/bundle1.o for architecture armv7).

  • Actually we use later -WP7.0, corresponding to what CGE uses now.

  • The additional parameters (the -XRxxx, and -Cfvfpv3 -Cparmv7) come from the Getting Started - iOS.rtf file from the FPC for iOS package.

3.5. iPhone Simulator is not an iPhone emulator

The iPhone Simulator is not an emulator of a real iPhone device, i.e. it does not emulate the processor inside the iPhone. Rather, it’s a modified version of the normal (desktop) macOS system, running on a normal CPU, same CPU as your macOS is using.

This should help you understand why we did some things above, e.g. why do we have a special compilation target for iPhone Simulator (because we cannot just run in the simulator the application compiled for an actual iPhone), and why it works fast (because it doesn’t emulate the iPhone CPU).

This is in contrast to the Android emulator that can emulate Android CPUs (Arm or Aarch64, though can also work on x86_64) and can run your application in a mostly-regular Android OS and environment in a virtual machine.

3.6. Using the build tool

  1. Make sure the build tool is available on $PATH, so you can call castle-engine in the terminal.

  2. Run in terminal:

     cd <castle-engine>/examples/2d_dragon_spine_game/
     castle-engine package --target=ios

    Tip: You can speedup this process: The package command by default cleans and recompiles everything, to make sure everything is recompiled for the current mode (which is a release mode by default). When developing, it’s often useful to make this process quicker, and recompile only what changed. Do this by adding --fast option:

     cd <castle-engine>/examples/2d_dragon_spine_game/
     castle-engine package --target=ios --fast

    Another way to speedup the process is to rebuild only code. Add the --update-only-code option for this.

    Tip: You can include iOS simulator support: Simply add the --ios-simulator command-line option. By default this is off, as including simulator support makes build longer (2 more platforms to compile for) and often it is not necessary. Like this:

     cd <castle-engine>/examples/2d_dragon_spine_game/
     castle-engine package --target=ios --ios-simulator
  3. Run Xcode and open the resulting project in <castle-engine>/examples/2d_dragon_spine_game/castle-engine-output/ios/xcode_project/.

    Open the file with .xcworkspace extension in that directory. For now, all iOS projects use CocoaPods and so you need to open them through the .xcworkspace file, not just .xcodeproj.

  4. Run the project from Xcode (Command + R), to see your game working in an iPhone simulator.

    Tip: Running on a real, physical device: It works out of the box! You only need to set the Development Team in Xcode, or set <ios team="xxx" /> attribute in CastleEngineManifest.xml.

    Selecting a team in Xcode

3.7. Archive (deploy on iOS)

You can perform further automatic packaging using the command-line CGE build tool. Call this:

castle-engine package --target=iOS --package-format=ios-archive-ad-hoc

This generates an IPA file which you can distribute to your testers e.g. using TestFairy. The resulting IPA file will be inside the castle-engine-output/ios/build/ subdirectory. It is somewhat similar to Android APK, although distributing IPA to testers is more cumbersome (their devices need to be registered in your Apple developer account, and Xcode must know them at build-time to sign IPA for them).

Other options are available:

  • ios-archive-development

  • ios-archive-app-store. Note that this does not upload to the AppStore (although we’d like to extend this someday to do it, but it’s unsure whether it is actually possible). In effect this is not very useful in practice, and you do not need to use this to release your iOS application on the TestFlight / AppStore.

Note that these options automatically sign the application. To make it work,

  1. Register as a (paid) Apple Developer,

  2. Place your "team ID" in the iOS section of the CastleEngineManifest.xml,

  3. Release from Xcode once manually. Xcode will ask you to configure your team, store the team password in your keychain etc. Once it works, next time you can do it all automatically.

  4. If you do it through SSH (not in an interactive GUI session) you need to call

     security unlock-keychain login.keychain

    or

     security unlock-keychain -p YOUR-PASSWORD login.keychain

    to unlock the keychain before building.

  5. And now castle-engine package --target=iOS --package-format=ios-archive-... will work smoothly.

3.8. Upload to TestFlight and AppStore

To upload the application to the AppStore:

  • Create and configure your iOS application on https://appstoreconnect.apple.com/ . You will link there the uploaded build number later.

  • Package project for iOS (any --package-format=, including default --package-format=ios-xcode-project, is OK).

  • Open resulting project in castle-engine-output/ios/ in Xcode. Switch target to "Any device", press "Archive". In the resulting window (once archive is done) press "Distribute" selecting the option to upload to the store.

4. Debugging the Pascal Code without iOS

Same instructions as for Testing mobile (OpenGL ES) rendering without an Android apply here too, as the Pascal code is platform independent.

5. Known problems

  • When compiling with FPC 3.2.2, there are a lot of warnings

      clang: warning: using sysroot for 'MacOSX' but targeting 'iPhone' [-Wincompatible-sysroot]

    They can be ignored. We link with proper iOS libraries, don’t worry. We don’t yet know how to avoid this warning.

  • The event loop on iOS must be controlled by the main program in Objective-C, not in Pascal. This means that Application.ProcessMessages cannot work. The library cannot force the main process to handle some events, and wait for something to happen. This means that you cannot use Application.ProcessMessages, or things depending on them.

    In particular CastleMessages unit is mostly useless on iOS. Functions in this unit make modal windows, running a message loop inside and only returning when user exits — similar to ShowMessage in Lazarus LCL / Delphi VCL.

    To make modal windows on iOS, design your modal dialog as a view and "push" it, waiting for it to return to the previous view. See the views manual and examples linked there. To have ready-to-use modal dialogs, use CastleDialogViews unit with ready descendants of TCastleView class that implement simple dialog windows.

  • On iOS, the OpenGL(ES) context is created and destroyed solely by the Objective-C code, not Pascal. You cannot use the Window.Open and Window.Close from the Pascal code to force recreating OpenGL context. This follows our cross-platform code guidelines.

  • The touchesEnded / touchesCancelled events on iOS (that in turn determine CGE events like TCastleView.Release, TCastleWindow.OnRelease) have poor behavior.

    It seems to be implemented like "report that touch ends, or is cancelled, when the finger position doesn’t change for the 0.36 seconds". It seems to be done regardless of when did the user actually released the finger, and if the user actually released the finger. You can test this unfortunate behavior even with simple CGE example examples/user_interface/test_all_view_events. As a result:

    • If you press and release the finger, you will notice that CGE Release event is reported with small (0.36 sec) delay instead of being instant. This small delay sometimes matters in games — you will e.g. see buttonLeft in Container.MousePressed for a short time, even after user physically released the finger.

    • If you keep pressing your finger, but without any movement, then the CGE Release event will be reported (and the touch will no longer be tracked). Even if you keep holding the finger down.

    There’s no reliable solution, it seems to be just the way iOS behaves. Trying various, even crazy, things (like not calling super in touchXxx events, or touchesCancelled) in OpenGLController.m code doesn’t help. Your application simply must be ready for this iOS weirdness — the CGE Release event may happen too early, or too soon, in case user presses the screen quickly, or presses and holds without moving.

7. Alternative: Using the latest FPC development branch

Instead of using the stable FPC, you can try using the latest development version of FPC (main branch from FPC GitLab). You need to install 4 cross-compilers, for all 4 platforms that are included in the "iOS target":

  1. arm / iOS # use "darwin" instead of "iOS" with older FPC < 3.2.2

  2. aarch64 / iOS # use "darwin" instead of "iOS" with older FPC < 3.2.2

  3. x86_64/ iPhoneSimulator

An easy way to install cross-compilers is to use fpcupdeluxe. Notes:

  • iPhoneSimulator is called i-sim in the fpcupdeluxe UI.

  • Our build tool uses the fpc binary available on your $PATH environment variable. Make sure you create an appropriate symlink to make it call the FPC installed by fpcupdeluxe (this wiki page describes this).

When you compiled your own FPC version, you may want to use --fpc-version-iphone-simulator "" on the command-line to the build tool. Otherwise the build tool may try to call FPC for iPhone Simulator target with a different FPC version (because some standard FPC versions required this).


To improve this documentation just edit this page and create a pull request to cge-www repository.