iOS framework for making Turbo native apps

Overview

Turbo Native for iOS

Note: The Hotwire frameworks are presented in beta form. We're using them all in production with HEY, but expect that significant changes might be made in response to early feedback.


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.

Features

  • Deliver fast, efficient hybrid apps. Avoid reloading JavaScript and CSS. Save memory by sharing one WKWebView.
  • Reuse mobile web views across platforms. Create your views once, on the server, in HTML. Deploy them to iOS, Android, and mobile browsers simultaneously. Ship new features without waiting on App Store approval.
  • Enhance web views with native UI. Navigate web views using native patterns. Augment web UI with native controls.
  • Produce large apps with small teams. Achieve baseline HTML coverage for free. Upgrade to native views as needed.

Requirements

Turbo iOS is written in Swift 5.3 and requires iOS 12 or higher, but we'll most likely drop iOS 12 in the near future. It supports web apps using either Turbo 7 or Turbolinks 5. The Turbo iOS framework has no dependencies.

Note: You should understand how Turbo works with web applications in the browser before attempting to use Turbo iOS. See the Turbo 7 documentation for details. Ensure that your web app sets the window.Turbo global variable as it's required by the native apps:

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

Getting Started

The best way to get started with Turbo iOS to try out the demo app first to get familiar with the framework. The demo app walks you through all the basic Turbo flows as well as some advanced features. To run the demo, clone this repo and open Demo/Demo.xcworkspace in Xcode and run the Demo target. See Demo/README.md for more details about the demo. When you’re ready to start your own application, read through the rest of the documentation.

Documentation

Contributing

Turbo iOS is open-source software, freely distributable under the terms of an MIT-style license. The source code is hosted on GitHub. Development is sponsored by Basecamp.

We welcome contributions in the form of bug reports, pull requests, or thoughtful discussions in the GitHub issue tracker.

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.


© 2020 Basecamp, LLC

