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
@@ -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