motion-layouts 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE +26 -0
- data/README.md +153 -0
- data/Rakefile +2 -0
- data/lib/.gitignore +0 -0
- data/lib/base.rb +48 -0
- data/lib/errors.rb +11 -0
- data/lib/layout_base.rb +110 -0
- data/lib/layouts/categories/buttons.rb +56 -0
- data/lib/layouts/categories/colors.rb +7 -0
- data/lib/layouts/categories/controllers.rb +37 -0
- data/lib/layouts/categories/core.rb +39 -0
- data/lib/layouts/categories/forms.rb +46 -0
- data/lib/layouts/categories/table_views.rb +53 -0
- data/lib/layouts/categories/tool_bars.rb +11 -0
- data/lib/layouts/categories/views.rb +91 -0
- data/lib/layouts/nodes/ui_text_field.rb +34 -0
- data/lib/layouts/nodes/ui_tool_bar.rb +27 -0
- data/lib/motion-layouts.rb +14 -0
- data/lib/motion-layouts/version.rb +5 -0
- data/motion-layouts.gemspec +17 -0
- data/spec/.gitignore +0 -0
- metadata +79 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
LICENCE
|
2
|
+
|
3
|
+
MIT: http://robmalko.mit-license.org
|
4
|
+
|
5
|
+
------------------------------------------------
|
6
|
+
|
7
|
+
The MIT License (MIT)
|
8
|
+
Copyright © 2012 Rob Malko <rob.malko@gmail.com>
|
9
|
+
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
11
|
+
of this software and associated documentation files (the “Software”), to deal
|
12
|
+
in the Software without restriction, including without limitation the rights
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
15
|
+
furnished to do so, subject to the following conditions:
|
16
|
+
|
17
|
+
The above copyright notice and this permission notice shall be included in
|
18
|
+
all copies or substantial portions of the Software.
|
19
|
+
|
20
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
26
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# Layouts for RubyMotion
|
2
|
+
|
3
|
+
A DSL for creating layouts easily in RubyMotion. Also comes bundled with a
|
4
|
+
set of categories to make life easier. I'm using the word category from
|
5
|
+
objective-c land which is basically the same as re-opening classes in ruby :D.
|
6
|
+
|
7
|
+
## Getting started
|
8
|
+
|
9
|
+
Add motion-layouts as a git submodule of your RubyMotion project:
|
10
|
+
|
11
|
+
git submodule add https://github.com/malkomalko/motion-layouts.git vendor/motion-layouts
|
12
|
+
|
13
|
+
Add the motion-layouts lib path to your project 'Rakefile'
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
Motion::Project::App.setup do |app|
|
17
|
+
app.name = 'myapp'
|
18
|
+
app.files.unshift(Dir.glob(File.join(app.project_dir, 'vendor/motion-layouts/lib/**/*.rb')))
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
Now, you can use motion-layouts to start making some layouts.
|
23
|
+
|
24
|
+
I put all my layouts by convention into app/layouts but feel free to do
|
25
|
+
whatever you want.
|
26
|
+
|
27
|
+
## Define a layout
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class NameEditorLayout
|
31
|
+
include Layouts::Base
|
32
|
+
|
33
|
+
def self.template
|
34
|
+
UIToolbar {
|
35
|
+
anchor 'top'
|
36
|
+
height 50
|
37
|
+
resize :top, :right, :left, :width
|
38
|
+
items [
|
39
|
+
['Cancel', 'cancel'],
|
40
|
+
[:flexible_space],
|
41
|
+
['Done', 'done']
|
42
|
+
]
|
43
|
+
}
|
44
|
+
UITextField {
|
45
|
+
id 'nameTextField'
|
46
|
+
delegate @controller
|
47
|
+
top 90
|
48
|
+
width 85.percent
|
49
|
+
align 'center'
|
50
|
+
text_color '222222'
|
51
|
+
background_color 'FFFFFF'
|
52
|
+
border_style 'rounded'
|
53
|
+
resize :top, :right, :left, :width
|
54
|
+
placeholder 'Enter the photo album name'
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
You start by including Layouts::Base and defining a self.template method.
|
61
|
+
|
62
|
+
## Instantiate your view (from controller)
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
def viewWillAppear(animated)
|
66
|
+
super
|
67
|
+
view.fromLayout(NameEditorLayout, self)
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
boom.. that's it, you should see a toolbar and a text field in your view.
|
72
|
+
|
73
|
+
## How it works
|
74
|
+
|
75
|
+
The project includes a mixture of categories and nodes.
|
76
|
+
|
77
|
+
Nodes are the entry point inside self.template in your layout:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class NameEditorLayout
|
81
|
+
include Layouts::Base
|
82
|
+
|
83
|
+
def self.template
|
84
|
+
UIToolbar {
|
85
|
+
...
|
86
|
+
}
|
87
|
+
UITextField {
|
88
|
+
...
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
Every node inherits from LayoutBase which sets up a lot of shared functionality
|
95
|
+
and handles proper instantiation.
|
96
|
+
|
97
|
+
You have access to a few instance variables inside each node:
|
98
|
+
|
99
|
+
```
|
100
|
+
@parent - the parent view
|
101
|
+
@view - the current view
|
102
|
+
@controller - the controller who instantiated the view via view.fromLayout
|
103
|
+
```
|
104
|
+
|
105
|
+
Every node can also set a defaults hash.
|
106
|
+
|
107
|
+
Let's take a look at the UITextField node:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
module Layouts
|
111
|
+
class UITextField < LayoutBase
|
112
|
+
def self.defaults
|
113
|
+
{
|
114
|
+
width: @parent.bounds.size.width * 0.90,
|
115
|
+
height: 30
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def border_style(style)
|
120
|
+
@view.borderStyle = ::UITextField::BORDER_STYLES.fetchWithDefault(style)
|
121
|
+
end
|
122
|
+
|
123
|
+
def placeholder(text)
|
124
|
+
@view.placeholder = text
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
This is where the categories come in. To make defining these nodes as easy
|
131
|
+
as possible, I'm creating a collection of categories to make the process
|
132
|
+
as smooth as can be.
|
133
|
+
|
134
|
+
Take a look inside the lib/layouts/categories folder to see some of the
|
135
|
+
helpers I've defined for you.
|
136
|
+
|
137
|
+
## Todo
|
138
|
+
|
139
|
+
Tests Tests Tests. This was mostly thrown together very quickly as a POC,
|
140
|
+
but there is nothing complex going on here.
|
141
|
+
|
142
|
+
Filling out a complete set of nodes. I'm throwing this out now in hopes that
|
143
|
+
people can create wrapper nodes for all the missing standard UI classes.
|
144
|
+
|
145
|
+
## Thanks
|
146
|
+
|
147
|
+
Quick thanks to https://github.com/mattetti/BubbleWrap for letting me gut
|
148
|
+
their README.md and for suggesting a rather nice convention for installing
|
149
|
+
custom libs into the vendor directory until something else better comes
|
150
|
+
along.
|
151
|
+
|
152
|
+
Also, thanks to Laurent and the whole RubyMotion community for making iOS
|
153
|
+
programming fun to learn.
|
data/Rakefile
ADDED
data/lib/.gitignore
ADDED
File without changes
|
data/lib/base.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Layouts
|
2
|
+
module Base
|
3
|
+
def initialize(view)
|
4
|
+
@view = view
|
5
|
+
end
|
6
|
+
|
7
|
+
def view
|
8
|
+
@view
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def layout(view, controller)
|
17
|
+
@instance = self.new(view)
|
18
|
+
@controller = controller
|
19
|
+
self.template
|
20
|
+
@views
|
21
|
+
end
|
22
|
+
|
23
|
+
def controller
|
24
|
+
@controller
|
25
|
+
end
|
26
|
+
|
27
|
+
def instance
|
28
|
+
@instance
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(klass, *args, &block)
|
32
|
+
build_layout_for(klass, *args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_layout_for(klass, *args, &block)
|
36
|
+
begin
|
37
|
+
layout = Layouts.const_get(klass)
|
38
|
+
@views ||= []
|
39
|
+
current_view = layout.alloc(self, klass, controller)
|
40
|
+
@views.push(current_view)
|
41
|
+
rescue NameError
|
42
|
+
return
|
43
|
+
end
|
44
|
+
layout.new(self, current_view, controller, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/errors.rb
ADDED
data/lib/layout_base.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Layouts
|
2
|
+
class LayoutBase
|
3
|
+
def self.method_missing(klass, *args, &block);end
|
4
|
+
|
5
|
+
def self.alloc(template, klass, controller)
|
6
|
+
klass = Module.const_get(klass)
|
7
|
+
@parent = template.instance.view
|
8
|
+
@controller = controller
|
9
|
+
_defaults = base_defaults.merge(defaults)
|
10
|
+
origin = [0, 0]
|
11
|
+
size = [_defaults[:width], _defaults[:height]]
|
12
|
+
|
13
|
+
klass.alloc.initWithFrame [origin, size]
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.base_defaults
|
17
|
+
{
|
18
|
+
width: @parent.bounds.size.width,
|
19
|
+
height: 50
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.defaults
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(template, view, controller, &block)
|
28
|
+
@parent = template.instance.view
|
29
|
+
@view = view
|
30
|
+
@controller = controller
|
31
|
+
@args = self.class.base_defaults
|
32
|
+
instance_eval &block
|
33
|
+
end
|
34
|
+
|
35
|
+
def id(id)
|
36
|
+
@controller.class.send(:attr_accessor, id)
|
37
|
+
@controller.instance_variable_set "@#{id}", @view
|
38
|
+
end
|
39
|
+
|
40
|
+
def height(height=50)
|
41
|
+
height = height.is_a?(Float) ?
|
42
|
+
@parent.bounds.size.height * height : height
|
43
|
+
@args[:height] = height
|
44
|
+
@view.frame = update_dimensions h: height
|
45
|
+
anchor('bottom') if @args[:anchor] == 'bottom'
|
46
|
+
end
|
47
|
+
|
48
|
+
def width(width=50)
|
49
|
+
width = width.is_a?(Float) ?
|
50
|
+
@parent.bounds.size.width * width : width
|
51
|
+
@args[:width] = width
|
52
|
+
@view.frame = update_dimensions w: width
|
53
|
+
end
|
54
|
+
|
55
|
+
def left(left=0)
|
56
|
+
left = left.is_a?(Float) ?
|
57
|
+
@parent.bounds.size.width * left : left
|
58
|
+
@args[:left] = left
|
59
|
+
@view.frame = update_dimensions x: left
|
60
|
+
end
|
61
|
+
|
62
|
+
def top(top=0)
|
63
|
+
top = top.is_a?(Float) ?
|
64
|
+
@parent.bounds.size.height * top : top
|
65
|
+
@args[:top] = top
|
66
|
+
@view.frame = update_dimensions y: top
|
67
|
+
end
|
68
|
+
|
69
|
+
def align(align='left')
|
70
|
+
width_left = @parent.bounds.size.width - @view.frame.size.width
|
71
|
+
|
72
|
+
case align
|
73
|
+
when 'left'
|
74
|
+
@view.frame = update_dimensions x: 0
|
75
|
+
when 'center'
|
76
|
+
@view.frame = update_dimensions x: width_left / 2
|
77
|
+
when 'right'
|
78
|
+
@view.frame = update_dimensions x: width_left
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def background_color(color='FFFFFF')
|
83
|
+
@view.backgroundColor = Color.from_hex(color)
|
84
|
+
end
|
85
|
+
|
86
|
+
def text_color(color='FFFFFF')
|
87
|
+
if @view.respond_to?(:textColor)
|
88
|
+
@view.textColor = Color.from_hex(color)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def delegate(instance)
|
93
|
+
if @view.respond_to?(:delegate) && !instance.nil?
|
94
|
+
@view.delegate = instance
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def resize(*masks)
|
99
|
+
@view.resizeMask(*masks)
|
100
|
+
end
|
101
|
+
|
102
|
+
def update_dimensions(opts={})
|
103
|
+
x = opts.fetch(:x, @view.frame.origin.x)
|
104
|
+
y = opts.fetch(:y, @view.frame.origin.y)
|
105
|
+
w = opts.fetch(:w, @view.frame.size.width)
|
106
|
+
h = opts.fetch(:h, @view.frame.size.height)
|
107
|
+
[[x, y], [w, h]]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class UIBarButtonItem
|
2
|
+
BUTTON_TYPES = {
|
3
|
+
default: UIBarButtonSystemItemDone,
|
4
|
+
done: UIBarButtonSystemItemDone,
|
5
|
+
cancel: UIBarButtonSystemItemCancel,
|
6
|
+
edit: UIBarButtonSystemItemEdit,
|
7
|
+
save: UIBarButtonSystemItemSave,
|
8
|
+
add: UIBarButtonSystemItemAdd,
|
9
|
+
flexible_space: UIBarButtonSystemItemFlexibleSpace,
|
10
|
+
fixed_space: UIBarButtonSystemItemFixedSpace,
|
11
|
+
compose: UIBarButtonSystemItemCompose,
|
12
|
+
reply: UIBarButtonSystemItemReply,
|
13
|
+
action: UIBarButtonSystemItemAction,
|
14
|
+
organize: UIBarButtonSystemItemOrganize,
|
15
|
+
bookmarks: UIBarButtonSystemItemBookmarks,
|
16
|
+
search: UIBarButtonSystemItemSearch,
|
17
|
+
refresh: UIBarButtonSystemItemRefresh,
|
18
|
+
stop: UIBarButtonSystemItemStop,
|
19
|
+
camera: UIBarButtonSystemItemCamera,
|
20
|
+
trash: UIBarButtonSystemItemTrash,
|
21
|
+
play: UIBarButtonSystemItemPlay,
|
22
|
+
pause: UIBarButtonSystemItemPause,
|
23
|
+
rewind: UIBarButtonSystemItemRewind,
|
24
|
+
fast_forward: UIBarButtonSystemItemFastForward,
|
25
|
+
undo: UIBarButtonSystemItemUndo,
|
26
|
+
redo: UIBarButtonSystemItemRedo,
|
27
|
+
page_curl: UIBarButtonSystemItemPageCurl
|
28
|
+
}
|
29
|
+
|
30
|
+
BUTTON_STYLES = {
|
31
|
+
default: UIBarButtonItemStyleBordered,
|
32
|
+
border: UIBarButtonItemStyleBordered,
|
33
|
+
plain: UIBarButtonItemStylePlain,
|
34
|
+
done: UIBarButtonItemStyleDone
|
35
|
+
}
|
36
|
+
|
37
|
+
def self.createTitle(title, target, action, style=:default)
|
38
|
+
style = BUTTON_STYLES.fetchWithDefault(style)
|
39
|
+
self.alloc.initWithTitle(title, style:style, target:target, action:action)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.createType(button, target, action)
|
43
|
+
button = BUTTON_TYPES.fetchWithDefault(button)
|
44
|
+
self.alloc.initWithBarButtonSystemItem(button, target:target, action:action)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.fixedSpace(width=20)
|
48
|
+
space = self.createType(:fixed_space, nil, nil)
|
49
|
+
space.width = width
|
50
|
+
space
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.flexibleSpace
|
54
|
+
self.createType(:flexible_space, nil, nil)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class UIViewController
|
2
|
+
PRESENTATION_STYLES = {
|
3
|
+
default: UIModalPresentationFormSheet,
|
4
|
+
full: UIModalPresentationFullScreen,
|
5
|
+
page: UIModalPresentationPageSheet,
|
6
|
+
form: UIModalPresentationFormSheet,
|
7
|
+
current: UIModalPresentationCurrentContext
|
8
|
+
}
|
9
|
+
|
10
|
+
TRANSITION_STYLES = {
|
11
|
+
default: UIModalTransitionStyleCoverVertical,
|
12
|
+
vertical: UIModalTransitionStyleCoverVertical,
|
13
|
+
flip: UIModalTransitionStyleFlipHorizontal,
|
14
|
+
dissolve: UIModalTransitionStyleCrossDissolve,
|
15
|
+
curl: UIModalTransitionStylePartialCurl
|
16
|
+
}
|
17
|
+
|
18
|
+
def createModal(opts={})
|
19
|
+
raise ExpectingHashParameter unless opts.is_a?(Hash)
|
20
|
+
klass = opts.fetch(:view, nil)
|
21
|
+
raise MissingViewException if klass.nil?
|
22
|
+
|
23
|
+
presentation = PRESENTATION_STYLES.fetchWithDefault(opts[:style])
|
24
|
+
transition = TRANSITION_STYLES.fetchWithDefault(opts[:transition])
|
25
|
+
|
26
|
+
modal = klass.alloc.init
|
27
|
+
modal.delegate = self
|
28
|
+
modal.modalPresentationStyle = presentation
|
29
|
+
modal.modalTransitionStyle = transition
|
30
|
+
|
31
|
+
def modal.show
|
32
|
+
self.delegate.presentModalViewController(self, animated:true)
|
33
|
+
end
|
34
|
+
|
35
|
+
modal
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Kernel
|
2
|
+
def delegateTo(selector)
|
3
|
+
if self.respond_to?(:delegate) && !self.delegate.nil?
|
4
|
+
self.delegate.send(selector) if self.delegate.respond_to?(selector)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Hash
|
10
|
+
def fetchWithDefault(key)
|
11
|
+
self.fetch(key, self[:default])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Numeric
|
16
|
+
def percent
|
17
|
+
self.to_f / 100.0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class NSOrderedSet
|
22
|
+
def push(val)
|
23
|
+
self.addObject(val)
|
24
|
+
end
|
25
|
+
|
26
|
+
def size
|
27
|
+
self.count
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](index)
|
31
|
+
self.objectAtIndex(index)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class NSMutableOrderedSet
|
36
|
+
def delete_at(index)
|
37
|
+
self.removeObjectAtIndex(index)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class UITextField
|
2
|
+
BORDER_STYLES = {
|
3
|
+
default: UITextBorderStyleRoundedRect,
|
4
|
+
none: UITextBorderStyleNone,
|
5
|
+
line: UITextBorderStyleLine,
|
6
|
+
bezel: UITextBorderStyleBezel,
|
7
|
+
rounded: UITextBorderStyleRoundedRect
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
class UITextInputTraits
|
12
|
+
KEYBOARD_TYPES = {
|
13
|
+
default: UIKeyboardTypeDefault,
|
14
|
+
ascii: UIKeyboardTypeASCIICapable,
|
15
|
+
numbers: UIKeyboardTypeNumbersAndPunctuation,
|
16
|
+
url: UIKeyboardTypeURL,
|
17
|
+
number_pad: UIKeyboardTypeNumberPad,
|
18
|
+
phone_pad: UIKeyboardTypePhonePad,
|
19
|
+
name_phone_pad: UIKeyboardTypeNamePhonePad,
|
20
|
+
email: UIKeyboardTypeEmailAddress,
|
21
|
+
decimal_pad: UIKeyboardTypeDecimalPad,
|
22
|
+
twitter: UIKeyboardTypeTwitter
|
23
|
+
}
|
24
|
+
|
25
|
+
RETURN_KEY_TYPES = {
|
26
|
+
default: UIReturnKeyDefault,
|
27
|
+
go: UIReturnKeyGo,
|
28
|
+
google: UIReturnKeyGoogle,
|
29
|
+
join: UIReturnKeyJoin,
|
30
|
+
next: UIReturnKeyNext,
|
31
|
+
route: UIReturnKeyRoute,
|
32
|
+
search: UIReturnKeySearch,
|
33
|
+
send: UIReturnKeySend,
|
34
|
+
yahoo: UIReturnKeyYahoo,
|
35
|
+
done: UIReturnKeyDone,
|
36
|
+
emergency: UIReturnKeyEmergencyCall
|
37
|
+
}
|
38
|
+
|
39
|
+
CAPITALIZATION_STYLES = {
|
40
|
+
default: UITextAutocapitalizationTypeNone,
|
41
|
+
none: UITextAutocapitalizationTypeNone,
|
42
|
+
words: UITextAutocapitalizationTypeWords,
|
43
|
+
sentences: UITextAutocapitalizationTypeSentences,
|
44
|
+
all: UITextAutocapitalizationTypeAllCharacters
|
45
|
+
}
|
46
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class UITableView
|
2
|
+
def deleteRows(rows)
|
3
|
+
self.deleteRowsAtIndexPaths(rows,
|
4
|
+
withRowAnimation:UITableViewRowAnimationFade)
|
5
|
+
end
|
6
|
+
|
7
|
+
def selectRow(row)
|
8
|
+
self.selectRowAtIndexPath(
|
9
|
+
NSIndexPath.indexPathForRow(row, inSection:row),
|
10
|
+
animated:false,
|
11
|
+
scrollPosition:UITableViewScrollPositionMiddle)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class UITableViewCell
|
16
|
+
CID = 'CELL'
|
17
|
+
|
18
|
+
STYLES = {
|
19
|
+
default: UITableViewCellStyleDefault,
|
20
|
+
subtitle: UITableViewCellStyleSubtitle,
|
21
|
+
value1: UITableViewCellStyleValue1,
|
22
|
+
value2: UITableViewCellStyleValue2
|
23
|
+
}
|
24
|
+
|
25
|
+
ACCESSORIES = {
|
26
|
+
default: UITableViewCellAccessoryNone,
|
27
|
+
disclosure: UITableViewCellAccessoryDisclosureIndicator,
|
28
|
+
detail: UITableViewCellAccessoryDetailDisclosureButton,
|
29
|
+
checkmark: UITableViewCellAccessoryCheckmark
|
30
|
+
}
|
31
|
+
|
32
|
+
SELECT_STYLES = {
|
33
|
+
default: UITableViewCellSelectionStyleBlue,
|
34
|
+
blue: UITableViewCellSelectionStyleBlue,
|
35
|
+
gray: UITableViewCellSelectionStyleGray,
|
36
|
+
none: UITableViewCellSelectionStyleNone
|
37
|
+
}
|
38
|
+
|
39
|
+
def self.display(tableView, opts={})
|
40
|
+
style = STYLES.fetchWithDefault(opts[:style])
|
41
|
+
accessoryType = ACCESSORIES.fetchWithDefault(opts[:accessory])
|
42
|
+
editAccessory = ACCESSORIES.fetchWithDefault(opts[:edit_accessory])
|
43
|
+
selectionStyle = SELECT_STYLES.fetchWithDefault(opts[:selection])
|
44
|
+
|
45
|
+
tableView.dequeueReusableCellWithIdentifier(CID) || begin
|
46
|
+
cell = self.alloc.initWithStyle(style, reuseIdentifier:CID)
|
47
|
+
cell.accessoryType = accessoryType
|
48
|
+
cell.editingAccessoryType = editAccessory
|
49
|
+
cell.selectionStyle = selectionStyle
|
50
|
+
cell
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class UIToolbar
|
2
|
+
def self.createAtTop(view, height=50)
|
3
|
+
self.alloc.initWithFrame([[0,0], [view.bounds.size.width,height]])
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.createAtBottom(view, height=50)
|
7
|
+
size = view.bounds.size
|
8
|
+
bottom = size.height - height
|
9
|
+
self.alloc.initWithFrame([[0,bottom], [size.width,height]])
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class UIView
|
2
|
+
RESIZE_MASKS = {
|
3
|
+
none: UIViewAutoresizingNone,
|
4
|
+
top: UIViewAutoresizingFlexibleBottomMargin,
|
5
|
+
right: UIViewAutoresizingFlexibleLeftMargin,
|
6
|
+
bottom: UIViewAutoresizingFlexibleTopMargin,
|
7
|
+
left: UIViewAutoresizingFlexibleRightMargin,
|
8
|
+
width: UIViewAutoresizingFlexibleWidth,
|
9
|
+
height: UIViewAutoresizingFlexibleHeight
|
10
|
+
}
|
11
|
+
|
12
|
+
def resizeMask(*masks)
|
13
|
+
self.autoresizingMask = RESIZE_MASKS[:none] and return self if masks.include?(:none)
|
14
|
+
self.autoresizingMask = masks.reduce(0) {|memo,mask| memo | RESIZE_MASKS.fetch(mask, 0)}
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def resizeAll
|
19
|
+
self.resizeMask :top, :right, :bottom, :left, :width, :height
|
20
|
+
end
|
21
|
+
|
22
|
+
def fromLayout(klass, controller)
|
23
|
+
_views_ = klass.layout(self, controller)
|
24
|
+
_views_.each {|_view_| self.addSubview _view_}
|
25
|
+
end
|
26
|
+
|
27
|
+
def initWithParent(parent)
|
28
|
+
@parent = parent
|
29
|
+
self.initWithFrame [[0,0],[0,0]]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module LayoutHelper
|
34
|
+
def height(height=50)
|
35
|
+
height = height.is_a?(Float) ?
|
36
|
+
@parent.bounds.size.height * height : height
|
37
|
+
self.frame = update_dimensions h: height
|
38
|
+
end
|
39
|
+
|
40
|
+
def width(width=50)
|
41
|
+
width = width.is_a?(Float) ?
|
42
|
+
@parent.bounds.size.width * width : width
|
43
|
+
self.frame = update_dimensions w: width
|
44
|
+
end
|
45
|
+
|
46
|
+
def left(left=0)
|
47
|
+
left = left.is_a?(Float) ?
|
48
|
+
@parent.bounds.size.width * left : left
|
49
|
+
self.frame = update_dimensions x: left
|
50
|
+
end
|
51
|
+
|
52
|
+
def top(top=0)
|
53
|
+
top = top.is_a?(Float) ?
|
54
|
+
@parent.bounds.size.height * top : top
|
55
|
+
self.frame = update_dimensions y: top
|
56
|
+
end
|
57
|
+
|
58
|
+
def align(align='left')
|
59
|
+
width_left = @parent.bounds.size.width - self.frame.size.width
|
60
|
+
|
61
|
+
case align
|
62
|
+
when 'left'
|
63
|
+
self.frame = self.update_dimensions x: 0
|
64
|
+
when 'center'
|
65
|
+
self.frame = self.update_dimensions x: width_left / 2
|
66
|
+
when 'right'
|
67
|
+
self.frame = self.update_dimensions x: width_left
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def verticalAlign(align='top')
|
72
|
+
height_left = @parent.bounds.size.height - self.frame.size.height
|
73
|
+
|
74
|
+
case align
|
75
|
+
when 'top'
|
76
|
+
self.frame = self.update_dimensions y: 0
|
77
|
+
when 'center'
|
78
|
+
self.frame = self.update_dimensions y: height_left / 2
|
79
|
+
when 'bottom'
|
80
|
+
self.frame = self.update_dimensions y: height_left
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def update_dimensions(opts={})
|
85
|
+
x = opts.fetch(:x, self.frame.origin.x)
|
86
|
+
y = opts.fetch(:y, self.frame.origin.y)
|
87
|
+
w = opts.fetch(:w, self.frame.size.width)
|
88
|
+
h = opts.fetch(:h, self.frame.size.height)
|
89
|
+
[[x, y], [w, h]]
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Layouts
|
2
|
+
class UITextField < LayoutBase
|
3
|
+
def self.defaults
|
4
|
+
{
|
5
|
+
width: @parent.bounds.size.width * 0.90,
|
6
|
+
height: 30
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
def border_style(style)
|
11
|
+
@view.borderStyle = ::UITextField::BORDER_STYLES.fetchWithDefault(style)
|
12
|
+
end
|
13
|
+
|
14
|
+
def placeholder(text)
|
15
|
+
@view.placeholder = text
|
16
|
+
end
|
17
|
+
|
18
|
+
def keyboard_type(type)
|
19
|
+
@view.keyboardType = ::UITextInputTraits::KEYBOARD_TYPES.fetchWithDefault(type)
|
20
|
+
end
|
21
|
+
|
22
|
+
def return_key_type(type)
|
23
|
+
@view.returnKeyType = ::UITextInputTraits::RETURN_KEY_TYPES.fetchWithDefault(type)
|
24
|
+
end
|
25
|
+
|
26
|
+
def secure(bool)
|
27
|
+
@view.setSecureTextEntry bool
|
28
|
+
end
|
29
|
+
|
30
|
+
def capitalize(style)
|
31
|
+
@view.autocapitalizationType = ::UITextInputTraits::CAPITALIZATION_STYLES.fetchWithDefault(style)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Layouts
|
2
|
+
class UIToolbar < LayoutBase
|
3
|
+
def self.defaults
|
4
|
+
{
|
5
|
+
height: 50
|
6
|
+
}
|
7
|
+
end
|
8
|
+
|
9
|
+
def anchor(type)
|
10
|
+
type ||= 'top'
|
11
|
+
@args[:anchor] = type
|
12
|
+
bottom = @parent.bounds.size.height - @args[:height]
|
13
|
+
@view.frame = update_dimensions y: type == 'bottom' ? bottom : 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def items(toolbar_items)
|
17
|
+
items = toolbar_items.map do |item|
|
18
|
+
if item[0].is_a?(String)
|
19
|
+
::UIBarButtonItem.createTitle(item[0], @controller, item[1])
|
20
|
+
elsif item[0].is_a?(Symbol)
|
21
|
+
::UIBarButtonItem.createType(item[0], @controller, item[1])
|
22
|
+
end
|
23
|
+
end.compact
|
24
|
+
@view.items = items
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise "#{__FILE__} must be required within a RubyMotion project Rakefile."
|
3
|
+
end
|
4
|
+
|
5
|
+
Motion::Project::App.setup do |app|
|
6
|
+
root = File.expand_path('../..', __FILE__)
|
7
|
+
files = Dir.glob(File.join(root, 'lib/**/*.rb')).reject{ |f| f == __FILE__ }
|
8
|
+
files.each do |file|
|
9
|
+
app.files.unshift(file)
|
10
|
+
end
|
11
|
+
|
12
|
+
app.files_dependencies File.join(root, 'lib/layouts/nodes/ui_text_field.rb') => File.join(root, 'lib/layout_base.rb')
|
13
|
+
app.files_dependencies File.join(root, 'lib/layouts/nodes/ui_tool_bar.rb') => File.join(root, 'lib/layout_base.rb')
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/motion-layouts/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["malkomalko (Robert Malko)"]
|
6
|
+
gem.email = ["robmalko@gmail.com"]
|
7
|
+
gem.description = %q{A DSL for creating layouts easily in RubyMotion. Also comes bundled with a set of categories to make life easier. I'm using the word category from objective-c land which is basically the same as re-opening classes in ruby :D.}
|
8
|
+
gem.summary = %q{A DSL for creating layouts easily in RubyMotion}
|
9
|
+
gem.homepage = "https://github.com/malkomalko/motion-layouts"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "motion-layouts"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Motion::Layouts::VERSION
|
17
|
+
end
|
data/spec/.gitignore
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: motion-layouts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- malkomalko (Robert Malko)
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-09-17 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: A DSL for creating layouts easily in RubyMotion. Also comes bundled with a set of categories to make life easier. I'm using the word category from objective-c land which is basically the same as re-opening classes in ruby :D.
|
18
|
+
email:
|
19
|
+
- robmalko@gmail.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- .gitignore
|
28
|
+
- Gemfile
|
29
|
+
- LICENSE
|
30
|
+
- README.md
|
31
|
+
- Rakefile
|
32
|
+
- lib/.gitignore
|
33
|
+
- lib/base.rb
|
34
|
+
- lib/errors.rb
|
35
|
+
- lib/layout_base.rb
|
36
|
+
- lib/layouts/categories/buttons.rb
|
37
|
+
- lib/layouts/categories/colors.rb
|
38
|
+
- lib/layouts/categories/controllers.rb
|
39
|
+
- lib/layouts/categories/core.rb
|
40
|
+
- lib/layouts/categories/forms.rb
|
41
|
+
- lib/layouts/categories/table_views.rb
|
42
|
+
- lib/layouts/categories/tool_bars.rb
|
43
|
+
- lib/layouts/categories/views.rb
|
44
|
+
- lib/layouts/nodes/ui_text_field.rb
|
45
|
+
- lib/layouts/nodes/ui_tool_bar.rb
|
46
|
+
- lib/motion-layouts.rb
|
47
|
+
- lib/motion-layouts/version.rb
|
48
|
+
- motion-layouts.gemspec
|
49
|
+
- spec/.gitignore
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: https://github.com/malkomalko/motion-layouts
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.6.2
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: A DSL for creating layouts easily in RubyMotion
|
78
|
+
test_files:
|
79
|
+
- spec/.gitignore
|