Comments
  • Form submissions with response HTML don't work

    Form submissions with response HTML don't work

    There's a potential bug in Turbo-iOS when it comes to submitting forms and rendering the response HTML.

    I notice the following flow when submitting a form in Turbo iOS:

    1. The form is submitted.
    2. The server issues a redirect upon successful form response.
    3. The redirect is followed, and the response is returned.
    4. The WebView bridge is alerted to a "Visit Proposed" event containing the response HTML.
    5. You're free to route the proposal as desired, usually ended with calling the "window.turboNative.visitLocationWithOptionsAndRestorationIdentifier" with the response HTML.

    The problem is, calling "window.turboNative.visitLocationWithOptionsAndRestorationIdentifier" containing response HTML returns the exact same page, completely ignoring the response HTML.

    If you try this in the demo app, it will work the first time and the first time only. Subsequent form submissions will continue to return the same page over and over again. This is because of a "Back Navigation" bug, where submitting the form the first time, the app thinks you hit the back button, and issues another get request.

    Walking through step by step in the Demo app, using Proxyman to see requests/responses to/from the server:

    1. Run the app
    2. Tap "Load a web page modally"
    3. Submit the form. Proxyman shows two get requests, the first from the redirect, the second from the above mentioned "Back Navigation" bug: The rendered screen is "correct" only because of the second get request.
    Screen Shot 2022-04-08 at 5 57 56 PM
    1. Hit the back button.

    2. Tap "Load a web page modally" again.

    3. Submit the form. Proxyman now shows one get request, as expected. However, the response HTML was ignored. This can be seen by comparing the time stamps of the response HTML with the time stamp rendered on screen:

    Screen Shot 2022-04-08 at 6 34 43 PM

    Walking through the "Back Navigation" bug:

    1. Run the app
    2. Tap "Load a web page modally"
    3. Place breakpoints on line 207 of "Session.swift" and line 47 of "Visit.swift".
    4. Submit the form.
    5. Breakpoint: The first time and first time only, a "visit did complete" message comes from the ScriptMessageHandler, setting the state of the Visit to complete. I have no idea why this event is sent, but it's the reason for the Back Navigation bug. Hit continue.
    6. Breakpoint Session's visitableViewWillAppear activates, and ends up in the "Navigating Backward" path, issuing another visit (GET request 2). It is this second GET request that gives the appearance of "correct" behavior when submitting the form the first time.
    Screen Shot 2022-04-08 at 6 09 48 PM

    Subsequent form submissions skip step 5, so on visitableViewWillAppear, it ends up in the "Navigating Forward" path, issuing only one GET request as it should. But because the response HTML is ignored by Turbo, it re-renders the same screen again and again. Refreshing the page renders the correct response.

    I believe this is bug in turbo.js, but I can't say for sure because I can't set breakpoints in the javascript code in Xcode. 😢 I need to do more research into how Turbo itself works.

    My current workaround is to set the response HTML to nil before passing the proposal to "window.turboNative.visitLocationWithOptionsAndRestorationIdentifier". This seems to trick Turbo into not knowing that the request came a redirected form submission, so it issues another GET request. Submitting two GET requests per form submission is not ideal, though.

    Screen Shot 2022-04-08 at 6 31 15 PM
    opened by izaguirrejoe 17
  • Delegate callback when web view process terminates

    Delegate callback when web view process terminates

    This is an intermediary solution for #50.

    In summary, WKWebView can terminate under certain circumstances when in the background. When a Turbo-powered app is re-opened after this occurs the app can be left in a weird state.

    This PR adds a new delegate callback, sessionWebViewProcessDidTerminate(_:), that developers can hook into to do what they see fit. @tony42, for example, is resetting the state of their app. I'm following that same approach.


    I'm not sure if/how this can be handled better inside of this library short of iOS 16 fixing the issue entirely. So this feels like the next best approach as it puts the power back in the developer's hands.

    opened by joemasilotti 14
  • Improve logging by displaying more relevant details and additional arguments

    Improve logging by displaying more relevant details and additional arguments

    The current debug logging is insufficient to understand what's going on behind the scenes and investigate issues. This is a step in the right direction.

    Previous log output:

    2022-06-30 03:29:06 +0000 - [ColdBootVisit] startVisit()
    2022-06-30 03:29:07 +0000 - [Bridge] ← pageLoaded
    2022-06-30 03:29:09 +0000 - [Bridge] ← visitProposed
    2022-06-30 03:29:09 +0000 - [JavaScriptVisit] startVisit()
    2022-06-30 03:29:09 +0000 - [Bridge] → window.turboNative.visitLocationWithOptionsAndRestorationIdentifier
    2022-06-30 03:29:09 +0000 - [Bridge] ← visitStarted
    2022-06-30 03:29:09 +0000 - [JavaScriptVisit] webView(_:didStartVisitWithIdentifier:hasCachedSnapshot:)
    2022-06-30 03:29:09 +0000 - [Bridge] ← visitRequestStarted
    2022-06-30 03:29:09 +0000 - [JavaScriptVisit] webView(_:didStartRequestForVisitWithIdentifier:date:)
    2022-06-30 03:29:09 +0000 - [Bridge] = window.turboNative.visitLocationWithOptionsAndRestorationIdentifier evaluation complete
    2022-06-30 03:29:09 +0000 - [Bridge] ← visitRequestFinished
    2022-06-30 03:29:09 +0000 - [JavaScriptVisit] webView(_:didFinishRequestForVisitWithIdentifier:date:)
    2022-06-30 03:29:09 +0000 - [Bridge] ← visitRequestCompleted
    2022-06-30 03:29:09 +0000 - [JavaScriptVisit] webView(_:didCompleteRequestForVisitWithIdentifier:)
    2022-06-30 03:29:09 +0000 - [Bridge] ← visitCompleted
    2022-06-30 03:29:09 +0000 - [JavaScriptVisit] webView(_:didCompleteVisitWithIdentifier:restorationIdentifier:)
    2022-06-30 03:29:09 +0000 - [Bridge] ← visitRendered
    2022-06-30 03:29:09 +0000 - [JavaScriptVisit] webView(_:didRenderForVisitWithIdentifier:)
    2022-06-30 03:29:11 +0000 - [JavaScriptVisit] startVisit()
    2022-06-30 03:29:11 +0000 - [Bridge] → window.turboNative.visitLocationWithOptionsAndRestorationIdentifier
    2022-06-30 03:29:11 +0000 - [Bridge] ← visitStarted
    2022-06-30 03:29:11 +0000 - [JavaScriptVisit] webView(_:didStartVisitWithIdentifier:hasCachedSnapshot:)
    2022-06-30 03:29:11 +0000 - [Bridge] = window.turboNative.visitLocationWithOptionsAndRestorationIdentifier evaluation complete
    2022-06-30 03:29:11 +0000 - [Bridge] ← visitCompleted
    2022-06-30 03:29:11 +0000 - [JavaScriptVisit] webView(_:didCompleteVisitWithIdentifier:restorationIdentifier:)
    2022-06-30 03:29:11 +0000 - [Bridge] ← visitRendered
    2022-06-30 03:29:11 +0000 - [JavaScriptVisit] webView(_:didRenderForVisitWithIdentifier:)
    

    New log output:

    2022-06-30 03:30:11 +0000 [Session] visit ["reload": false, "location": https://turbo-native-demo.glitch.me, "options": Turbo.VisitOptions(action: Turbo.VisitAction.replace, response: nil)]
    2022-06-30 03:30:11 +0000 [ColdBootVisit] startVisit https://turbo-native-demo.glitch.me [:]
    2022-06-30 03:30:12 +0000 [Bridge] ← pageLoaded ["timestamp": 1656559812055, "restorationIdentifier": 42918dac-ca08-4d87-ae88-d866b21e9e14] [:]
    2022-06-30 03:30:12 +0000 [ColdBootVisit] completeVisit https://turbo-native-demo.glitch.me [:]
    2022-06-30 03:30:18 +0000 [Bridge] ← visitProposed ["location": https://turbo-native-demo.glitch.me/one, "options": {
        action = advance;
    }, "timestamp": 1656559818238] [:]
    2022-06-30 03:30:18 +0000 [Session] visit ["options": Turbo.VisitOptions(action: Turbo.VisitAction.advance, response: nil), "location": https://turbo-native-demo.glitch.me/one, "reload": false]
    2022-06-30 03:30:18 +0000 [JavascriptVisit] startVisit https://turbo-native-demo.glitch.me/one [:]
    2022-06-30 03:30:18 +0000 [Bridge] → window.turboNative.visitLocationWithOptionsAndRestorationIdentifier [Optional("https://turbo-native-demo.glitch.me/one"), Optional({
        action = advance;
    }), nil] [:]
    2022-06-30 03:30:18 +0000 [Bridge] ← visitStarted ["identifier": 6cd858b3-5a0c-4695-a5b0-48597a97ea1c, "hasCachedSnapshot": 0, "timestamp": 1656559818246] [:]
    2022-06-30 03:30:18 +0000 [JavascriptVisit] didStartVisitWithIdentifier https://turbo-native-demo.glitch.me/one ["identifier": "6cd858b3-5a0c-4695-a5b0-48597a97ea1c", "hasCachedSnapshot": false]
    2022-06-30 03:30:18 +0000 [Bridge] ← visitRequestStarted ["identifier": 6cd858b3-5a0c-4695-a5b0-48597a97ea1c, "timestamp": 1656559818248] [:]
    2022-06-30 03:30:18 +0000 [JavascriptVisit] didFinishRequestForVisitWithIdentifier https://turbo-native-demo.glitch.me/one ["identifier": "6cd858b3-5a0c-4695-a5b0-48597a97ea1c", "date": 2022-06-30 03:30:18 +0000]
    2022-06-30 03:30:18 +0000 [Bridge] = window.turboNative.visitLocationWithOptionsAndRestorationIdentifier evaluation complete [:]
    2022-06-30 03:30:18 +0000 [Bridge] ← visitRequestCompleted ["identifier": 6cd858b3-5a0c-4695-a5b0-48597a97ea1c, "timestamp": 1656559818344] [:]
    2022-06-30 03:30:18 +0000 [JavascriptVisit] didCompleteRequestForVisitWithIdentifier https://turbo-native-demo.glitch.me/one ["identifier": "6cd858b3-5a0c-4695-a5b0-48597a97ea1c"]
    2022-06-30 03:30:18 +0000 [Bridge] ← visitRequestFinished ["identifier": 6cd858b3-5a0c-4695-a5b0-48597a97ea1c, "timestamp": 1656559818344] [:]
    2022-06-30 03:30:18 +0000 [JavascriptVisit] didFinishRequestForVisitWithIdentifier https://turbo-native-demo.glitch.me/one ["identifier": "6cd858b3-5a0c-4695-a5b0-48597a97ea1c", "date": 2022-06-30 03:30:18 +0000]
    2022-06-30 03:30:18 +0000 [Bridge] ← visitCompleted ["identifier": 6cd858b3-5a0c-4695-a5b0-48597a97ea1c, "restorationIdentifier": 4904de48-531e-42ec-b9e2-6ec8304108a9, "timestamp": 1656559818347] [:]
    2022-06-30 03:30:18 +0000 [JavascriptVisit] didCompleteVisitWithIdentifier https://turbo-native-demo.glitch.me/one ["identifier": "6cd858b3-5a0c-4695-a5b0-48597a97ea1c", "restorationIdentifier": "4904de48-531e-42ec-b9e2-6ec8304108a9"]
    2022-06-30 03:30:18 +0000 [Bridge] ← visitRendered ["identifier": 6cd858b3-5a0c-4695-a5b0-48597a97ea1c, "timestamp": 1656559818366] [:]
    2022-06-30 03:30:18 +0000 [JavascriptVisit] didRenderForVisitWithIdentifier https://turbo-native-demo.glitch.me/one ["identifier": "6cd858b3-5a0c-4695-a5b0-48597a97ea1c"]
    2022-06-30 03:30:20 +0000 [Session] visit ["location": https://turbo-native-demo.glitch.me, "options": Turbo.VisitOptions(action: Turbo.VisitAction.restore, response: nil), "reload": false]
    2022-06-30 03:30:20 +0000 [JavascriptVisit] startVisit https://turbo-native-demo.glitch.me [:]
    2022-06-30 03:30:20 +0000 [Bridge] → window.turboNative.visitLocationWithOptionsAndRestorationIdentifier [Optional("https://turbo-native-demo.glitch.me"), Optional({
        action = restore;
    }), Optional("42918dac-ca08-4d87-ae88-d866b21e9e14")] [:]
    2022-06-30 03:30:20 +0000 [Bridge] ← visitStarted ["identifier": a966548e-8e36-4b39-a039-e4a03ec27734, "hasCachedSnapshot": 1, "timestamp": 1656559820532] [:]
    2022-06-30 03:30:20 +0000 [JavascriptVisit] didStartVisitWithIdentifier https://turbo-native-demo.glitch.me ["hasCachedSnapshot": true, "identifier": "a966548e-8e36-4b39-a039-e4a03ec27734"]
    2022-06-30 03:30:20 +0000 [Bridge] = window.turboNative.visitLocationWithOptionsAndRestorationIdentifier evaluation complete [:]
    2022-06-30 03:30:20 +0000 [Bridge] ← visitCompleted ["restorationIdentifier": 42918dac-ca08-4d87-ae88-d866b21e9e14, "identifier": a966548e-8e36-4b39-a039-e4a03ec27734, "timestamp": 1656559820533] [:]
    2022-06-30 03:30:20 +0000 [JavascriptVisit] didCompleteVisitWithIdentifier https://turbo-native-demo.glitch.me ["restorationIdentifier": "42918dac-ca08-4d87-ae88-d866b21e9e14", "identifier": "a966548e-8e36-4b39-a039-e4a03ec27734"]
    2022-06-30 03:30:20 +0000 [Bridge] ← visitRendered ["identifier": a966548e-8e36-4b39-a039-e4a03ec27734, "timestamp": 1656559820549] [:]
    2022-06-30 03:30:20 +0000 [JavascriptVisit] didRenderForVisitWithIdentifier https://turbo-native-demo.glitch.me ["identifier": "a966548e-8e36-4b39-a039-e4a03ec27734"]
    
    opened by jayohms 7
  • Accessing Camera / GPS

    Accessing Camera / GPS

    Does turbo-ios have inbuilt APIs to request access to Camera or GPS? Or is there an expectation that users of turbo-ios will have to write additional swift code to handle such requests?

    opened by daya 7
  • Demo doesn't work against a Turbo/Rails app with importmap-rails 0.9.1

    Demo doesn't work against a Turbo/Rails app with importmap-rails 0.9.1

    The latest importmap-rails (included in Rails 7 rc1) includes es-module-shims.js version 1.3.5. This latter library creates blob URLs that the Turbo Demo app tries to visit. This causes errors with http status code 0.

    One current workaround is to fork importmap-rails and downgrade es-module-shims.js to version 1.2.0 so the backend is serving this downgraded shim to the Demo app.

    opened by hudon 7
  • WebView crashes on iOS 15

    WebView crashes on iOS 15

    I don't think this relates to Turbolinks but it likely affects all apps which run it (or some of them at leats).

    When our app is in background for some time and one opens a bunch of other apps so that the phone is low on memory, our app will get to a weird state the next time you open it. No content is shown in the webview and there is a bunch of messages in the XCode console. My understanding is that WebView runs a service which gets killed by the OS and is vital for WebView to run properly.

    I do have a workaround which requires a slight change in the Turbo library. I changed the method webViewWebContentProcessDidTerminate() in Session.swift to post a notification when the webview process terminates:

    public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
            debugLog("[Session] webViewWebContentProcessDidTerminate")
            NotificationCenter.default.post(name: Notification.Name("webViewWebContentProcessDidTerminate"), object: nil)
        }
    

    Then I basically restart our App with a fresh WebView. It's not ideal as I loose the app state but it's the best I could come up with.

    I'd like to ask two things:

    • do you have the same issue with other Turbo apps or is it just us?
    • would it be possible to add my change to the official Turbo code? I think it might be useful for other developers as well.

    I've found this forum post which is likely exactly the issue we have:

    https://developer.apple.com/forums/thread/684843

    Please note that this is not happening on iOS 14.

    Thanks a lot

    Tony

    opened by tony42 7
  • Demo crashes when rendering native numbers view in modal

    Demo crashes when rendering native numbers view in modal

    We noticed this issue because we'd like to access the TurboNavigationController inside of a native modal in order to invoke some navigation from there.

    To reproduce the issue you simply need to add "presentation": "modal", in line 21 of path-configuration.json: https://github.com/hotwired/turbo-ios/blob/2277f7dcd52c3f8eac735980166e64bd5745272b/Demo/path-configuration.json#L21

    After that, when you run the demo, you can click on "intercept with a native view" and click on one of the rows. Now the app will crash.

    It seems as if the navigationController in this case is simply an instance of UINavigationController and not of TurboNavigationController, so the forced cast lets the app crash. I think this is caused by this line here: https://github.com/hotwired/turbo-ios/blob/2277f7dcd52c3f8eac735980166e64bd5745272b/Demo/Navigation/TurboNavigationController.swift#L89

    So the questions are:

    1. Is this a bug, or intended behavior?
    2. If it's intended, how do we achieve navigation from within a native modal instead?

    N. b. I tested the same thing with the android demo where it doesn't crash, so I suspect this is indeed a bug.

    opened by mradke 6
  • [Question] How to send cookies with OAuth response?

    [Question] How to send cookies with OAuth response?

    The end of the Authorization doc mentions getting cookies alongside an OAuth response.

    For HEY, we have a slightly different approach. We get the cookies back along with our initial OAuth request and set those cookies directly to the web view and global cookie stores.

    First, can you elaborate a bit more on how that's possible with Rails? This request is coming from a new client, so it shouldn't have any existing or shared session/cookies. Are they created fresh? If so, how?

    Second, am I wrong to assume that the access token request should be hitting an API-like controller? This will be a request without a CSRF token, so no protection can be added there. But if I work with, for example, ActionController::API, then I don't even have access to the cookies.

    I've spent a while on this and feel like I'm missing something obvious. But I'm kind of lost as to how this all lines up. Any help would be greatly appreciated! Thanks in advance.

    opened by joemasilotti 6
  • Option to disable logging

    Option to disable logging

    I've found Turbo logging to be quite verbose and get in the way of my application logs. This PR lets the developer disable Turbo logging by adding the disableTurboLog launch argument to their scheme.

    image

    This could have been an environment variable, but the double negative get's weird, and those are parsed as strings. If we wanted to add logging levels then environment variables are the better choice.

    opened by joemasilotti 6
  • Final location.pathname when accessing GET redirects

    Final location.pathname when accessing GET redirects

    In brief: After visiting a GET request that responded with a redirect, the location.pathname of a turbo-ios webview is the requested path rather than the redirect-target path. This is different from how it is handled in Chrome, Safari, and Mobile Safari where the resulting location.pathname is the ultimate redirect-target path.

    Please see: https://github.com/johnmaxwell/turbo-native-demo/pull/1/files

    This fork and patch to the hotwired/turbo-native-demo demo app adds a /get-redirect GET route that simply redirects (302) to /one and an index link for it.

    image image ☝️Tapping the "Go through a redirect" on a desktop browser (Chrome or Safari) takes the browser to /one as expected, showing both the page content and updating the browser URL.


    image image image ☝️ In the context of turbo-ios, the browser is shown the content of /one as expected, but the location.pathname remains on /get-redirect


    Is this expected behavior or a known issue?

    image ☝️In our production Rails app, we have a notification tray that contains a variety of types of notifications, each resolving to a different target URL. In that tray, we link not to the target URL, but to a notification URL -- e.g. /notifications/123 -- where the user's interaction is logged before forwarding them to the final target -- e.g. /final-target

    In attempting to package our Rails app in a turbo-ios project, we have noticed that the app location.pathname hangs on /notification/123 . For subsequent link clicks, the referrer is then passed as /notification/123 instead of /final-target.

    opened by johnmaxwell 5
  • If a form submission redirects, the target page is rendered twice

    If a form submission redirects, the target page is rendered twice

    This gif demonstrates the bug:

    form

    To replicate, using https://github.com/hotwired/turbo-native-demo

    // index.ejs
    
      <form method="post" action="/redirect">
        <button type="submit">Submit Form and redirect to another page</button>
      </form>
    
    // server.js
    
    app.post("/redirect", (request, response) => {
      response.redirect("/one")
    })
    

    Or click here to see the changes: https://github.com/hotwired/turbo-native-demo/compare/main...ghiculescu:duplicate-page-replica (if you'd like redirect behavior in the demo, I can tidy up the UI and make a PR - but that's not the issue here :))

    When you click on the button:

    • The POST request is made and returns a 302
    • Turbo.js catches the 302 and makes a GET to /one
    • Turbo-native's didProposeVisit is called with /one as the desired URL. session.visit(/one) gets called. And another GET to /one is made.

    You can see this in the demo server logs:

    Sat Jan 09 2021 17:35:23 GMT-0700 (Mountain Standard Time) -- POST /redirect
    Sat Jan 09 2021 17:35:23 GMT-0700 (Mountain Standard Time) -- GET /one
    Sat Jan 09 2021 17:35:23 GMT-0700 (Mountain Standard Time) -- GET /one
    

    As well as the doubled up requests in Safari's inspector. I would expect only a single GET /one.

    This isn't too bad in this case, but where it gets really problematic is if the POST request sets a flash (in Rails). For example:

    def create
      redirect_to some_path, alert: "something happened"
    end
    

    In this case the flash will be consumed by the first GET request, and will not appear in the second one. So from the app's perspective no flash message is shown (but if you do exactly the same behavior through a web browser, you do see a flash).

    documentation 
    opened by ghiculescu 5
  • Effects for navigation bar in modal

    Effects for navigation bar in modal

    Hello, I'm a newbie. I'm following the Demo from this repository completely, and I've come across the fact that the navbar in modal windows sometimes doesn't appear with a blur.  

    At first, I sinned on my HTML layout, but it doesn't seem to be the case. I recorded a video example from my device, where I opened the same page several times, where the blur effect appears / is absent. I don't see any pattern and would appreciate any advice.

    https://user-images.githubusercontent.com/5102591/187005782-16b0b8ab-c697-4dc7-97aa-9a7fb9f38226.MOV

    opened by tabuna 5
  • Page restoration is broken on turbo-7.2.0-beta.2

    Page restoration is broken on turbo-7.2.0-beta.2

    For the turbo-native-demo project, which shipped with turbo-7.0.1 by default, restoration behaviors works well. After upgrading Turbo to 7.2.0-beta.2, page restoration breaks. For the first time swiping back to the parent view, it works fine; However, following forwards and swiping backs will issue page reload for the parent view, which breaks the restoration behavior.

    Here is a demo video and screenshot of server request logs.

    https://user-images.githubusercontent.com/300616/185535451-cc51ed86-a9de-49d0-a27b-82983a529a85.mov

    截屏2022-08-19 10 58 49
    opened by kidlj 1
  • Cast from 'Error' to unrelated type 'TurboError' always fails

    Cast from 'Error' to unrelated type 'TurboError' always fails

    Following the instructions in the docs I have the following code:

    	func session(_ session: Session, didFailRequestForVisitable visitable: Visitable, error: Error) {
    		if let turboError = error as? TurboError {
    			switch turboError {
    			case .http(let statusCode):
    				// Display or handle the HTTP error code
    			case .networkFailure, .timeoutFailure:
    				// Display appropriate error messages
    			}
    		} else {
    			// Display the network failure or retry the visit
    		}
            }
    

    The first line shows the warning:

    Cast from 'Error' to unrelated type 'TurboError' always fails

    Any idea what's wrong? Seen it before? I'm using Xcode Version 13.4 (13F17a) if it matters.

    opened by getaaron 1
  • Config to delay activity indicator animation

    Config to delay activity indicator animation

    This PR adds a configuration option to delay the activity indicator animation. It was inspired by #89.

    I also added a new documentation file, Configuration.md. This outlines how to configure this change along with the existing logging option.

    The default delay is set to 0 so this should be safe to maintain backwards compatibility.

    opened by joemasilotti 0
  • Lint and format code

    Lint and format code

    This PR lints and formats every Swift file according to the standards from SwiftFormat.

    • Remove trailing whitespace
    • Consolidate double blank lines
    • Alphabetize imports
    • Use enums over structs when only accessed statically
    • Consolidate spacing and indentation
    • Use commas over ampersands in if statements
    • One line methods with empty implementations

    I understand that I made some personal decisions with the formatting options provided. But I don't think anyone will have a problem applying others (like trailing whitespace).

    Think of this PR as the beginning of a discussion: do we have a style guide for this project? And if not, are we interested in implementing and enforcing one? My vote is yes, and I'm happy to take the lead if this is the direction the maintainers would like to go.

    opened by joemasilotti 0
  • Add CONTRIBUTING.md

    Add CONTRIBUTING.md

    This PR adds CONTRIBUTING.md and a note to the README. I never remember the Carthage command to get the tests running locally so hopefully this will help someone.

    I left the "guide" intentionally sparse as I didn't want to make too many assumptions about how Basecamp wants to manage contributor expectations.

    opened by joemasilotti 0
Releases(7.0.0-rc.7)
  • 7.0.0-rc.7(Jun 30, 2022)

    What's Changed

    • Add missing sessionWebViewProcessDidTerminate to Demo by @ghiculescu in https://github.com/hotwired/turbo-ios/pull/69
    • Add sessionWebViewProcessDidTerminate to quick start by @ghiculescu in https://github.com/hotwired/turbo-ios/pull/72
    • Fixing Xcode warning: class-constrained protocol is deprecated by @ludagoo in https://github.com/hotwired/turbo-ios/pull/47
    • Improve logging by displaying more relevant details and additional arguments by @jayohms in https://github.com/hotwired/turbo-ios/pull/83
    • Fix typo in migration guide by @frederfred in https://github.com/hotwired/turbo-ios/pull/70
    • Fix CI by @svara in https://github.com/hotwired/turbo-ios/pull/85
    • Allow a Session's snapshot cache to be cleared manually when another Session has finished a form submission by @jayohms in https://github.com/hotwired/turbo-ios/pull/84
    • Expose a new TurboLog.debugLoggingEnabled API that apps can enable to see debug logs by @jayohms in https://github.com/hotwired/turbo-ios/pull/86

    New Contributors

    • @ludagoo made their first contribution in https://github.com/hotwired/turbo-ios/pull/47
    • @frederfred made their first contribution in https://github.com/hotwired/turbo-ios/pull/70
    • @svara made their first contribution in https://github.com/hotwired/turbo-ios/pull/85

    Full Changelog: https://github.com/hotwired/turbo-ios/compare/7.0.0-rc.6...7.0.0-rc.7

    Source code(tar.gz)
    Source code(zip)
  • 7.0.0-rc.6(Jan 24, 2022)

    What's Changed

    • Delegate callback when web view process terminates by @joemasilotti in https://github.com/hotwired/turbo-ios/pull/56
    • Docs: Make it clearer that you should use the latest version by @ghiculescu in https://github.com/hotwired/turbo-ios/pull/60
    • Conform test class to protocol to fix broken test by @jayohms in https://github.com/hotwired/turbo-ios/pull/63

    Full Changelog: https://github.com/hotwired/turbo-ios/compare/7.0.0-rc.5...7.0.0-rc.6

    Source code(tar.gz)
    Source code(zip)
  • 7.0.0-rc.5(Dec 14, 2021)

    What's Changed

    • Skip the same-page anchor scrolling behavior for visits initiated from the native side by @jayohms in https://github.com/hotwired/turbo-ios/pull/59

    Full Changelog: https://github.com/hotwired/turbo-ios/compare/7.0.0-rc.4...7.0.0-rc.5

    Source code(tar.gz)
    Source code(zip)
  • 7.0.0-rc.4(Dec 8, 2021)

    What's Changed

    • Initialize path config from file synchronously by @joemasilotti in https://github.com/hotwired/turbo-ios/pull/27
    • Allow blob:http* urls to load from es-module-shims by @jayohms in https://github.com/hotwired/turbo-ios/pull/58

    New Contributors

    • @jayohms made their first contribution in https://github.com/hotwired/turbo-ios/pull/58

    Full Changelog: https://github.com/hotwired/turbo-ios/compare/7.0.0-rc.3...7.0.0-rc.4

    Source code(tar.gz)
    Source code(zip)
  • 7.0.0-rc.3(Dec 7, 2021)

    What's Changed

    • Expose visit by @olivaresf in https://github.com/hotwired/turbo-ios/pull/53
    • Register the adapter only after turbo is loaded by @railsbob in https://github.com/hotwired/turbo-ios/pull/52

    New Contributors

    • @olivaresf made their first contribution in https://github.com/hotwired/turbo-ios/pull/53
    • @railsbob made their first contribution in https://github.com/hotwired/turbo-ios/pull/52

    Full Changelog: https://github.com/hotwired/turbo-ios/compare/7.0.0-rc.2...7.0.0-rc.3

    Source code(tar.gz)
    Source code(zip)
  • 7.0.0-rc.2(Sep 30, 2021)

  • 7.0.0-rc.1(Sep 14, 2021)

    Changes:

    • Update the Hotwire subdomains
    • Allow VisitProposal construction outside of Turbo
    • fix directories for relative links
    • Fix link reference to code in Turbo library
    • remove extra word
    • Add instructions to select the correct app lifecycle type under XCode 12
    • Default arguments in record() test helper
    • Fix tense of error message
    • Update Migration.md
    • Update adapter to account for location -> URL

    Demo:

    • Always call session.visit with options
    Source code(tar.gz)
    Source code(zip)
  • 7.0.0-beta.1(Dec 18, 2020)

Owner
Hotwire
Build modern web apps by sending HTML over the wire
Hotwire
A document-based SwiftUI application for viewing and editing EEG data, aimed at making software for viewing brain imaging data more accessible.

Trace A document-based SwiftUI application for viewing and editing EEG data, aimed at making software for viewing brain imaging data more accessible.

Tahmid Azam 3 Aug 9, 2022
Adventures-with-Swift - Building Native iOS Apps with UIKit and SiwftUI 

Adventures with Swift, UIKit, & SwiftUI As I have experience working with React Native and have dabbled a bit with Flutter, I've decided to dive in th

Daniel Stafford 3 Jan 19, 2022
A weather app developed in React Native. It is the React Native version of SwiftWeather.

ReactNativeWeather A weather app developed in React Native. It is the React Native version of SwiftWeather How to run the app Install react-native If

Jake Lin 22 Jun 7, 2022
Native iOS app using the exposure notification framework from Apple.

Corona Warn App - iOS Development • Documentation • Contribute • Support • Changelog • Licensing The goal of this project is to develop the official C

Corona-Warn-App 1.7k Sep 16, 2022
This framework contains SBB (Swiss Federal Railways) UI elements for iOS SwiftUI Apps

Framework: Design System Mobile for iOS & SwiftUI This framework contains SBB (Swiss Federal Railways) UI elements for iOS SwiftUI Apps. It allows an

Swiss Federal Railways (SBB) 18 Sep 5, 2022
Seaglass is a truly native macOS client for Matrix. It is written in Swift and uses the Cocoa user interface framework.

Seaglass is a truly native macOS client for Matrix. It is written in Swift and uses the Cocoa user interface framework.

null 1 Jan 17, 2022
Super basic iOS app to browse open-source-ios-apps

Super basic iOS app to browse open-source-ios-apps

null 71 Sep 11, 2022
Native Jellyfin Client for iOS and tvOS

Swiftfin Swiftfin is a modern client for the Jellyfin media server. Redesigned in Swift to maximize direct play with the power of VLC and look native

Jellyfin 653 Sep 25, 2022
Native and encrypted password manager for iOS and macOS.

Open Sesame Native and encrypted password manager for iOS and macOS. What is it? OpenSesame is a free and powerful password manager that lets you mana

OpenSesame 421 Sep 23, 2022
📱 Guideo - Native iOS App crafted with Swift and SwiftUI

Guideo An awesome iOS Native App ?? About Guideo App wire-framed and crafted from scratch by a team of 4. Our final project of the  Apple Foundation

Lyane Lamara 4 May 23, 2022
GitHub-User is an iOS native application, written in Swift programming language.

#GitHub-User GitHub-User is an iOS native application, written in Swift programming language. This project is an interview take home project. The arch

Zeljko Lucic 1 Mar 25, 2022
Native iOS app built in SwiftUI, displays a collection of user's books.

Native iOS app built in SwiftUI, displays a collection of user's books.

Matthew Eilar 1 May 23, 2022
Riddler is a riddle game built as a native iOS app in Swift using SwiftUI

Riddler is a riddle game built as a native iOS app in Swift using SwiftUI. It includes 50 challenging riddles with hints for when you get stuck. The game tracks your stats so you can compare your performance against your friends, and see who can answer all 50 riddles the quickest.

Oliver Stenning 2 Jul 16, 2022
Angela Yu iOS tutorials TODO apps practice

Todoey ✓ Our Goal The objective of this tutorial is to understand how to save data in iOS. We'll look at various choices and learn to use UserDefaults

Ferdous 0 Jan 1, 2022
A curated list of Open Source example iOS apps developed in Swift

Example iOS Apps A curated list of Open Source example iOS apps developed in Swift. How to Use Example-iOS-Apps is an amazing list for people who are

null 1 Dec 15, 2021
CS193p---Assignments - Assignment Solutions for Stanford CS193p - Developing Apps for iOS

Assignment Solutions for Stanford CS193p - Developing Apps for iOS Note: This is ongoing work Task done Programming Assignment 1 x Programming Assignm

null 0 Jan 12, 2022
A simple star rating library for SwiftUI apps on macOS and iOS

DLDRating A simple star rating library for SwiftUI apps on macOS and iOS. Features Installation Usage Styling Credits DLDRating was made by Dionne Lie

null 1 Mar 6, 2022
Create a beautiful Onabording for your iOS/iPadOS apps in just a few minutes.

Create a beautiful Onabording for your iOS/iPadOS apps in just a few minutes.

Jem Alvarez 6 Sep 9, 2022
Demonstration of using UIWindowScene and SwiftUI to provide a native-looking Mac preferences window in Catalyst

CatalystPrefsWindow Ever wondered how to create a more Mac-like preferences window for Catalyst? Perhaps Settings Bundles are too limiting for the kin

Steven Troughton-Smith 145 Sep 13, 2022