Reusable Turbo-iOS Project Configured Entirely From Your Rails App
Tags: rails, ios, turbo, turboios
Published: 2021-06-25 04:54:46

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](https://github.com/hotwired/turbo-ios)!

---

With [Turbo-iOS](https://github.com/hotwired/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](https://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](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](https://turbo.hotwire.dev/handbook/frames). Although it should work well with it, you really can use whatever additional backend frameworks like [Stimulus Reflex](https://docs.stimulusreflex.com), 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](https://github.com/hotwired/turbo-ios), they were all super helpful resources for me.
- [Hybrid iOS apps with Turbo by Joe Masilotti](https://masilotti.com/turbo-ios/hybrid-apps-with-turbo/)
- [Drifting Ruby Turbo Native for iOS by David Kimura](https://www.driftingruby.com/episodes/turbo-native-for-ios)
- [Native tab bar with Turbo-iOS by Bram Jetten](https://bramjetten.dev/articles/native-tab-bar-with-turbo-ios)

---

#### 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
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.
```javascript
import { Turbo } from "@hotwired/turbo-rails";
window.Turbo = Turbo;
```
Add any custom javascript to **turbo/bridge.js** in your javascript folder.
```javascript
export default class Bridge {
  static sayHello() {
    document.body.innerHTML = "<h1>Hello!</h1>"
  }
}
```

Then import this in your **application.js** file.
```javascript
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`
```ruby
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.
```erb
<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. 
```erb
<% 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.
```ruby
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](https://hotpot.ai/free-icons?s=sfSymbols).

---

#### Add Turbo Route
In your **routes.rb** add route pointing to `turbo#index`.
```ruby
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](https://github.com/hotwired/turbo-ios)!

---

If you find [this project](https://github.com/dalezak/turbo-ios-base) useful or have suggestions on improvements, _please let me know!_