motion-turbo 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +48 -0
  3. data/lib/motion-turbo.rb +15 -0
  4. data/lib/turbo/logging.rb +31 -0
  5. data/lib/turbo/path_configuration/path_configuration.rb +55 -0
  6. data/lib/turbo/path_configuration/path_configuration_decoder.rb +25 -0
  7. data/lib/turbo/path_configuration/path_configuration_loader.rb +62 -0
  8. data/lib/turbo/path_configuration/path_rule.rb +34 -0
  9. data/lib/turbo/session/navigation_delegate_methods.rb +43 -0
  10. data/lib/turbo/session/session.rb +183 -0
  11. data/lib/turbo/session/session_delegate_methods.rb +30 -0
  12. data/lib/turbo/session/visit_delegate_methods.rb +67 -0
  13. data/lib/turbo/session/visitable_delegate_methods.rb +51 -0
  14. data/lib/turbo/session/web_view_delegate_methods.rb +46 -0
  15. data/lib/turbo/turbo_error.rb +30 -0
  16. data/lib/turbo/visit/cold_boot_visit.rb +109 -0
  17. data/lib/turbo/visit/javascript_visit.rb +107 -0
  18. data/lib/turbo/visit/visit.rb +91 -0
  19. data/lib/turbo/visit/visit_options.rb +36 -0
  20. data/lib/turbo/visit/visit_proposal.rb +13 -0
  21. data/lib/turbo/visit/visit_response.rb +29 -0
  22. data/lib/turbo/visitable/visitable.rb +58 -0
  23. data/lib/turbo/visitable/visitable_view.rb +20 -0
  24. data/lib/turbo/visitable/visitable_view_controller.rb +71 -0
  25. data/lib/turbo/visitable_view/activity_indicator.rb +36 -0
  26. data/lib/turbo/visitable_view/constraints.rb +14 -0
  27. data/lib/turbo/visitable_view/refresh_control.rb +61 -0
  28. data/lib/turbo/visitable_view/screenshots.rb +56 -0
  29. data/lib/turbo/visitable_view/scroll_view.rb +21 -0
  30. data/lib/turbo/visitable_view/web_view.rb +28 -0
  31. data/lib/turbo/web_view/script_message.rb +77 -0
  32. data/lib/turbo/web_view/script_message_handler.rb +16 -0
  33. data/lib/turbo/web_view/web_view_bridge.rb +154 -0
  34. metadata +104 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a50ec993094594fc8592e14e3f127671cfc4b65e3e5c5523459269c9872029c3
