Reusable Turbo-iOS Project Configured Entirely From Your Rails App

Ever since I started building Rails app, I've dreamt about one day building mobile apps in Rails. Well, that day has finally arrived, thanks to Turbo-iOS!

With Turbo-iOS you can basically point an iOS app to your backend Rails app, it handles the native navbar functionality and pushing views however the content of those views is simply your backend web app.

Build high-fidelity hybrid apps with native navigation and a single shared web view. Turbo Native for iOS provides the tooling to wrap your Turbo 7-enabled web app in a native iOS shell. It manages a single WKWebView instance across multiple view controllers, giving you native navigation UI with all the client-side performance benefits of Turbo. - github.com/hotwired/turbo-ios

This is pretty amazing, however after playing with it a bit I soon discovered it still required a lot of custom Swift code and working in Xcode. For example if you wanted to show a tabbar in your app, or want to change your navbar colors. This is ok if you have some experience using Xcode and Swift, however it's a major barrier for everyone who hasn't used it before.

So this is why I created https://github.com/dalezak/turbo-ios-base, a Turbo-iOS base project that's driven entirely from your backend Rails app. Clone the project, follow the steps below and your iOS app will be driven entirely from your backend Rails app including navbar color, navbar buttons, tabbar color, tabbar tabs, triggering backend javascript and restricting functionality whether users are logged in or not.

I had five main goals for the Turbo-iOS Base Project:

  1. reusable base project that can be pointed to any Rails app
  2. app styling, tabs and navbar buttons driven from the server
  3. handle both authenticated and unauthenticated users
  4. all logic and functionality contained in a single Swift file
  5. no need for other developers to write any Swift code

Disclaimer: It's been over six years since I've written anything in Objective-C, and I've never written any Swift prior to this project. So the code has lots of room for improvement, refactoring, cleanup, etc.

Important Note: This Turbo-iOS Base Project doesn't tie you to only using Turbo Frames. Although it should work well with it, you really can use whatever additional backend frameworks like Stimulus Reflex, etc. In fact, the app I built this for is using SR on the backend.

If you haven't already, I highly recommend you read the following articles about Turbo-iOS, they were all super helpful resources for me.

Clone Repo

Clone this repo locally to get started.

git clone https://github.com/dalezak/turbo-ios-base.git

Update Target Information

  • open App.xcodeproj
  • click on App project
  • select App under Targets
  • change Display Name to the name of your app
  • change Bundle Identifier to your reverse domain name

Update Info.plist URLs

  • open Info.plist file
  • expand TURBO_URL item
  • change development to your local environment
  • change production to your production environment

Replace Asset Images

  • visit https://appicon.co
  • upload 1024 x 1024 image
  • click Generate button
  • replace Assets.xcassets in the project with downloaded file

Add Turbo Gem

Add turbo-rails to your Gemfile.

gem "turbo-rails"

Add Turbo Javascript

If you are using Yarn, then run

yarn add @hotwired/turbo-rails

If you are using NPM, then run

npm add @hotwired/turbo-rails

Import Turbo Javascript

Add the following code to your application.js file.

import { Turbo } from "@hotwired/turbo-rails";
window.Turbo = Turbo;

Add any custom javascript to turbo/bridge.js in your javascript folder.

export default class Bridge {
  static sayHello() {
    document.body.innerHTML = "<h1>Hello!</h1>"
  }
}

Then import this in your application.js file.

import Bridge from "../turbo/bridge.js";
window.bridge = Bridge;

Add Rails Helpers

In your Rails app, add the following helpers to your application_helper.rb

def turbo?
  request.user_agent.include?("Turbo-")
end

def turbo_ios?
  request.user_agent.include?("Turbo-iOS")
end

def turbo_android?
  request.user_agent.include?("Turbo-Android")
end

Add Authenticated Header

Add the following metatag to your <head> so the app knows if a user is logged in or not.

<meta name="turbo:authenticated" content="<%= user_signed_in? %>">

Hide Page Navigation

Since Turbo-iOS handles the native navbar, you don't need to show your page navigation anymore. Add unless turbo? check around where you usually render your navbar in your Rails app.

<% unless turbo? %>
  <nav class="d-block">
    <%= render 'partials/navbar' %>
  </nav>
<% end %>

Add Turbo Controller

Add turbo_controller.rb which will return turbo.json used for rules and settings, here's a sample to get you started.

class TurboController < ApplicationController
  def index
    render json: {
      "settings": {
        "navbar": {
          "background": "#888888",
          "foreground": "#ffffff"
        },
        "tabbar": {
          "background": "#888888",
          "selected": "#ffffff",
          "unselected": "#bbbbbb"
        },
        "tabs": [
          {
            "title": "Home",
            "visit": "/",
            "icon_ios": "house",
            "protected": false
          },
          {
            "title": "Profile",
            "visit": "/profile",
            "icon_ios": "person",
            "protected": true
          }
        ],
        "buttons": [
          {
            "path": "/",
            "side": "left",
            "icon_ios": "line.horizontal.3",
            "script": "window.bridge.showMenu();",
            "protected": false
          },
          {
            "path": "/",
            "side": "right",
            "title": "Add",
            "visit": "/posts/new",
            "protected": true
          }
        ]
      },
      "rules": [
        {
          "patterns": [
            "/new$",
            "/edit$"
          ],
          "properties": {
            "presentation": "modal"
          }
        },
        {
          "patterns": [
            "/users/login"
          ],
          "properties": {
            "presentation": "modal"
          }
        },
        {
          "patterns": [
            "/users/logout"
          ],
          "properties": {
            "presentation": "replace"
          }
        }
      ]
    }
    end 
end

To see all the available iOS icons you can use for navbar buttons or tabbar icons, visit https://hotpot.ai/free-icons?s=sfSymbols.

Add Turbo Route

In your routes.rb add route pointing to turbo#index.

get 'turbo', to: "turbo#index", as: :turbo

Write Beautiful Ruby

And that's it! Everything should now be configured including the app colors, tabs, navbar buttons, etc which are all driven from the turbo.json returned from the turbo_controller.rb.

Now the app tabs and navbar buttons should appear according to the protected property if the user is authenticated or not. Your navbar buttons can either visit a page or trigger javascript on your server.

The best part is you shouldn't need to write any Swift code, so you can focus on your backend Rails application. This is something I've dreamt about ever since I first started using Rails, and it's now possible thanks to Turbo-iOS!

If you find this project useful or have suggestions on improvements, please let me know!

20