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
@@ -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, didProposeVisit: 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
+ Turbo.logger.debug("[ColdBootVisit] #{name} #{location.absoluteString}", :cyan)
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
+ Turbo.logger.debug("[JavaScriptVisit] #{name} #{location.absoluteString}", :green)
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