motion-turbo-ios 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,46 @@
1
+ module Turbo
2
+ class Session
3
+ module WebViewDelegateMethods
4
+ def webView(webView, didProposeVisitToLocation: location, withOptions: options)
5
+ properties = pathConfiguration ? pathConfiguration.propertiesForURL(location) : {}
6
+ proposal = VisitProposal.alloc.initWithURL(location, options: options, properties: properties)
7
+ delegate.session(self, didProposeVisitProsal: proposal) if delegate
8
+ end
9
+
10
+ def webView(webView, didStartFormSubmissionToLocation: location)
11
+ delegate.sessionDidStartFormSubmission(self) if delegate
12
+ end
13
+
14
+ def webView(webView, didFinishFormSubmissionToLocation: location)
15
+ delegate.sessionDidFinishFormSubmission(self) if delegate
16
+ end
17
+
18
+ def webViewDidInvalidatePage(webView)
19
+ if topmostVisitable
20
+ topmostVisitable.updateVisitableScreenshot
21
+ topmostVisitable.showVisitableScreenshot
22
+ topmostVisitable.showVisitableActivityIndicator
23
+ reload
24
+ end
25
+ end
26
+
27
+ # Initial page load failed, this will happen when we couldn't find Turbo JS on the page
28
+ def webView(webView, didFailInitialPageLoadWithError: error)
29
+ return unless currentVisit = self.currentVisit && !initialized
30
+
31
+ @initialized = false
32
+ currentVisit.cancel
33
+ visitDidFail(currentVisit)
34
+ visit(currentVisit, requestDidFailWithError: error)
35
+ end
36
+
37
+ def webView(webView, didFailJavaScriptEvaluationWithError: error)
38
+ return unless currentVisit = self.currentVisit && initialized
39
+
40
+ @initialized = false
41
+ currentVisit.cancel
42
+ visit(currentVisit.visitable)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,30 @@
1
+ module Turbo
2
+ class TurboError < NSError
3
+ ERROR_DOMAIN = "com.basecamp.Turbolinks"
4
+ ERROR_CODES = {
5
+ http_failure: 0,
6
+ network_failure: -1,
7
+ content_type_mismatch: -2
8
+ }
9
+ attr_accessor :statusCode
10
+
11
+ def self.pageLoadFailure
12
+ localizedDescription = "The page could not be loaded due to a configuration error."
13
+ errorWithDomain(ERROR_DOMAIN, code: 123, userInfo: { NSLocalizedDescriptionKey => localizedDescription })
14
+ end
15
+
16
+ def self.errorWithCode(code, localizedDescription: localizedDescription)
17
+ errorWithDomain(ERROR_DOMAIN, code: ERROR_CODES[code], userInfo: { NSLocalizedDescriptionKey => localizedDescription })
18
+ end
19
+
20
+ def self.errorWithCode(code, statusCode: statusCode)
21
+ error = errorWithDomain(ERROR_DOMAIN, code: ERROR_CODES[code], userInfo: { "statusCode" => statusCode, NSLocalizedDescriptionKey => "HTTP Error: #{statusCode}" })
22
+ error.statusCode = statusCode
23
+ error
24
+ end
25
+
26
+ def self.errorWithCode(code, error: error)
27
+ errorWithDomain(ERROR_DOMAIN, code: ERROR_CODES[code], userInfo: { "error" => error, NSLocalizedDescriptionKey => error.localizedDescription })
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,109 @@
1
+ module Turbo
2
+ # A "Cold Boot" visit is the initial visit to load the page, including all resources
3
+ # Subsequent visits go through Turbo and use `JavaScriptVisit`
4
+ class ColdBootVisit < Visit
5
+ attr_reader :navigation
6
+
7
+ def startVisit
8
+ log("startVisit")
9
+ webView.navigationDelegate = self
10
+ bridge.pageLoadDelegate = self
11
+
12
+ request = NSURLRequest.alloc.initWithURL(location)
13
+ @navigation = webView.loadRequest(request)
14
+
15
+ delegate.visitDidStart(self) if delegate
16
+ startRequest
17
+ end
18
+
19
+ def cancelVisit
20
+ log("cancelVisit")
21
+ removeNavigationDelegate
22
+ webView.stopLoading
23
+ finishRequest
24
+ end
25
+
26
+ def completeVisit
27
+ log("completeVisit")
28
+ removeNavigationDelegate
29
+ delegate.visitDidInitializeWebView(self) if delegate
30
+ end
31
+
32
+ def failVisit
33
+ log("cancelVisit")
34
+ removeNavigationDelegate
35
+ finishRequest
36
+ end
37
+
38
+ def removeNavigationDelegate
39
+ if webView.navigationDelegate == self
40
+ webView.navigationDelegate = nil
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def log(name)
47
+ debugLog("[ColdBootVisit] #{name} #{location.absoluteString}")
48
+ end
49
+
50
+ # WKNavigationDelegate methods
51
+
52
+ public
53
+
54
+ def webView(webView, didFinishNavigation: navigation)
55
+ if navigation == self.navigation
56
+ finishRequest
57
+ end
58
+ end
59
+
60
+ def webView(webView, decidePolicyForNavigationAction: navigationAction, decisionHandler: decisionHandler)
61
+ @navigationActionDecisionHandler = decisionHandler
62
+ # Ignore any clicked links before the cold boot finishes navigation
63
+ if navigationAction.navigationType == WKNavigationTypeLinkActivated
64
+ @navigationActionDecisionHandler.call(WKNavigationActionPolicyCancel)
65
+ if url = navigationAction.request.URL
66
+ UIApplication.sharedApplication.openURL(url)
67
+ end
68
+ else
69
+ @navigationActionDecisionHandler.call(WKNavigationActionPolicyAllow)
70
+ end
71
+ end
72
+
73
+ def webView(webView, decidePolicyForNavigationResponse: navigationResponse, decisionHandler: decisionHandler)
74
+ if httpResponse = navigationResponse.response
75
+ if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300
76
+ decisionHandler.call(WKNavigationResponsePolicyAllow)
77
+ else
78
+ decisionHandler.call(WKNavigationResponsePolicyCancel)
79
+ fail(TurboError.errorWithCode(:http_failure, statusCode: httpResponse.statusCode))
80
+ end
81
+ else
82
+ decisionHandler.call(WKNavigationResponsePolicyCancel)
83
+ fail(TurboError.errorWithCode(:network_failure, localizedDescription: "An unknown error occurred"))
84
+ end
85
+ end
86
+
87
+ def webView(webView, didFailProvisionalNavigation: navigation, withError: originalError)
88
+ if navigation == self.navigation
89
+ fail(TurboError.errorWithCode(:network_failure, error: originalError))
90
+ end
91
+ end
92
+
93
+ def webView(webView, didFailNavigation: navigation, withError: originalError)
94
+ if navigation === self.navigation
95
+ fail(TurboError.errorWithCode(:network_failure, error: originalError))
96
+ end
97
+ end
98
+
99
+ # WebViewPageLoadDelegate
100
+
101
+ def webView(webView, didLoadPageWithRestorationIdentifier: restorationIdentifier)
102
+ @restorationIdentifier = restorationIdentifier
103
+ delegate.visitDidRender(self) if delegate
104
+ complete
105
+ end
106
+
107
+ end
108
+ end
109
+
@@ -0,0 +1,107 @@
1
+ module Turbo
2
+ # A JavaScript managed visit through the Turbo library
3
+ # All visits are JavaScriptVisits except the initial ColdBootVisit
4
+ # or if a reload() is issued
5
+ class JavaScriptVisit < Visit
6
+ attr_writer :identifier, :hasCachedSnapshot
7
+ def identifier
8
+ @identifier ||= "(pending)"
9
+ end
10
+
11
+ def initWithVisitable(visitable, options: options, bridge: bridge, restorationIdentifier: restorationIdentifier)
12
+ initWithVisitable(visitable, options: options, bridge: bridge)
13
+ self.restorationIdentifier = restorationIdentifier
14
+ self
15
+ end
16
+
17
+ def description
18
+ "<#{dynamicType} #{identifier}: state=#{state} location=#{location}>"
19
+ end
20
+
21
+ def startVisit
22
+ log("startVisit")
23
+ bridge.visitDelegate = self
24
+ bridge.visitLocation(location, withOptions: options, restorationIdentifier: restorationIdentifier)
25
+ end
26
+
27
+ def cancelVisit
28
+ log("cancelVisit")
29
+ bridge.cancelVisitWithIdentifier(identifier)
30
+ finishRequest
31
+ end
32
+
33
+ def failVisit
34
+ log("failVisit")
35
+ finishRequest
36
+ end
37
+
38
+ # WebViewVisitDelegate
39
+
40
+ def webView(webView, didStartVisitWithIdentifier: identifier, hasCachedSnapshot: hasCachedSnapshot)
41
+ log("didStartVisitWithIdentifier", arguments: { identifier: identifier, hasCachedSnapshot: hasCachedSnapshot })
42
+ self.identifier = identifier
43
+ self.hasCachedSnapshot = hasCachedSnapshot
44
+
45
+ delegate.visitDidStart(self) if delegate
46
+ end
47
+
48
+ def webView(webView, didStartRequestForVisitWithIdentifier: identifier, date: date)
49
+ log("didStartRequestForVisitWithIdentifier", arguments: { identifier: identifier, date: date })
50
+ if identifier == self.identifier
51
+ startRequest
52
+ end
53
+ end
54
+
55
+ def webView(webView, didCompleteRequestForVisitWithIdentifier: identifier)
56
+ log("didCompleteRequestForVisitWithIdentifier", arguments: { identifier: identifier })
57
+ if identifier == self.identifier
58
+ delegate.visitWillLoadResponse(self) if hasCachedSnapshot && delegate
59
+ end
60
+ end
61
+
62
+ def webView(webView, didFailRequestForVisitWithIdentifier: identifier, statusCode: statusCode)
63
+ log("didCompleteRequestForVisitWithIdentifier", arguments: { identifier: identifier, statusCode: statusCode })
64
+ if identifier == self.identifier
65
+ # TODO implemented differently
66
+ if statusCode == 0
67
+ error = TurboError.errorWithCode(:network_failure, localizedDescription: "A network error occurred.")
68
+ else
69
+ error = TurboError.errorWithCode(:http_failure, statusCode: statusCode)
70
+ end
71
+ fail(error)
72
+ end
73
+ end
74
+
75
+ def webView(webView, didFinishRequestForVisitWithIdentifier: identifier, date: date)
76
+ log("didFinishRequestForVisitWithIdentifier", arguments: { identifier: identifier, date: date })
77
+ if identifier == self.identifier
78
+ finishRequest
79
+ end
80
+ end
81
+
82
+ def webView(webView, didRenderForVisitWithIdentifier: identifier)
83
+ log("didRenderForVisitWithIdentifier", arguments: { identifier: identifier })
84
+ if identifier == self.identifier
85
+ delegate.visitDidRender(self) if delegate
86
+ end
87
+ end
88
+
89
+ def webView(webView, didCompleteVisitWithIdentifier: identifier, restorationIdentifier: restorationIdentifier)
90
+ log("didCompleteVisitWithIdentifier", arguments: { identifier: identifier, restorationIdentifier: restorationIdentifier })
91
+ if identifier == self.identifier
92
+ @restorationIdentifier = restorationIdentifier
93
+ complete
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def log(name)
100
+ log(name, arguments: nil)
101
+ end
102
+
103
+ def log(name, arguments: arguments)
104
+ debugLog("[JavaScriptVisit] #{name} #{location.absoluteString}", arguments: arguments)
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,91 @@
1
+ module Turbo
2
+ class Visit
3
+ attr_accessor :delegate, :restorationIdentifier
4
+ attr_reader :visitable,
5
+ :webView,
6
+ :bridge,
7
+ :options,
8
+ :state,
9
+ :location,
10
+ :hasCachedSnapshot
11
+
12
+ def initWithVisitable(visitable, options: options, bridge: bridge)
13
+ raise visitable.visitableURL.inspect if visitable.visitableURL.is_a?(Hash)# TODO
14
+ raise options.inspect unless options.is_a?(VisitOptions)# TODO
15
+ @visitable = visitable
16
+ @location = visitable.visitableURL
17
+ @options = options
18
+ @bridge = bridge
19
+ @webView = @bridge.webView
20
+ @state = :initialized
21
+ @hasCachedSnapshot = false
22
+ self
23
+ end
24
+
25
+ def start
26
+ if state == :initialized
27
+ delegate.visitWillStart(self) if delegate
28
+ @state = :started
29
+ startVisit
30
+ end
31
+ end
32
+
33
+ def cancel
34
+ if state == :started
35
+ @state = :canceled
36
+ cancelVisit
37
+ end
38
+ end
39
+
40
+ def complete
41
+ if state == :started
42
+ @state = :completed
43
+ completeVisit
44
+ delegate.visitDidComplete(self) if delegate
45
+ delegate.visitDidFinish(self) if delegate
46
+ end
47
+ end
48
+
49
+ def fail(error)
50
+ if state == :started
51
+ @state = :failed
52
+ delegate.visit(self, requestDidFailWithError: error) if delegate
53
+ failVisit
54
+ delegate.visitDidFail(self) if delegate
55
+ delegate.visitDidFinish(self) if delegate
56
+ end
57
+ end
58
+
59
+ # Hooks for subclasses
60
+ def startVisit; end
61
+ def cancelVisit; end
62
+ def completeVisit; end
63
+ def failVisit; end
64
+
65
+ private
66
+
67
+ # Request state
68
+
69
+ def requestStarted
70
+ @requestStarted ||= false
71
+ end
72
+
73
+ def requestFinished
74
+ @requestFinished ||= false
75
+ end
76
+
77
+ def startRequest
78
+ unless requestStarted
79
+ @requestStarted = true
80
+ delegate.visitRequestDidStart(self) if delegate
81
+ end
82
+ end
83
+
84
+ def finishRequest
85
+ if requestStarted && !requestFinished
86
+ @requestFinished = true
87
+ delegate.visitRequestDidFinish(self) if delegate
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,36 @@
1
+ module Turbo
2
+ class VisitOptions
3
+ attr_reader :action, :response
4
+
5
+ # TODO not used for now
6
+ def initWithAction(action, response: response)
7
+ @action = action
8
+ if response
9
+ @response = VisitResponse.alloc.initWithStatusCode(response["statusCode"], responseHTML: response["innerHTML"])
10
+ end
11
+ self
12
+ end
13
+
14
+ def initFromHash(options)
15
+ raise "options is not a Hash" unless options.is_a?(Hash) # TODO remove?
16
+ @action = options["action"] || 'advance'
17
+ if response = options["response"]
18
+ @response = VisitResponse.alloc.initWithStatusCode(response["statusCode"], responseHTML: response["responseHTML"])
19
+ end
20
+ self
21
+ end
22
+
23
+ # TODO Codable, JSONCodable?
24
+ def encode
25
+ if response
26
+ { action: @action, response: response.encode }
27
+ else
28
+ { action: @action }
29
+ end
30
+ end
31
+
32
+ def inspect
33
+ "#<VisitOptions action=#{action} response=#{response}>"
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ module Turbo
2
+ class VisitProposal
3
+ attr_reader :url, :options, :properties
4
+
5
+ def initWithURL(url, options: options, properties: properties)
6
+ @url = url
7
+ raise unless options.is_a?(VisitOptions)
8
+ @options = options
9
+ @properties = properties || {}
10
+ self
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module Turbo
2
+ class VisitResponse
3
+ attr_reader :statusCode, :responseHTML
4
+
5
+ def initWithStatusCode(statusCode, responseHTML: responseHTML)
6
+ @statusCode = statusCode
7
+ @responseHTML = responseHTML
8
+ self
9
+ end
10
+
11
+ # TODO Codable, JSONCodable?
12
+ def encode
13
+ { statusCode: statusCode, responseHTML: responseHTML }
14
+ end
15
+
16
+ def inspect
17
+ "#<VisitResponse statusCode=#{statusCode} responseHTML=#{responseHTML.inspect}>"
18
+ end
19
+
20
+ #TODO public var isSuccessful: Bool {
21
+ #switch statusCode {
22
+ #case 200...299:
23
+ #return true
24
+ #default:
25
+ #return false
26
+ #}
27
+ #}
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ module Turbo
2
+ module Visitable
3
+ attr_accessor :visitableDelegate
4
+ attr_reader :visitableView, :visitableURL
5
+
6
+ def reloadVisitable
7
+ visitableDelegate.visitableDidRequestReload(self) if visitableDelegate
8
+ end
9
+
10
+ def showVisitableActivityIndicator
11
+ visitableView.showActivityIndicator
12
+ end
13
+
14
+ def hideVisitableActivityIndicator
15
+ visitableView.hideActivityIndicator
16
+ end
17
+
18
+ def activateVisitableWebView(webView)
19
+ visitableView.activateWebView(webView, forVisitable: self)
20
+ end
21
+
22
+ def deactivateVisitableWebView
23
+ visitableView.deactivateWebView
24
+ end
25
+
26
+ def updateVisitableScreenshot
27
+ visitableView.updateScreenshot
28
+ end
29
+
30
+ def showVisitableScreenshot
31
+ visitableView.showScreenshot
32
+ end
33
+
34
+ def hideVisitableScreenshot
35
+ visitableView.hideScreenshot
36
+ end
37
+
38
+ def clearVisitableScreenshot
39
+ visitableView.clearScreenshot
40
+ end
41
+
42
+ def visitableWillRefresh
43
+ visitableView.refreshControl.beginRefreshing
44
+ end
45
+
46
+ def visitableDidRefresh
47
+ visitableView.refreshControl.endRefreshing
48
+ end
49
+
50
+ def visitableViewDidRequestRefresh
51
+ visitableDelegate.visitableDidRequestRefresh(self) if visitableDelegate
52
+ end
53
+
54
+ def visitableViewController
55
+ self
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,20 @@
1
+ module Turbo
2
+ class VisitableView < UIView
3
+ include WebView
4
+ include Screenshots
5
+ include RefreshControl
6
+ include ActivityIndicator
7
+ include Constraints
8
+ include ScrollView
9
+
10
+ def initWithFrame(frame)
11
+ super
12
+ setup
13
+ self
14
+ end
15
+
16
+ def setup
17
+ installActivityIndicatorView
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,71 @@
1
+ module Turbo
2
+ class VisitableViewController < UIViewController
3
+ include Visitable
4
+
5
+ attr_accessor :visitableDelegate
6
+ attr_reader :visitableURL
7
+
8
+ def initWithURL(url)
9
+ if url.is_a? String
10
+ url = NSURL.alloc.initWithString(url)
11
+ end
12
+ @visitableURL = url
13
+ self
14
+ end
15
+
16
+ # View Lifecycle methods
17
+
18
+ def viewDidLoad
19
+ super
20
+ installVisitableView
21
+ end
22
+
23
+ def viewWillAppear(animated)
24
+ super
25
+ visitableDelegate.visitableViewWillAppear(self) if visitableDelegate
26
+ end
27
+
28
+ def viewDidAppear(animated)
29
+ super
30
+ visitableDelegate.visitableViewDidAppear(self) if visitableDelegate
31
+ end
32
+
33
+ # Visitable
34
+
35
+ def visitableDidRender
36
+ self.title = visitableView.webView.title if visitableView.webView
37
+ end
38
+
39
+ def showVisitableActivityIndicator
40
+ visitableView.showActivityIndicator
41
+ end
42
+
43
+ def hideVisitableActivityIndicator
44
+ visitableView.hideActivityIndicator
45
+ end
46
+
47
+ # Visitable View
48
+
49
+ def visitableView
50
+ @visitableView ||= begin
51
+ view = VisitableView.alloc.initWithFrame(CGRectZero)
52
+ view.translatesAutoresizingMaskIntoConstraints = false
53
+ view
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def installVisitableView
60
+ view.addSubview(visitableView)
61
+ #view.insertSubview(visitableView, atIndex: 0)
62
+ self.edgesForExtendedLayout = UIRectEdgeAll # TODO document fix for fullscre
63
+ NSLayoutConstraint.activateConstraints([
64
+ visitableView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
65
+ visitableView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor),
66
+ visitableView.topAnchor.constraintEqualToAnchor(view.topAnchor),
67
+ visitableView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
68
+ ])
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,36 @@
1
+ module Turbo
2
+ class VisitableView < UIView
3
+ module ActivityIndicator
4
+ def activityIndicatorView
5
+ @activityIndicatorView ||= begin
6
+ view = UIActivityIndicatorView.alloc.initWithActivityIndicatorStyle(UIActivityIndicatorViewStyleMedium)
7
+ view.translatesAutoresizingMaskIntoConstraints = false
8
+ view.color = UIColor.grayColor
9
+ view.hidesWhenStopped = true
10
+ view
11
+ end
12
+ end
13
+
14
+ def showActivityIndicator
15
+ if !isRefreshing
16
+ activityIndicatorView.startAnimating
17
+ bringSubviewToFront(activityIndicatorView)
18
+ end
19
+ end
20
+
21
+ def hideActivityIndicator
22
+ activityIndicatorView.stopAnimating
23
+ end
24
+
25
+ private
26
+
27
+ def installActivityIndicatorView
28
+ addSubview(activityIndicatorView)
29
+ NSLayoutConstraint.activateConstraints([
30
+ activityIndicatorView.centerXAnchor.constraintEqualToAnchor(centerXAnchor),
31
+ activityIndicatorView.centerYAnchor.constraintEqualToAnchor(centerYAnchor)
32
+ ])
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ module Turbo
2
+ class VisitableView < UIView
3
+ module Constraints
4
+ def addFillConstraintsForSubview(view)
5
+ NSLayoutConstraint.activateConstraints([
6
+ view.leadingAnchor.constraintEqualToAnchor(leadingAnchor),
7
+ view.trailingAnchor.constraintEqualToAnchor(trailingAnchor),
8
+ view.topAnchor.constraintEqualToAnchor(topAnchor),
9
+ view.bottomAnchor.constraintEqualToAnchor(bottomAnchor)
10
+ ])
11
+ end
12
+ end
13
+ end
14
+ end