rmx 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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