4
+ data.tar.gz: fcd34843ff4ca1322cb91aad6ce665dd77e3d6d43583144f5f70cffde5ebf482
5
+ SHA512:
6
+ metadata.gz: 98c870947f0fcacc45691a53e6781f6158cc50954ab22956bb5884dcff5f161be859518aae15e9f8711deab12456937e92af2f7c308bc44510e3b1c237ee13f5
7
+ data.tar.gz: 1f32a4f0f8f5a86f9284ec801008cda7dd4f539cdc2b674e20eb74bdb6bc450d3ce125347f89418e1cb301fa1987ba6d1e6c89d221a96c2307b8fdc5189ce916
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Turbo Native for RubyMotion
2
+
3
+ **Build high-fidelity hybrid apps with native navigation and a single shared web view.** Turbo Native for RubyMotion provides the tooling to wrap your [Turbo 7](https://github.com/hotwired/turbo)-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.
4
+
5
+ ## Features
6
+
7
+ - **Deliver fast, efficient hybrid apps.** Avoid reloading JavaScript and CSS. Save memory by sharing one WKWebView.
8
+ - **Reuse mobile web views across platforms.** Create your views once, on the server, in HTML. Deploy them to iOS, [Android](https://github.com/hotwired/turbo-android), and mobile browsers simultaneously. Ship new features without waiting on App Store approval.
9
+ - **Enhance web views with native UI.** Navigate web views using native patterns. Augment web UI with native controls.
10
+ - **Produce large apps with small teams.** Achieve baseline HTML coverage for free. Upgrade to native views as needed.
11
+
12
+ ### Features of Turbo Native for RubyMotion
13
+
14
+ - **Ruby syntax. Native performance.** Build native iOS and Android apps using the Ruby syntax you know and love with the same performance as Swift and Java by using [RubyMotion](http://www.rubymotion.com/).
15
+
16
+ ## Requirements
17
+
18
+ Turbo Native for RubyMotion is compatible with all versions of RubyMotion.
19
+
20
+ **Note:** You should understand how Turbo works with web applications in the browser before attempting to use Turbo iOS. See the [Turbo 7 documentation](https://github.com/hotwired/turbo) for details. Ensure that your web app sets the `window.Turbo` global variable as it's required by the native apps:
21
+
22
+ ```javascript
23
+ import { Turbo } from "@hotwired/turbo-rails"
24
+ window.Turbo = Turbo
25
+ ```
26
+
27
+ ## Getting Started
28
+
29
+ The best way to get started with Turbo Native is 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](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.
30
+
31
+ ## Documentation
32
+
33
+ - [Quick Start](docs/QuickStartGuide.md)
34
+
35
+ ## Contributing
36
+
37
+ Turbo iOS is open-source software, freely distributable under the terms of an [MIT-style license](LICENSE). The [source code is hosted on GitHub](https://github.com/hotwired/turbo-ios).
38
+ Development is sponsored by [Basecamp](https://basecamp.com/).
39
+
40
+ We welcome contributions in the form of bug reports, pull requests, or thoughtful discussions in the [GitHub issue tracker](https://github.com/hotwired/turbo-ios/issues).
41
+
42
+ Please note that this project is released with a [Contributor Code of Conduct](CONDUCT.md). By participating in this project you agree to abide by its terms.
43
+
44
+ ---
45
+
46
+ © 2020 Basecamp, LLC
47
+
48
+
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ unless defined?(Motion::Project::Config)
4
+ raise "This file must be required within a RubyMotion project Rakefile."
5
+ end
6
+
7
+ lib_dir_path = File.dirname(File.expand_path(__FILE__))
8
+ Motion::Project::App.setup do |app|
9
+ app.files.unshift(Dir.glob(File.join(lib_dir_path, "turbo/**/*.rb")))
10
+ app.resources_dirs.unshift(Dir.glob(File.join(lib_dir_path, "../resources")))
11
+ app.frameworks += ['WebKit']
12
+ end
13
+
14
+ module Turbo
15
+ end
@@ -0,0 +1,31 @@
1
+ module Turbo
2
+ def self.logger
3
+ @logger ||= Motion::Lager.new(level: "debug")
4
+ end
5
+ end
6
+
7
+ class TurboLog
8
+ def self.debugLoggingEnabled
9
+ true # TODO NSBundle.mainBundle.objectForInfoDictionaryKey('DEBUG')
10
+ end
11
+ end
12
+
13
+ def debugLog(message)
14
+ debugLog(message, arguments: {})
15
+ end
16
+
17
+ def debugLog(message, arguments: arguments)
18
+ timestamp = NSDate.new
19
+
20
+ log2("#{timestamp} #{message} #{arguments}")
21
+ end
22
+
23
+ def debugPrint(message)
24
+ log2(message)
25
+ end
26
+
27
+ def log2(message)
28
+ if TurboLog.debugLoggingEnabled
29
+ NSLog(message)
30
+ end
31
+ end
@@ -0,0 +1,55 @@
1
+ module Turbo
2
+ class PathConfiguration
3
+
4
+ attr_accessor :sources, :loader, :delegate, :rules, :settings
5
+ # Multiple sources will be loaded in order
6
+ # Remote sources should be last since they're loaded async
7
+ def initWithSources(sources)
8
+ self.sources = sources
9
+ self.rules = []
10
+ load
11
+ self
12
+ end
13
+
14
+ # Returns a merged hash containing all the properties
15
+ # that match this url
16
+ # Note: currently only looks at path, not query, but most likely will
17
+ # add query support in the future, so it's best to always use this over the path variant
18
+ # unless you're sure you'll never need to reference other parts of the URL in the future
19
+ def propertiesForURL(url)
20
+ propertiesForPath(url.path)
21
+ end
22
+
23
+ # Returns a merged hash containing all the properties
24
+ # that match this path
25
+ def propertiesForPath(path)
26
+ #source = NSString.stringWithContentsOfURL(url, encoding: NSUTF8StringEncoding, error: nil)
27
+ properties = {}
28
+
29
+ rules.each do |rule|
30
+ if rule.match(path)
31
+ properties.merge!(rule.properties)
32
+ end
33
+ end
34
+ properties
35
+ end
36
+
37
+ private
38
+
39
+ def load
40
+ loader = PathConfigurationLoader.alloc.initWithSources(sources)
41
+ if loader
42
+ loader.load do |config|
43
+ self.send(:update, config)
44
+ end
45
+ end
46
+ end
47
+
48
+ def update(config)
49
+ # Update our internal state with the config from the loader
50
+ self.settings = config.settings
51
+ self.rules = config.rules
52
+ delegate.pathConfigurationDidUpdate if delegate
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,25 @@
1
+ module Turbo
2
+ class PathConfigurationDecoder
3
+ attr_accessor :settings, :rules
4
+
5
+ def initWithSettings(settings, rules: rules)
6
+ self.settings = settings
7
+ self.rules = rules
8
+ self
9
+ end
10
+
11
+ def initWithJSON(json)
12
+ # rules must be present, settings are optional
13
+ #guard let rulesArray = json["rules"] as? [[String: AnyHashable]] else {
14
+ #throw JSONDecodingError.invalidJSON
15
+ #}
16
+
17
+ rules = json["rules"].map do |rule|
18
+ PathRule.alloc.initWithRule(rule)
19
+ end
20
+ settings = json["settings"]
21
+
22
+ initWithSettings(settings, rules: rules)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,62 @@
1
+ module Turbo
2
+ class PathConfigurationLoader
3
+ PathConfigurationLoaderCompletionHandler = Class.new(PathConfigurationDecoder)
4
+
5
+ attr_accessor :sources, :completionHandler
6
+ def initWithSources(sources)
7
+ self.sources = sources
8
+ self
9
+ end
10
+
11
+ def load(&completionHandler)
12
+ #completionHandler = PathConfigurationLoaderCompletionHandler.new
13
+
14
+ sources.each do |source|
15
+ #case source
16
+ #when .data(let data)
17
+ #data = loadData(source)
18
+ data = loadFile(source)
19
+ completionHandler.call(data)
20
+
21
+ #when .file(let url):
22
+ #loadFile(source)
23
+ #when .server(let url):
24
+ #download(from: url)
25
+ #end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def cacheDirectory
32
+ "Turbo"
33
+ end
34
+
35
+ def configurationCacheFilename
36
+ "path-configuration.json"
37
+ end
38
+
39
+ # MARK: - File
40
+
41
+ def loadFile(url)
42
+ #precondition(url.isFileURL, "URL provided for file is not a file url")
43
+ error_ptr = Pointer.new(:object)
44
+ data = NSData.alloc.initWithContentsOfURL(url, options:NSDataReadingUncached, error:error_ptr)
45
+ #begin
46
+ #data = File.read(url) #try Data(contentsOf: url)
47
+ loadData(data)
48
+ #end catch {
49
+ #debugPrint("[path-configuration] *** error loading configuration from file: \(url), error: \(error)")
50
+ #end
51
+ #end
52
+ end
53
+
54
+ # MARK: - Data
55
+
56
+ def loadData(json) #, cache: cache)
57
+ error_ptr = Pointer.new(:object)
58
+ data = NSJSONSerialization.JSONObjectWithData(json, options: 0, error: error_ptr)
59
+ PathConfigurationDecoder.alloc.initWithJSON(data)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,34 @@
1
+ module Turbo
2
+ class PathRule
3
+ # Array of regular expressions to match against
4
+ attr_accessor :patterns
5
+
6
+ # The properties to apply for matches
7
+ attr_accessor :properties
8
+
9
+ # Convenience method to retrieve a String value for a key
10
+ # Access `properties` directly to get a different type
11
+ def subscript(key)
12
+ properties[key].to_s
13
+ end
14
+
15
+ def initWithRule(rule)
16
+ self.patterns = rule["patterns"]
17
+ self.properties = rule["properties"]
18
+ self
19
+ end
20
+
21
+ # Returns true if any pattern in this rule matches `path`
22
+ def match(path)
23
+ patterns.each do |pattern|
24
+ #guard let regex = try? NSRegularExpression(pattern: pattern) else { continue }
25
+ regex = %r(#{pattern})
26
+
27
+ if path =~ regex
28
+ return true
29
+ end
30
+ end
31
+ false
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,43 @@
1
+ module Turbo
2
+ class Session
3
+ module NavigationDelegateMethods
4
+
5
+ attr_accessor :navigationAction
6
+
7
+ def webView(webview, decidePolicyForNavigationAction: navigationAction, decisionHandler: decisionHandler)
8
+ navigationDecision = NavigationDecision(navigationAction: navigationAction)
9
+ decisionHandler(navigationDecision.policy)
10
+
11
+ if url = navigationDecision.externallyOpenableURL
12
+ openExternalURL(url)
13
+ elsif navigationDecision.shouldReloadPage
14
+ reload
15
+ end
16
+ end
17
+
18
+ def policy
19
+ navigationAction.navigationType == WKNavigationTypeLinkActivated || isMainFrameNavigation ? WKNavigationResponsePolicyCancel : WKNavigationResponsePolicyAllow
20
+ end
21
+
22
+ def externallyOpenableURL
23
+ if url = navigationAction.request.url && shouldOpenURLExternally
24
+ url
25
+ end
26
+ end
27
+
28
+ def shouldOpenURLExternally
29
+ type = navigationAction.navigationType
30
+ return type == WKNavigationTypeLinkActivated || (isMainFrameNavigation && type == WKNavigationTypeOther)
31
+ end
32
+
33
+ def shouldReloadPage
34
+ type = navigationAction.navigationType
35
+ return isMainFrameNavigation && type == WKNavigationTypeReload
36
+ end
37
+
38
+ def isMainFrameNavigation
39
+ navigationAction.targetFrame.isMainFrame if navigationAction.targetFrame
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,183 @@
1
+ module Turbo
2
+ # A Session represents the main interface for managing
3
+ # a Turbo app in a web view. Each Session manages a single web view
4
+ # so you should create multiple sessions to have multiple web views, for example
5
+ # when using modals or tabs
6
+ class Session
7
+ include VisitDelegateMethods
8
+ include VisitableDelegateMethods
9
+ include WebViewDelegateMethods
10
+ include SessionDelegateMethods
11
+ include NavigationDelegateMethods
12
+
13
+ attr_accessor :delegate, :webView, :pathConfiguration, :bridge
14
+ attr_reader :initialized,
15
+ :refreshing,
16
+ :activatedVisitable,
17
+ :currentVisit,
18
+ :topmostVisit,
19
+ :topmostVisitable
20
+
21
+ def init
22
+ initWithConfiguration(WKWebViewConfiguration.alloc.init)
23
+ end
24
+
25
+ def initWithConfiguration(configuration)
26
+ webViewConfiguration = configuration
27
+ webView = WKWebView.alloc.initWithFrame(CGRectZero, configuration: webViewConfiguration)
28
+ webView.layer.borderColor = UIColor.blueColor
29
+ webView.layer.borderWidth = 2
30
+
31
+ initWithWebView(webView)
32
+ end
33
+
34
+ def initWithWebView(webView)
35
+ @webView = webView
36
+ @initialized = false
37
+ @refreshing = false
38
+ setup
39
+ self
40
+ end
41
+
42
+ private
43
+
44
+ def bridge
45
+ @bridge ||= WebViewBridge.alloc.initWithWebView(webView)
46
+ end
47
+
48
+ def setup
49
+ webView.translatesAutoresizingMaskIntoConstraints = false
50
+ bridge.delegate = self
51
+ end
52
+
53
+ public
54
+
55
+ # The topmost visitable is the visitable that has most recently completed a visit
56
+ def topmostVisitable
57
+ topmostVisit.visitable if topmostVisit
58
+ end
59
+
60
+ # The active visitable is the visitable that currently owns the web view
61
+ def activeVisitable
62
+ activatedVisitable
63
+ end
64
+
65
+ def visitVisitable(visitable)
66
+ visitVisitable(visitable, options: nil)
67
+ end
68
+
69
+ def visitVisitable(visitable, action: action)
70
+ visitVisitable(visitable, options: VisitOptions.alloc.initWithAction(action, response: nil))
71
+ end
72
+
73
+ def visitVisitable(visitable, options: options)
74
+ visitVisitable(visitable, options: options, reload: false)
75
+ end
76
+
77
+ def visitVisitable(visitable, options: options, reload: reload)
78
+ # TODO raise instead?
79
+ raise "Visitable must provide a url! #{visitable}" unless visitable.visitableURL
80
+
81
+ visitable.visitableDelegate = self
82
+
83
+ if reload
84
+ @initialized = false
85
+ end
86
+
87
+ visit = makeVisit(visitable, options: options || VisitOptions.alloc.initFromHash({}))
88
+ @currentVisit.cancel if currentVisit
89
+ @currentVisit = visit
90
+
91
+ log("visit", { location: visit.location, options: visit.options, reload: reload })
92
+
93
+ visit.delegate = self
94
+ visit.start
95
+ end
96
+
97
+ def makeVisit(visitable, options: options)
98
+ if initialized
99
+ restorationIdentifier = restorationIdentifierForVisitable(visitable)
100
+ JavaScriptVisit.alloc.initWithVisitable(visitable, options: options, bridge: bridge, restorationIdentifier: restorationIdentifier)
101
+ else
102
+ ColdBootVisit.alloc.initWithVisitable(visitable, options: options, bridge: bridge)
103
+ end
104
+ end
105
+
106
+ def reload
107
+ return unless visitable = topmostVisitable
108
+ @initialized = false
109
+ visitVisitable(visitable)
110
+ @topmostVisit = currentVisit
111
+ end
112
+
113
+ def clearSnapshotCache
114
+ bridge.clearSnapshotCache
115
+ end
116
+
117
+ # Visitable activation
118
+
119
+ private
120
+
121
+ def activateVisitable(visitable)
122
+ return if isActivatedVisitable(visitable)
123
+
124
+ deactivateActivatedVisitable
125
+ visitable.activateVisitableWebView(webView)
126
+ @activatedVisitable = visitable
127
+ end
128
+
129
+ def deactivateActivatedVisitable
130
+ if activatedVisitable
131
+ deactivateVisitable(activatedVisitable, showScreenshot: true)
132
+ end
133
+ end
134
+
135
+ def deactivateVisitable(visitable, showScreenshot: showScreenshot)
136
+ if visitable == activatedVisitable
137
+ if showScreenshot
138
+ visitable.updateVisitableScreenshot
139
+ visitable.showVisitableScreenshot
140
+ end
141
+
142
+ visitable.deactivateVisitableWebView
143
+ @activatedVisitable = nil
144
+ end
145
+ end
146
+
147
+ def isActivatedVisitable(visitable)
148
+ visitable == activatedVisitable
149
+ end
150
+
151
+ # Visitable restoration identifiers
152
+
153
+ def visitableRestorationIdentifiers
154
+ @visitableRestorationIdentifiers ||= NSMapTable.weakToStrongObjectsMapTable
155
+ end
156
+
157
+ def restorationIdentifierForVisitable(visitable)
158
+ visitableRestorationIdentifiers.objectForKey(visitable.visitableViewController)
159
+ end
160
+
161
+ def storeRestorationIdentifier(restorationIdentifier, forVisitable: visitable)
162
+ visitableRestorationIdentifiers.setObject(restorationIdentifier, forKey: visitable.visitableViewController)
163
+ end
164
+
165
+ # MARK: - Navigation
166
+
167
+ def completeNavigationForCurrentVisit
168
+ if currentVisit
169
+ @topmostVisit = currentVisit
170
+ end
171
+ end
172
+
173
+ private
174
+
175
+ def log(name)
176
+ log(name, {})
177
+ end
178
+
179
+ def log(name, arguments)
180
+ Turbo.logger.debug("[Session] #{name} #{arguments}", :magenta)
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,30 @@
1
+ module Turbo
2
+ class Session
3
+ module SessionDelegateMethods
4
+
5
+ def sessionDidLoadWebView(session)
6
+ session.webView.navigationDelegate = session
7
+ end
8
+
9
+ def session(session, openExternalURL: url)
10
+ UIApplication.shared.open(url)
11
+ end
12
+
13
+ def sessionDidStartRequest(session)
14
+ end
15
+
16
+ def sessionDidFinishRequest(session)
17
+ end
18
+
19
+ def sessionDidStartFormSubmission(session)
20
+ end
21
+
22
+ def sessionDidFinishFormSubmission(session)
23
+ end
24
+
25
+ def session(session, didReceiveAuthenticationChallenge: challenge, &completionHandler)
26
+ completionHandler.call(NSURLAuthenticationChallengeSender.performDefaultHandlingForAuthenticationChallenge, nil)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,67 @@
1
+ module Turbo
2
+ class Session
3
+ module VisitDelegateMethods
4
+ def visitRequestDidStart(visit)
5
+ delegate.sessionDidStartRequest(self) if delegate
6
+ end
7
+
8
+ def visitRequestDidFinish(visit)
9
+ delegate.sessionDidFinishRequest(self) if delegate
10
+ end
11
+
12
+ def visit(visit, requestDidFailWithError: error)
13
+ delegate.session(self, didFailRequestForVisitable: visit.visitable, withError: error) if delegate
14
+ end
15
+
16
+ def visitDidInitializeWebView(visit)
17
+ @initialized = true
18
+ delegate.sessionDidLoadWebView(self) if delegate
19
+ end
20
+
21
+ def visitWillStart(visit)
22
+ visit.visitable.showVisitableScreenshot
23
+ activateVisitable(visit.visitable)
24
+ end
25
+
26
+ def visitDidStart(visit)
27
+ unless visit.hasCachedSnapshot
28
+ visit.visitable.showVisitableActivityIndicator
29
+ end
30
+ end
31
+
32
+ def visitWillLoadResponse(visit)
33
+ visit.visitable.updateVisitableScreenshot
34
+ visit.visitable.showVisitableScreenshot
35
+ end
36
+
37
+ def visitDidRender(visit)
38
+ visit.visitable.hideVisitableScreenshot
39
+ visit.visitable.hideVisitableActivityIndicator
40
+ visit.visitable.visitableDidRender
41
+ end
42
+
43
+ def visitDidComplete(visit)
44
+ if restorationIdentifier = visit.restorationIdentifier
45
+ storeRestorationIdentifier(restorationIdentifier, forVisitable: visit.visitable)
46
+ end
47
+ end
48
+
49
+ def visitDidFail(visit)
50
+ visit.visitable.clearVisitableScreenshot
51
+ visit.visitable.showVisitableScreenshot
52
+ visit.visitable.hideVisitableActivityIndicator
53
+ end
54
+
55
+ def visitDidFinish(visit)
56
+ if refreshing
57
+ @refreshing = false
58
+ visit.visitable.visitableDidRefresh
59
+ end
60
+ end
61
+
62
+ def visit(visit, didReceiveAuthenticationChallenge: challenge, &completionHandler)
63
+ delegate?.session(self, didReceiveAuthenticationChallenge: challenge, completionHandler: completionHandler)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,51 @@
1
+ module Turbo
2
+ class Session
3
+ module VisitableDelegateMethods
4
+ def visitableViewWillAppear(visitable)
5
+ return unless topmostVisit && currentVisit
6
+
7
+ if visitable == topmostVisit.visitable && visitable.visitableViewController.isMovingToParentViewController
8
+ # Back swipe gesture canceled
9
+ if topmostVisit.state.to_sym == :completed
10
+ currentVisit.cancel
11
+ else
12
+ visitVisitable(visitable, action: :advance)
13
+ end
14
+ elsif visitable == currentVisit.visitable && currentVisit.state.to_sym == :started
15
+ # Navigating forward - complete navigation early
16
+ completeNavigationForCurrentVisit
17
+ elsif visitable != topmostVisit.visitable
18
+ # Navigating backward
19
+ visitVisitable(visitable, action: :restore)
20
+ end
21
+ end
22
+
23
+ def visitableViewDidAppear(visitable)
24
+ if currentVisit && visitable == currentVisit.visitable
25
+ # Appearing after successful navigation
26
+ completeNavigationForCurrentVisit
27
+ if currentVisit.state.to_sym != :failed
28
+ activateVisitable(visitable)
29
+ end
30
+ elsif topmostVisit && visitable == topmostVisit.visitable && topmostVisit.state == :completed
31
+ # Reappearing after canceled navigation
32
+ visitVisitable(visitable, action: :restore)
33
+ end
34
+ end
35
+
36
+ def visitableDidRequestReload(visitable)
37
+ if visitable == topmostVisitable
38
+ reload
39
+ end
40
+ end
41
+
42
+ def visitableDidRequestRefresh(visitable)
43
+ if visitable == topmostVisitable
44
+ @refreshing = true
45
+ visitable.visitableWillRefresh
46
+ reload
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end