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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +18 -0
- data/app/app_delegate.rb +5 -0
- data/lib/motion/RMXActionSheet.rb +14 -0
- data/lib/motion/RMXAutoLayoutLabel.rb +19 -0
- data/lib/motion/RMXAutoLayoutScrollView.rb +53 -0
- data/lib/motion/RMXCommonMethods.rb +27 -0
- data/lib/motion/RMXEventsFromProxy.rb +103 -0
- data/lib/motion/RMXExecutionBlock.rb +51 -0
- data/lib/motion/RMXKeyboardHelpers.rb +54 -0
- data/lib/motion/RMXLongTask.rb +133 -0
- data/lib/motion/RMXNavigationController.rb +133 -0
- data/lib/motion/RMXObjectExtensions.rb +10 -0
- data/lib/motion/RMXSegmentedController.rb +71 -0
- data/lib/motion/RMXSetAttributes.rb +34 -0
- data/lib/motion/RMXStrongToWeakHash.rb +46 -0
- data/lib/motion/RMXSynchronizedStrongToWeakHash.rb +40 -0
- data/lib/motion/RMXTableHandler.rb +212 -0
- data/lib/motion/RMXTableViewCell.rb +85 -0
- data/lib/motion/RMXTableViewCellInnerContentView.rb +16 -0
- data/lib/motion/RMXTableViewController.rb +65 -0
- data/lib/motion/RMXUnsafeUnretainedHolder.rb +30 -0
- data/lib/motion/RMXView.rb +84 -0
- data/lib/motion/RMXViewController.rb +65 -0
- data/lib/motion/RMXViewControllerPresentation.rb +127 -0
- data/lib/motion/RMXWeakHolder.rb +36 -0
- data/lib/motion/RMXWeakToStrongHash.rb +39 -0
- data/lib/motion/accessors.rb +29 -0
- data/lib/motion/base.rb +12 -0
- data/lib/motion/env.rb +9 -0
- data/lib/motion/events.rb +61 -0
- data/lib/motion/layout.rb +357 -0
- data/lib/motion/ui.rb +55 -0
- data/lib/motion/util.rb +155 -0
- data/lib/rmx/version.rb +3 -0
- data/lib/rmx.rb +50 -0
- data/resources/Default-568h@2x.png +0 -0
- data/rmx.gemspec +19 -0
- data/spec/main_spec.rb +240 -0
- 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
|
data/lib/motion/base.rb
ADDED
data/lib/motion/env.rb
ADDED
@@ -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
|