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
@@ -0,0 +1,30 @@
1
+ class RMXUnsafeUnretainedHolder
2
+
3
+ def initialize(_value=nil)
4
+ @value_desc = _value.rmx_object_desc
5
+ self.value = _value
6
+ self
7
+ end
8
+
9
+ def value
10
+ @holder.nonretainedObjectValue
11
+ end
12
+
13
+ def value=(_value)
14
+ @holder = NSValue.valueWithNonretainedObject(_value)
15
+ _value
16
+ end
17
+
18
+ def hash
19
+ value.hash
20
+ end
21
+
22
+ def eql?(other)
23
+ other.hash == value.hash
24
+ end
25
+
26
+ def inspect
27
+ "(unsafe)#{@value_desc}"
28
+ end
29
+
30
+ end
@@ -0,0 +1,84 @@
1
+ class RMXView < UIView
2
+
3
+ include RMXCommonMethods
4
+ include RMXSetAttributes
5
+
6
+ attr_accessor :updatedSize, :reportSizeChanges
7
+
8
+ def rmx_dealloc
9
+ NSNotificationCenter.defaultCenter.removeObserver(self)
10
+ super
11
+ end
12
+
13
+ def prepare
14
+ end
15
+
16
+ def setup
17
+ end
18
+
19
+ def init
20
+ s = super
21
+ prepare
22
+ setUserInteractionEnabled(false)
23
+ setup
24
+ s
25
+ end
26
+
27
+ def setUserInteractionEnabled(bool)
28
+ @userInteractionEnabled = bool
29
+ end
30
+
31
+ def self.create(attributes={})
32
+ x = new
33
+ x.attributes = attributes
34
+ x
35
+ end
36
+
37
+ # normal userInteractionEnabled means the view and all subviews can't be clicked. what we normally
38
+ # want is subviews to be clickable, but not the parent. this custom hitTest allows that behavior.
39
+ def hitTest(point, withEvent:event)
40
+ s = super
41
+ if s == self && @userInteractionEnabled == false
42
+ return nil
43
+ end
44
+ s
45
+ end
46
+
47
+ def requiresConstraintBasedLayout
48
+ true
49
+ end
50
+
51
+ def layoutSubviews
52
+ s = super
53
+ if reportSizeChanges
54
+ Dispatch::Queue.main.async do
55
+ size = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
56
+ unless updatedSize == size
57
+ self.updatedSize = size
58
+ RMX.new(self).trigger(:updatedSize, size)
59
+
60
+ if tableView = is_or_within_a?(UITableView)
61
+ if tableView.delegate.respondsToSelector('tableView:viewDidUpdateSize:')
62
+ tableView.delegate.tableView(tableView, viewDidUpdateSize:self)
63
+ end
64
+ # p "unbounced reload"
65
+ RMX.new(self).debounce(:reloadTableUpdatedSize) do
66
+ # p "debounced reload"
67
+ if controller = tableView.lAncestorViewController
68
+ if controller.viewState == :viewDidAppear
69
+ tableView.beginUpdates
70
+ tableView.endUpdates
71
+ else
72
+ tableView.reloadData
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ end
79
+ end
80
+ end
81
+ s
82
+ end
83
+
84
+ end
@@ -0,0 +1,65 @@
1
+ class RMXViewController < UIViewController
2
+
3
+ include RMXCommonMethods
4
+ include RMXViewControllerPresentation
5
+ include RMXKeyboardHelpers
6
+ include RMXSetAttributes
7
+
8
+ def init
9
+ s = super
10
+ NSNotificationCenter.defaultCenter.addObserver(self, selector:'refresh', name:UIApplicationWillEnterForegroundNotification, object:nil)
11
+ listenForKeyboardChanged
12
+ prepare
13
+ s
14
+ end
15
+
16
+ def prepare
17
+ end
18
+
19
+ def loaded
20
+ end
21
+
22
+ def refresh
23
+ end
24
+
25
+ def viewDidLoad
26
+ s = super
27
+ loaded
28
+ s
29
+ end
30
+
31
+ def viewWillAppear(animated)
32
+ s = super
33
+ rmx_viewWillAppear(animated)
34
+ s
35
+ end
36
+
37
+ def viewDidAppear(animated)
38
+ s = super
39
+ rmx_viewDidAppear(animated)
40
+ s
41
+ end
42
+
43
+ def viewWillDisappear(animated)
44
+ s = super
45
+ rmx_viewWillDisappear(animated)
46
+ s
47
+ end
48
+
49
+ def viewDidDisappear(animated)
50
+ s = super
51
+ rmx_viewDidDisappear(animated)
52
+ s
53
+ end
54
+
55
+ def didReceiveMemoryWarning
56
+ p "didReceiveMemoryWarning"
57
+ super
58
+ end
59
+
60
+ def rmx_dealloc
61
+ NSNotificationCenter.defaultCenter.removeObserver(self)
62
+ super
63
+ end
64
+
65
+ end
@@ -0,0 +1,127 @@
1
+ module RMXViewControllerPresentation
2
+
3
+ def self.included(klass)
4
+ klass.send(:include, InstanceMethods)
5
+ klass.send(:attr_accessor, :viewState)
6
+ end
7
+
8
+ module FactoryMethods
9
+
10
+ # presentViewController should always be called on the next runloop to avoid quirks.
11
+ # this just wraps that behavior
12
+ def present(opts)
13
+ unless [ :origin, :view_controller, :animated, :completion ].all? { |x| opts.key?(x) }
14
+ raise "Missing RMXViewControllerPresentation.present opts: #{opts.inspect}"
15
+ end
16
+ Dispatch::Queue.main.async do
17
+ opts[:origin].presentViewController(opts[:view_controller], animated:opts[:animated], completion:opts[:completion])
18
+ end
19
+ end
20
+
21
+ # remove the controller from the display heirarchy, taking into account how it is
22
+ # currently presented. avoid nesting animations and corrupting the UI by using
23
+ # whenOrIfViewState and executing on the main thread async to ensure it is not yanked out of the
24
+ # UI during existing animations
25
+ def dismiss(opts)
26
+ unless [ :view_controller, :animated, :completion ].all? { |x| opts.key?(x) }
27
+ raise "Missing RMXViewControllerPresentation.dismiss opts: #{opts.inspect}"
28
+ end
29
+ animated = opts[:animated]
30
+ block = opts[:completion]
31
+ view_controller = opts[:view_controller]
32
+ navigationController = view_controller.navigationController
33
+
34
+
35
+ if view_controller.presentingViewController
36
+ Dispatch::Queue.main.async do
37
+ view_controller.dismissViewControllerAnimated(animated, completion:block)
38
+ end
39
+ elsif navigationController
40
+ if index = navigationController.viewControllers.index(view_controller)
41
+ before_index = index - 1
42
+ before_index = 0 if index < 0
43
+ pop_to_controller = navigationController.viewControllers[before_index]
44
+ if pop_to_controller && pop_to_controller != navigationController.viewControllers.last
45
+ # p "pop_to_controller", pop_to_controller
46
+ # p "navigationController.popToViewController(pop_to_controller, animated:animated)"
47
+ Dispatch::Queue.main.async do
48
+ RMX.new(navigationController).once(:done_animating, &block) if block
49
+ navigationController.popToViewController(pop_to_controller, animated:animated)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+ extend FactoryMethods
58
+
59
+ module InstanceMethods
60
+
61
+ def triggerViewState!(animated)
62
+ # p "triggerViewState!", @viewState, animated
63
+ RMX.new(self).trigger(@viewState, animated)
64
+ end
65
+
66
+ def whenOrIfViewState(viewState, &block)
67
+ if viewState == @viewState
68
+ block.call
69
+ else
70
+ RMX.new(self).once(viewState, &block)
71
+ end
72
+ end
73
+
74
+ def appearing(animated)
75
+ end
76
+
77
+ def rmx_viewWillAppear(animated)
78
+ @viewState = :viewWillAppear
79
+ triggerViewState!(animated)
80
+ appearing(animated)
81
+ end
82
+
83
+ def appeared(animated)
84
+ end
85
+
86
+ def rmx_viewDidAppear(animated)
87
+ @viewState = :viewDidAppear
88
+ triggerViewState!(animated)
89
+ appeared(animated)
90
+ end
91
+
92
+ def disappearing(animated)
93
+ end
94
+
95
+ def rmx_viewWillDisappear(animated)
96
+ @viewState = :viewWillDisappear
97
+ triggerViewState!(animated)
98
+ disappearing(animated)
99
+ end
100
+
101
+ def disappeared(animated)
102
+ end
103
+
104
+ def rmx_viewDidDisappear(animated)
105
+ @viewState = :viewDidDisappear
106
+ triggerViewState!(animated)
107
+ disappeared(animated)
108
+ end
109
+
110
+ def present(vc, animated=false, &block)
111
+ RMXViewControllerPresentation.present({
112
+ :origin => self,
113
+ :view_controller => vc,
114
+ :animated => animated,
115
+ :completion => block
116
+ })
117
+ end
118
+
119
+ def dismiss(animated=false, &block)
120
+ RMXViewControllerPresentation.dismiss({
121
+ :view_controller => self,
122
+ :animated => animated,
123
+ :completion => block
124
+ })
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,36 @@
1
+ class RMXWeakHolder
2
+
3
+ def initialize(_value=nil)
4
+ @value_desc = _value.rmx_object_desc
5
+ @holder = NSHashTable.weakObjectsHashTable
6
+ self.value = _value
7
+ self
8
+ end
9
+
10
+ def value
11
+ @holder.anyObject
12
+ end
13
+
14
+ def value=(_value)
15
+ @holder.removeAllObjects
16
+ @holder.addObject(_value) unless _value.nil?
17
+ _value
18
+ end
19
+
20
+ def hash
21
+ value.hash
22
+ end
23
+
24
+ def eql?(other)
25
+ other.hash == value.hash
26
+ end
27
+
28
+ def inspect
29
+ if val = value
30
+ "(weak)#{@value_desc}"
31
+ else
32
+ "(deallocated)#{@value_desc}"
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,39 @@
1
+ class RMXWeakToStrongHash < Hash
2
+
3
+ def [](key)
4
+ super(RMXWeakHolder.new(key))
5
+ end
6
+
7
+ def []=(key, value)
8
+ super(RMXWeakHolder.new(key), value)
9
+ end
10
+
11
+ def delete(key)
12
+ super(RMXWeakHolder.new(key))
13
+ end
14
+
15
+ def keys
16
+ out = []
17
+ keys = [] + super
18
+ while keys.size > 0
19
+ key = keys.shift
20
+ if val = key.value
21
+ out << val
22
+ end
23
+ end
24
+ out
25
+ end
26
+
27
+ def values
28
+ out = []
29
+ keys = [] + self.keys
30
+ while keys.size > 0
31
+ key = keys.shift
32
+ if val = key.value
33
+ out << self[key]
34
+ end
35
+ end
36
+ out
37
+ end
38
+
39
+ end
@@ -0,0 +1,29 @@
1
+ class RMX
2
+
3
+ CREATE_WEAK_HOLDER = proc do
4
+ RMXWeakHolder.new
5
+ end
6
+
7
+ # creates an +attr_accessor+ like behavior, but the object is
8
+ # stored within an NSHashTable.weakObjectsHashTable and retrieved
9
+ # from the NSHashTable on demand.
10
+ # does not conform to KVO like a normal attr_accessor.
11
+ def weak_attr_accessor(*attrs)
12
+ if object = unsafe_unretained_object
13
+ attrs.each do |attr|
14
+ attr_holder = "#{attr}_holder"
15
+ object.send(:define_method, attr) do
16
+ if holder = RMX.new(self).ivar(attr_holder)
17
+ holder.value
18
+ end
19
+ end
20
+ object.send(:define_method, "#{attr}=") do |val|
21
+ holder = RMX.new(self).ivar(attr_holder, &CREATE_WEAK_HOLDER)
22
+ holder.value = val
23
+ val
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,12 @@
1
+ class RMX
2
+
3
+ def initialize(_object)
4
+ @unsafe_unretained_object_holder = RMXUnsafeUnretainedHolder.new(_object)
5
+ self
6
+ end
7
+
8
+ def unsafe_unretained_object
9
+ @unsafe_unretained_object_holder.value
10
+ end
11
+
12
+ end
data/lib/motion/env.rb ADDED
@@ -0,0 +1,9 @@
1
+ class RMX
2
+
3
+ DEBUG_LONGTASK = Env['rmx_debug_longtask'] == '1'
4
+ DEBUG_DEALLOC = Env['rmx_debug_dealloc'] == '1'
5
+ DEBUG_EVENTS = Env['rmx_debug_events'] == '1'
6
+ DEBUG_QUEUES = Env['rmx_debug_queues'] == '1'
7
+ DEBUG = Env['rmx_debug'] == '1'
8
+
9
+ end
@@ -0,0 +1,61 @@
1
+ class RMX
2
+
3
+ CREATE_EVENT_PROXY = proc do
4
+ RMXEventsFromProxy.new
5
+ end
6
+
7
+ def events_from_proxy
8
+ res = nil
9
+ RMXEventsFromProxy::QUEUE.sync do
10
+ res = ivar(:_rmx_events_from_proxy, &CREATE_EVENT_PROXY)
11
+ end
12
+ res
13
+ end
14
+
15
+ def _events_from_proxy
16
+ res = nil
17
+ RMXEventsFromProxy::QUEUE.sync do
18
+ res = ivar(:_rmx_events_from_proxy)
19
+ end
20
+ res
21
+ end
22
+
23
+ # register a callback when an event is triggered on this object.
24
+ def on(event, opts={}, &block)
25
+ if object = unsafe_unretained_object
26
+ _opts = opts.dup
27
+ if _opts[:strong]
28
+ _opts[:strong] = [ object, block.owner ]
29
+ end
30
+ events_from_proxy.on(event, _opts, &block)
31
+ end
32
+ end
33
+
34
+ def now_and_on(event, opts={}, &block)
35
+ events_from_proxy.now_and_on(event, opts.dup, &block)
36
+ end
37
+
38
+ # register a callback when an event is triggered on this object and remove it after it fires once
39
+ def once(event, opts={}, &block)
40
+ _opts = opts.dup
41
+ _opts[:limit] = 1
42
+ on(event, _opts, &block)
43
+ end
44
+
45
+ # RMX(@model).off(:fire, &block) # remove :fire for specific handler
46
+ # RMX(@model).off(:fire) # remove all :fire in all knowns contexts
47
+ # RMX(@model).off # remove all events in all known contexts
48
+ def off(event=nil, execution_block=nil)
49
+ if proxy = _events_from_proxy
50
+ proxy.off(event, execution_block)
51
+ end
52
+ end
53
+
54
+ # trigger an event with value on this object
55
+ def trigger(event, *values)
56
+ if proxy = _events_from_proxy
57
+ proxy.trigger(event, *values)
58
+ end
59
+ end
60
+
61
+ end