motion-turbo-ios 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-ios.rb +15 -0
  4. data/lib/turbo/logging.rb +25 -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 +184 -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 +90 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e7a10ca24cbd7e3473c49d8721a90826036bc67af2e9871e2aa2c60ccb57ffcb
4
+ data.tar.gz: 065b1e687eb6e77bdf1305857a842485523fb2839c8d530f90bb0b236b20329d
5
+ SHA512:
6
+ metadata.gz: 9989b9032156906195c0ecb28a1e3db27a532dbb27188fb5e1d3109bccdc1c654bbd04595051a6a846754ef6fc2d4f20749540c86fa968f6ef190e551d597049
7
+ data.tar.gz: 123dc0c7a486ee91aaa9a3274cafc97938bf945da7feb41697b34d2f11ab66ad8c165ea74a6c6e9b63941398431f784b9611f59ed7b7597f68ea703cc4c44022
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Turbo Native iOS for RubyMotion
2
+
3
+ **Build high-fidelity hybrid apps with native navigation and a single shared web view.** Turbo Native iOS 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 iOS 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 iOS 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 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](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,25 @@
1
+ class TurboLog
2
+ def self.debugLoggingEnabled
3
+ true # TODO NSBundle.mainBundle.objectForInfoDictionaryKey('DEBUG')
4
+ end
5
+ end
6
+
7
+ def debugLog(message)
8
+ debugLog(message, arguments: {})
9
+ end
10
+
11
+ def debugLog(message, arguments: arguments)
12
+ timestamp = NSDate.new
13
+
14
+ log2("#{timestamp} #{message} #{arguments}")
15
+ end
16
+
17
+ def debugPrint(message)
18
+ log2(message)
19
+ end
20
+
21
+ def log2(message)
22
+ if TurboLog.debugLoggingEnabled
23
+ NSLog(message)
24
+ end
25
+ 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,184 @@
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 = WKWebView.alloc.initWithFrame(CGRectMake(-50,050,450,100), configuration: webViewConfiguration)
29
+ webView.layer.borderColor = UIColor.blueColor
30
+ webView.layer.borderWidth = 2
31
+
32
+ initWithWebView(webView)
33
+ end
34
+
35
+ def initWithWebView(webView)
36
+ @webView = webView
37
+ @initialized = false
38
+ @refreshing = false
39
+ setup
40
+ self
41
+ end
42
+
43
+ private
44
+
45
+ def bridge
46
+ @bridge ||= WebViewBridge.alloc.initWithWebView(webView)
47
+ end
48
+
49
+ def setup
50
+ webView.translatesAutoresizingMaskIntoConstraints = false
51
+ bridge.delegate = self
52
+ end
53
+
54
+ public
55
+
56
+ # The topmost visitable is the visitable that has most recently completed a visit
57
+ def topmostVisitable
58
+ topmostVisit.visitable if topmostVisit
59
+ end
60
+
61
+ # The active visitable is the visitable that currently owns the web view
62
+ def activeVisitable
63
+ activatedVisitable
64
+ end
65
+
66
+ def visitVisitable(visitable)
67
+ visitVisitable(visitable, options: nil)
68
+ end
69
+
70
+ def visitVisitable(visitable, action: action)
71
+ visitVisitable(visitable, options: VisitOptions.alloc.initWithAction(action, response: nil))
72
+ end
73
+
74
+ def visitVisitable(visitable, options: options)
75
+ visitVisitable(visitable, options: options, reload: false)
76
+ end
77
+
78
+ def visitVisitable(visitable, options: options, reload: reload)
79
+ # TODO raise instead?
80
+ raise "Visitable must provide a url! #{visitable}" unless visitable.visitableURL
81
+
82
+ visitable.visitableDelegate = self
83
+
84
+ if reload
85
+ @initialized = false
86
+ end
87
+
88
+ visit = makeVisit(visitable, options: options || VisitOptions.alloc.initFromHash({}))
89
+ @currentVisit.cancel if currentVisit
90
+ @currentVisit = visit
91
+
92
+ log("visit", { location: visit.location, options: visit.options, reload: reload })
93
+
94
+ visit.delegate = self
95
+ visit.start
96
+ end
97
+
98
+ def makeVisit(visitable, options: options)
99
+ if initialized
100
+ restorationIdentifier = restorationIdentifierForVisitable(visitable)
101
+ JavaScriptVisit.alloc.initWithVisitable(visitable, options: options, bridge: bridge, restorationIdentifier: restorationIdentifier)
102
+ else
103
+ ColdBootVisit.alloc.initWithVisitable(visitable, options: options, bridge: bridge)
104
+ end
105
+ end
106
+
107
+ def reload
108
+ return unless visitable = topmostVisitable
109
+ @initialized = false
110
+ visitVisitable(visitable)
111
+ @topmostVisit = currentVisit
112
+ end
113
+
114
+ def clearSnapshotCache
115
+ bridge.clearSnapshotCache
116
+ end
117
+
118
+ # Visitable activation
119
+
120
+ private
121
+
122
+ def activateVisitable(visitable)
123
+ return if isActivatedVisitable(visitable)
124
+
125
+ deactivateActivatedVisitable
126
+ visitable.activateVisitableWebView(webView)
127
+ @activatedVisitable = visitable
128
+ end
129
+
130
+ def deactivateActivatedVisitable
131
+ if activatedVisitable
132
+ deactivateVisitable(activatedVisitable, showScreenshot: true)
133
+ end
134
+ end
135
+
136
+ def deactivateVisitable(visitable, showScreenshot: showScreenshot)
137
+ if visitable == activatedVisitable
138
+ if showScreenshot
139
+ visitable.updateVisitableScreenshot
140
+ visitable.showVisitableScreenshot
141
+ end
142
+
143
+ visitable.deactivateVisitableWebView
144
+ @activatedVisitable = nil
145
+ end
146
+ end
147
+
148
+ def isActivatedVisitable(visitable)
149
+ visitable == activatedVisitable
150
+ end
151
+
152
+ # Visitable restoration identifiers
153
+
154
+ def visitableRestorationIdentifiers
155
+ @visitableRestorationIdentifiers ||= NSMapTable.weakToStrongObjectsMapTable
156
+ end
157
+
158
+ def restorationIdentifierForVisitable(visitable)
159
+ visitableRestorationIdentifiers.objectForKey(visitable.visitableViewController)
160
+ end
161
+
162
+ def storeRestorationIdentifier(restorationIdentifier, forVisitable: visitable)
163
+ visitableRestorationIdentifiers.setObject(restorationIdentifier, forKey: visitable.visitableViewController)
164
+ end
165
+
166
+ # MARK: - Navigation
167
+
168
+ def completeNavigationForCurrentVisit
169
+ if currentVisit
170
+ @topmostVisit = currentVisit
171
+ end
172
+ end
173
+
174
+ private
175
+
176
+ def log(name)
177
+ log(name, {})
178
+ end
179
+
180
+ def log(name, arguments)
181
+ debugLog("[Session] #{name}", arguments: arguments)
182
+ end
183
+ end
184
+ 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