rmx 0.6.0

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/Gemfile +7 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +40 -0
  6. data/Rakefile +18 -0
  7. data/app/app_delegate.rb +5 -0
  8. data/lib/motion/RMXActionSheet.rb +14 -0
  9. data/lib/motion/RMXAutoLayoutLabel.rb +19 -0
  10. data/lib/motion/RMXAutoLayoutScrollView.rb +53 -0
  11. data/lib/motion/RMXCommonMethods.rb +27 -0
  12. data/lib/motion/RMXEventsFromProxy.rb +103 -0
  13. data/lib/motion/RMXExecutionBlock.rb +51 -0
  14. data/lib/motion/RMXKeyboardHelpers.rb +54 -0
  15. data/lib/motion/RMXLongTask.rb +133 -0
  16. data/lib/motion/RMXNavigationController.rb +133 -0
  17. data/lib/motion/RMXObjectExtensions.rb +10 -0
  18. data/lib/motion/RMXSegmentedController.rb +71 -0
  19. data/lib/motion/RMXSetAttributes.rb +34 -0
  20. data/lib/motion/RMXStrongToWeakHash.rb +46 -0
  21. data/lib/motion/RMXSynchronizedStrongToWeakHash.rb +40 -0
  22. data/lib/motion/RMXTableHandler.rb +212 -0
  23. data/lib/motion/RMXTableViewCell.rb +85 -0
  24. data/lib/motion/RMXTableViewCellInnerContentView.rb +16 -0
  25. data/lib/motion/RMXTableViewController.rb +65 -0
  26. data/lib/motion/RMXUnsafeUnretainedHolder.rb +30 -0
  27. data/lib/motion/RMXView.rb +84 -0
  28. data/lib/motion/RMXViewController.rb +65 -0
  29. data/lib/motion/RMXViewControllerPresentation.rb +127 -0
  30. data/lib/motion/RMXWeakHolder.rb +36 -0
  31. data/lib/motion/RMXWeakToStrongHash.rb +39 -0
  32. data/lib/motion/accessors.rb +29 -0
  33. data/lib/motion/base.rb +12 -0
  34. data/lib/motion/env.rb +9 -0
  35. data/lib/motion/events.rb +61 -0
  36. data/lib/motion/layout.rb +357 -0
  37. data/lib/motion/ui.rb +55 -0
  38. data/lib/motion/util.rb +155 -0
  39. data/lib/rmx/version.rb +3 -0
  40. data/lib/rmx.rb +50 -0
  41. data/resources/Default-568h@2x.png +0 -0
  42. data/rmx.gemspec +19 -0
  43. data/spec/main_spec.rb +240 -0
  44. metadata +86 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9cd874bc129e8aef4429698b30fe2bc429c419f0
