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:
- reusable base project that can be pointed to any Rails app
- app styling, tabs and navbar buttons driven from the server
- handle both authenticated and unauthenticated users
- all logic and functionality contained in a single Swift file
- 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.
- Hybrid iOS apps with Turbo by Joe Masilotti
- Drifting Ruby Turbo Native for iOS by David Kimura
- Native tab bar with Turbo-iOS by Bram Jetten
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!
Top comments (5)
Amazing work here! I love that EVERYTHING is configurable via the Rails code without needing any Swift.
Wow! This is really cool, some very nice patterns in here! Will definitely steal this for my own projects. That meta tag for authentication is very neat.
Bookmarked a year ago as when tried it worked great. Thanks for posting. How would you handle device rotation and split view controller (ipad/larger screens iphones)?
How would settings for an app with a login (no tab bar) to reach home (with tab bar) would look?
If you want no tabs when user is logged out, but tabs when user is logged in:
In this case the tabbar will be hidden unless the user has logged in.