4
+ data.tar.gz: dcc6f40d497510993ea5a28c7b6dee46868552e9
5
+ SHA512:
6
+ metadata.gz: c900776ee05bcd66a894871dec855c5589493b28effd6e6b227386587402fabd355d194f19cf99fcc1bf140d9225ae22104705f6ffda2a445686f9754e73566f
7
+ data.tar.gz: e97f08b80bb2568f896af0717cb07124dca1bdd615bdb4590751ef278397a612adc47fa80c5eee2c336704843176b79cf224d60dcf569a251ba38b3601a287df
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ build
19
+ build*
20
+ *.bridgesupport
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :test, :development do
4
+ gem 'rake'
5
+ end
6
+
7
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Joe Noon
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ RMX
2
+ -----------------
3
+
4
+ #### Extensions and helpers for dealing with various areas of rubymotion and iOS.
5
+
6
+ #### [Documentation](https://github.com/joenoon/rmx/wiki/RMX-Documentation)
7
+
8
+ #### [CHANGELOG](https://github.com/joenoon/rmx/wiki/CHANGELOG)
9
+
10
+ Installation
11
+ -----------------
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'rmx'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Contributing
22
+ -----------------
23
+
24
+ If you have a better way to accomplish anything this library is doing, please share!
25
+
26
+ 1. Fork it
27
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
28
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
29
+ 4. Push to the branch (`git push origin my-new-feature`)
30
+ 5. Create new Pull Request
31
+
32
+ License
33
+ -----------------
34
+
35
+ Please see [LICENSE](https://github.com/joenoon/rmx/blob/master/LICENSE.txt) for licensing details.
36
+
37
+ Author
38
+ -----------------
39
+
40
+ [Joe Noon](https://github.com/joenoon)
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+ if ENV['rubymotion']
3
+ $:.unshift('/Library/RubyMotion/lib')
4
+ if ENV.fetch('platform', 'ios') == 'ios'
5
+ require 'motion/project/template/ios'
6
+ elsif ENV['platform'] == 'osx'
7
+ require 'motion/project/template/osx'
8
+ else
9
+ raise "Unsupported platform #{ENV['platform']}"
10
+ end
11
+ require 'bundler'
12
+ Bundler.require
13
+
14
+ Motion::Project::App.setup do |app|
15
+ # Use `rake config' to see complete project settings.
16
+ app.name = 'rmx'
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ class AppDelegate
2
+ def application(application, didFinishLaunchingWithOptions:launchOptions)
3
+ true
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ class RMXActionSheet < UIActionSheet
2
+
3
+ def init
4
+ s = super
5
+ self.delegate = self
6
+ s
7
+ end
8
+
9
+ def actionSheet(actionSheet, clickedButtonAtIndex:buttonIndex)
10
+ title = actionSheet.buttonTitleAtIndex(buttonIndex)
11
+ RMX.new(self).trigger(:clickedButton, { :index => buttonIndex, :title => title })
12
+ end
13
+
14
+ end
@@ -0,0 +1,19 @@
1
+ class RMXAutoLayoutLabel < UILabel
2
+ def layoutSubviews
3
+ super
4
+ if numberOfLines == 0
5
+ if preferredMaxLayoutWidth != frame.size.width
6
+ self.preferredMaxLayoutWidth = frame.size.width
7
+ setNeedsUpdateConstraints
8
+ end
9
+ end
10
+ end
11
+ def intrinsicContentSize
12
+ s = super
13
+ if numberOfLines == 0
14
+ # found out that sometimes intrinsicContentSize is 1pt too short!
15
+ s.height += 1
16
+ end
17
+ s
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ class RMXAutoLayoutScrollView < UIScrollView
2
+ class FollowView < UIView
3
+ RMX.new(self).weak_attr_accessor :fittedView
4
+ def layoutSubviews
5
+ s = super
6
+ fittedView.invalidateIntrinsicContentSize
7
+ fittedView.setNeedsUpdateConstraints
8
+ fittedView.layoutIfNeeded
9
+ s
10
+ end
11
+ end
12
+ class FittedView < UIView
13
+ RMX.new(self).weak_attr_accessor :followView
14
+ def intrinsicContentSize
15
+ followView.frame.size
16
+ end
17
+ end
18
+ attr_accessor :contentView
19
+ def self.fitted_to(parent)
20
+ followView = FollowView.new
21
+ followView.userInteractionEnabled = false
22
+ followView.hidden = true
23
+ fittedView = FittedView.new
24
+ fittedView.backgroundColor = UIColor.clearColor
25
+ fittedView.followView = followView
26
+ followView.fittedView = fittedView
27
+ # add a subview to parent called followView
28
+ RMX::Layout.new do |layout|
29
+ layout.view parent
30
+ layout.subviews "x" => followView
31
+ layout.eqs %Q{
32
+ x.top == 0
33
+ x.right == 0
34
+ x.bottom == 0
35
+ x.left == 0
36
+ }
37
+ end
38
+ x = new
39
+ x.contentView = fittedView
40
+ # add a subview to scrollView called contentView
41
+ RMX::Layout.new do |layout|
42
+ layout.view x
43
+ layout.subviews "x" => fittedView
44
+ layout.eqs %Q{
45
+ x.top == 0
46
+ x.right == 0
47
+ x.bottom == 0
48
+ x.left == 0
49
+ }
50
+ end
51
+ x
52
+ end
53
+ end
@@ -0,0 +1,27 @@
1
+ module RMXCommonMethods
2
+
3
+ def rmx_dealloc
4
+ end
5
+
6
+ if RMX::DEBUG_DEALLOC
7
+ def dealloc
8
+ p " - dealloc! (queue: #{Dispatch::Queue.current.description})"
9
+ rmx_dealloc
10
+ super
11
+ end
12
+ else
13
+ def dealloc
14
+ rmx_dealloc
15
+ super
16
+ end
17
+ end
18
+
19
+ def description
20
+ rmx_object_desc
21
+ end
22
+
23
+ def inspect
24
+ rmx_object_desc
25
+ end
26
+
27
+ end
@@ -0,0 +1,103 @@
1
+ # Proxy class used to hold the actual handlers and contexts of handlers.
2
+ # When the real class deallocates, all handlers are removed.
3
+ class RMXEventsFromProxy
4
+
5
+ QUEUE = Dispatch::Queue.new("RMXEventsFromProxy")
6
+
7
+ def initialize
8
+ @events = {}
9
+ end
10
+
11
+ # def rmx_dealloc
12
+ # off
13
+ # super
14
+ # end
15
+
16
+ def on(event, opts={}, &block)
17
+ block ||= proc {}
18
+ event = event.to_s
19
+ execution_block = RMXExecutionBlock.new
20
+ execution_block.strong = opts.delete(:strong)
21
+ execution_block.limit = opts.delete(:limit)
22
+ execution_block.queue = opts.delete(:queue)
23
+ execution_block.meta = opts
24
+ execution_block.block = block
25
+ off_block = RMXExecutionBlock.new
26
+ off_block.block = proc do
27
+ off(event, execution_block)
28
+ end
29
+ QUEUE.sync do
30
+ @events[event] ||= []
31
+ @events[event] << execution_block
32
+ end
33
+ off_block
34
+ end
35
+
36
+ def now_and_on(event, opts={}, &block)
37
+ RMX.block_on_main_q(block)
38
+ on(event, opts, &block)
39
+ end
40
+
41
+ def off(event=nil, execution_block=nil)
42
+ QUEUE.sync do
43
+ if event.is_a?(String) || event.is_a?(Symbol)
44
+ event = event.to_s
45
+ if execution_block
46
+ if events = @events[event]
47
+ deleted = events.delete(execution_block)
48
+ if RMX::DEBUG_EVENTS
49
+ p "remove the one block for the event", "EVENT:", event, "deleted:", deleted
50
+ end
51
+ end
52
+ else
53
+ deleted = @events.delete(event)
54
+ if RMX::DEBUG_EVENTS
55
+ p "remove all blocks for the event", "EVENT:", event, "deleted:", deleted
56
+ end
57
+ end
58
+ else
59
+ @events.clear
60
+ if RMX::DEBUG_EVENTS
61
+ p "remove all blocks for all events"
62
+ end
63
+ end
64
+ end
65
+ nil
66
+ end
67
+
68
+ def trigger(event, *values)
69
+ QUEUE.async do
70
+ # if RMX::DEBUG_EVENTS
71
+ # p "TRIGGER:", event, values, "@events", @events
72
+ # end
73
+ event = event.to_s
74
+ _execution_blocks = @events[event]
75
+ next unless _execution_blocks
76
+ execution_blocks = _execution_blocks.dup
77
+ while execution_blocks.size > 0
78
+ execution_block = execution_blocks.shift
79
+ exclusive = execution_block.meta[:exclusive]
80
+ if exclusive
81
+ _exclusive = exclusive.dup
82
+ while _exclusive.size > 0
83
+ exclude = _exclusive.shift.to_s
84
+ deleted = @events.delete(exclude)
85
+ if RMX::DEBUG_EVENTS
86
+ p "REMOVING DUE TO EXCLUSIVE:", exclude, "FOR EVENT:", event, "deleted:", deleted
87
+ end
88
+ end
89
+ end
90
+ execution_block.call(*values)
91
+ if execution_block.dead
92
+ deleted = _execution_blocks.delete(execution_block)
93
+ if RMX::DEBUG_EVENTS
94
+ p "REMOVING DUE TO DEAD:", "FOR EVENT:", event, "deleted:", deleted
95
+ end
96
+ end
97
+ end
98
+ end
99
+ nil
100
+ end
101
+
102
+ end
103
+
@@ -0,0 +1,51 @@
1
+ class RMXExecutionBlock
2
+ attr_accessor :queue, :the_block, :dead, :desc, :strong, :meta, :limit
3
+ RMX.new(self).weak_attr_accessor :block_owner
4
+
5
+ def block=(_block)
6
+ self.block_owner = _block.owner
7
+ self.the_block = RMX.safe_block(_block)
8
+ end
9
+
10
+ def actual_queue
11
+ q = queue
12
+ if q == :async
13
+ q = RMXEventsFromProxy::QUEUE
14
+ elsif q == :main
15
+ q = Dispatch::Queue.main
16
+ end
17
+ q ||= Dispatch::Queue.main
18
+ q
19
+ end
20
+
21
+ def call(*args)
22
+ should_cleanup = false
23
+ if !dead && (blk = the_block) && (o = block_owner) && (q = actual_queue)
24
+ q.async do
25
+ blk.call(*args)
26
+ blk
27
+ o
28
+ end
29
+ else
30
+ should_cleanup = true
31
+ end
32
+ if limit
33
+ if limit > 0
34
+ self.limit -= 1
35
+ end
36
+ if limit == 0
37
+ should_cleanup = true
38
+ end
39
+ end
40
+ if should_cleanup
41
+ self.dead = true
42
+ self.strong = nil
43
+ self.meta = nil
44
+ self.limit = nil
45
+ self.the_block = nil
46
+ self.block_owner = nil
47
+ end
48
+ end
49
+
50
+ end
51
+
@@ -0,0 +1,54 @@
1
+ module RMXKeyboardHelpers
2
+
3
+ def keyboard_proxy
4
+ keyboard_proxy_constraints unless @keyboard_proxy_constraints
5
+ @keyboard_proxy
6
+ end
7
+
8
+ def keyboard_proxy_constraints
9
+ @keyboard_proxy ||= UIView.new
10
+ @keyboard_proxy_constraints ||= begin
11
+ x = {}
12
+ RMX::Layout.new do |layout|
13
+ layout.view = view
14
+ layout.subviews = {
15
+ "keyboard_proxy" => @keyboard_proxy
16
+ }
17
+ x[:bottom] = layout.eq "keyboard_proxy.bottom == #{-RMX.currentKeyboardHeight}"
18
+ x[:height] = layout.eq "keyboard_proxy.height == 0"
19
+ end
20
+ x
21
+ end
22
+ end
23
+
24
+ def listenForKeyboardChanged
25
+ NSNotificationCenter.defaultCenter.addObserver(self, selector:'keyboardChangedInternal:', name:"rmxKeyboardChanged", object:nil)
26
+ end
27
+
28
+ # listens for the rmxKeyboardChanged notification and extracts the userInfo to call a friendlier method
29
+ def keyboardChangedInternal(notification)
30
+ if isViewLoaded
31
+ info = notification.userInfo
32
+ keyboardChanged(info)
33
+ end
34
+ end
35
+
36
+ # by default, looks to see if the controller is using the @keyboard_proxy_constraint convention.
37
+ # if so, sets the constraint's constant and refreshes the layout in the same animationDuration
38
+ # as the keyboard's animation.
39
+ #
40
+ # if you want to do more/other stuff on keyboardChanged, you can override this, call super, or
41
+ # do everything on your own.
42
+ def keyboardChanged(info)
43
+ if constraint = @keyboard_proxy_constraints && @keyboard_proxy_constraints[:height]
44
+ Dispatch::Queue.main.async do
45
+ UIView.animateWithDuration(info[:animationDuration], animations: lambda do
46
+ keyboard_proxy_constraints[:bottom].constant = -RMX.currentKeyboardHeight
47
+ view.setNeedsUpdateConstraints
48
+ view.layoutIfNeeded
49
+ end)
50
+ end
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,133 @@
1
+ # RMXLongTask encapsulates beginBackgroundTaskWithExpirationHandler/endBackgroundTask:
2
+ #
3
+ # RMXLongTask.new("my long task") do |task|
4
+ # do_something_long
5
+ # task.end!
6
+ # end
7
+ #
8
+ # RMXLongTask.new("my long task") do |task|
9
+ # do_something_long_async do
10
+ # # later this long task finishes...
11
+ # task.end!
12
+ # end
13
+ # end
14
+ #
15
+ class RMXLongTask
16
+ attr_accessor :bgTask, :desc
17
+
18
+ include RMXCommonMethods
19
+
20
+ def self.time_remaining
21
+ UIApplication.sharedApplication.backgroundTimeRemaining
22
+ end
23
+
24
+ def self.when_all_complete(&block)
25
+ Dispatch::Queue.main.async do
26
+ if RMXLongTask.outstanding_tasks.size.zero?
27
+ RMX.block_on_main_q(block)
28
+ else
29
+ RMX.new(RMXLongTask).once(:all_complete) do
30
+ block.call
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.outstanding_queue
37
+ Dispatch.once do
38
+ @outstanding_queue = Dispatch::Queue.new("#{NSBundle.mainBundle.bundleIdentifier}.outstanding.LongTask")
39
+ end
40
+ @outstanding_queue
41
+ end
42
+
43
+ def self.outstanding_tasks
44
+ Dispatch.once do
45
+ @outstanding_tasks = []
46
+ end
47
+ @outstanding_tasks
48
+ end
49
+
50
+ # RMXLongTask.verbose("my long task") { |task| task.end! }
51
+ def self.verbose(desc=nil, &block)
52
+ _creator(desc, true, true, &block)
53
+ end
54
+
55
+ # RMXLongTask.create("my long task") { |task| task.end! }
56
+ def self.create(desc=nil, &block)
57
+ _creator(desc, false, true, &block)
58
+ end
59
+
60
+ def self.internal(desc=nil, &block)
61
+ _creator(desc, false, false, &block)
62
+ end
63
+
64
+ def self._creator(desc=nil, verbose=false, tracking=true, &block)
65
+ x = new(desc, verbose, tracking)
66
+ block.weak!.call(x)
67
+ x
68
+ end
69
+
70
+ def self.reset_outstanding_tasks!
71
+ RMXLongTask.outstanding_queue.sync do
72
+ size = RMXLongTask.outstanding_tasks.size
73
+ if size > 0
74
+ p "WARNING: reset_outstanding_tasks! (was: #{size})"
75
+ RMXLongTask.outstanding_tasks.removeAllObjects
76
+ end
77
+ end
78
+ end
79
+
80
+ def initialize(desc=nil, verbose=false, tracking=true)
81
+ @verbose = verbose
82
+ @tracking = tracking
83
+ @desc = "#{rmx_object_desc} #{desc}"
84
+ @bgTask = UIApplication.sharedApplication.beginBackgroundTaskWithExpirationHandler(lambda do
85
+ p "ERROR: #{@desc} didn't call #end! in time!"
86
+ __end!
87
+ end)
88
+ if DEBUG_LONGTASK || @verbose
89
+ p "CREATED: #{@desc}"
90
+ end
91
+ if @tracking
92
+ RMXLongTask.outstanding_queue.sync do
93
+ RMXLongTask.outstanding_tasks << self
94
+ end
95
+ end
96
+ self
97
+ end
98
+
99
+ def end!
100
+ if DEBUG_LONGTASK || @verbose
101
+ p "SUCCESS: #{@desc} ended successfully."
102
+ end
103
+ __end!
104
+ end
105
+
106
+ def __end!
107
+ if @tracking
108
+ RMXLongTask.outstanding_queue.sync do
109
+ RMXLongTask.outstanding_tasks.delete(self)
110
+ RMXLongTask.internal("check for all complete") do |internal_task|
111
+ Dispatch::Queue.main.async do
112
+ if RMXLongTask.outstanding_tasks.size.zero?
113
+ RMX.new(RMXLongTask).trigger(:all_complete)
114
+ end
115
+ internal_task.end!
116
+ end
117
+ end
118
+ end
119
+ end
120
+ if @bgTask && @bgTask != UIBackgroundTaskInvalid
121
+ UIApplication.sharedApplication.endBackgroundTask(@bgTask)
122
+ @bgTask = UIBackgroundTaskInvalid
123
+ end
124
+ end
125
+
126
+ def rmx_dealloc
127
+ if DEBUG_DEALLOC
128
+ p "DEALLOC: #{@desc}"
129
+ end
130
+ super
131
+ end
132
+
133
+ end