motion-kit 0.0.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +839 -0
  3. data/lib/motion-kit-cocoa/cocoa_util.rb +59 -0
  4. data/lib/motion-kit-cocoa/constraints/constraint.rb +765 -0
  5. data/lib/motion-kit-cocoa/constraints/constraint_placeholder.rb +22 -0
  6. data/lib/motion-kit-cocoa/constraints/constraints_layout.rb +536 -0
  7. data/lib/motion-kit-cocoa/constraints/constraints_target.rb +52 -0
  8. data/lib/motion-kit-cocoa/layouts/cagradientlayer_layout.rb +13 -0
  9. data/lib/motion-kit-cocoa/layouts/calayer_layout.rb +27 -0
  10. data/lib/motion-kit-cocoa/layouts/sugarcube_compat.rb +38 -0
  11. data/lib/motion-kit-ios/dummy.rb +93 -0
  12. data/lib/motion-kit-ios/ios_util.rb +20 -0
  13. data/lib/motion-kit-ios/layouts/constraints_layout.rb +22 -0
  14. data/lib/motion-kit-ios/layouts/layout_device.rb +32 -0
  15. data/lib/motion-kit-ios/layouts/layout_orientation.rb +47 -0
  16. data/lib/motion-kit-ios/layouts/uibutton_layout.rb +52 -0
  17. data/lib/motion-kit-ios/layouts/uiview_layout.rb +45 -0
  18. data/lib/motion-kit-ios/layouts/uiview_layout_autoresizing.rb +75 -0
  19. data/lib/motion-kit-ios/layouts/uiview_layout_constraints.rb +36 -0
  20. data/lib/motion-kit-ios/layouts/uiview_layout_frame.rb +307 -0
  21. data/lib/motion-kit-ios/layouts/uiview_layout_gradient.rb +20 -0
  22. data/lib/motion-kit-osx/dummy.rb +89 -0
  23. data/lib/motion-kit-osx/layouts/constraints_layout.rb +42 -0
  24. data/lib/motion-kit-osx/layouts/nsmenu_extensions.rb +186 -0
  25. data/lib/motion-kit-osx/layouts/nsmenu_layout.rb +109 -0
  26. data/lib/motion-kit-osx/layouts/nsmenuitem_extensions.rb +45 -0
  27. data/lib/motion-kit-osx/layouts/nstablecolumn_layout.rb +12 -0
  28. data/lib/motion-kit-osx/layouts/nstableview_layout.rb +20 -0
  29. data/lib/motion-kit-osx/layouts/nsview_layout.rb +47 -0
  30. data/lib/motion-kit-osx/layouts/nsview_layout_autoresizing.rb +75 -0
  31. data/lib/motion-kit-osx/layouts/nsview_layout_constraints.rb +34 -0
  32. data/lib/motion-kit-osx/layouts/nsview_layout_frame.rb +375 -0
  33. data/lib/motion-kit-osx/layouts/nswindow_frame.rb +14 -0
  34. data/lib/motion-kit-osx/layouts/nswindow_layout.rb +77 -0
  35. data/lib/motion-kit-osx/osx_util.rb +16 -0
  36. data/lib/motion-kit.rb +33 -0
  37. data/lib/motion-kit/calculate.rb +263 -0
  38. data/lib/motion-kit/layouts/base_layout.rb +299 -0
  39. data/lib/motion-kit/layouts/base_layout_class_methods.rb +43 -0
  40. data/lib/motion-kit/layouts/parent.rb +68 -0
  41. data/lib/motion-kit/layouts/view_layout.rb +327 -0
  42. data/lib/motion-kit/motion-kit.rb +18 -0
  43. data/lib/motion-kit/object.rb +16 -0
  44. data/lib/motion-kit/util.rb +20 -0
  45. data/lib/motion-kit/version.rb +1 -1
  46. data/spec/ios/apply_styles_spec.rb +21 -0
  47. data/spec/ios/autoresizing_helper_spec.rb +224 -0
  48. data/spec/ios/calculate_spec.rb +322 -0
  49. data/spec/ios/calculator_spec.rb +31 -0
  50. data/spec/ios/constraints_helpers/attribute_lookup_spec.rb +27 -0
  51. data/spec/ios/constraints_helpers/axis_lookup_spec.rb +13 -0
  52. data/spec/ios/constraints_helpers/center_constraints_spec.rb +419 -0
  53. data/spec/ios/constraints_helpers/constraint_placeholder_spec.rb +72 -0
  54. data/spec/ios/constraints_helpers/priority_lookup_spec.rb +19 -0
  55. data/spec/ios/constraints_helpers/relationship_lookup_spec.rb +27 -0
  56. data/spec/ios/constraints_helpers/relative_corners_spec.rb +274 -0
  57. data/spec/ios/constraints_helpers/relative_location_spec.rb +111 -0
  58. data/spec/ios/constraints_helpers/simple_constraints_spec.rb +2763 -0
  59. data/spec/ios/constraints_helpers/size_constraints_spec.rb +422 -0
  60. data/spec/ios/constraints_helpers/view_lookup_constraints_spec.rb +93 -0
  61. data/spec/ios/create_layout_spec.rb +40 -0
  62. data/spec/ios/custom_layout_spec.rb +13 -0
  63. data/spec/ios/deferred_spec.rb +89 -0
  64. data/spec/ios/device_helpers_spec.rb +51 -0
  65. data/spec/ios/frame_helper_spec.rb +1150 -0
  66. data/spec/ios/layer_layout_spec.rb +36 -0
  67. data/spec/ios/layout_extensions_spec.rb +70 -0
  68. data/spec/ios/layout_spec.rb +74 -0
  69. data/spec/ios/layout_state_spec.rb +27 -0
  70. data/spec/ios/motionkit_util_spec.rb +102 -0
  71. data/spec/ios/objc_selectors_spec.rb +10 -0
  72. data/spec/ios/orientation_helper_specs.rb +67 -0
  73. data/spec/ios/parent_layout_spec.rb +19 -0
  74. data/spec/ios/parent_spec.rb +45 -0
  75. data/spec/ios/remove_layout_spec.rb +25 -0
  76. data/spec/ios/root_layout_spec.rb +53 -0
  77. data/spec/ios/setters_spec.rb +63 -0
  78. data/spec/ios/uibutton_layout_spec.rb +24 -0
  79. data/spec/ios/uitextfield_spec.rb +14 -0
  80. data/spec/ios/view_attr_spec.rb +25 -0
  81. data/spec/osx/autoresizing_helper_spec.rb +224 -0
  82. data/spec/osx/constraints_helper_spec.rb +0 -0
  83. data/spec/osx/constraints_helpers/orientation_lookup_spec.rb +13 -0
  84. data/spec/osx/constraints_helpers/simple_constraints_spec.rb +2095 -0
  85. data/spec/osx/constraints_helpers/size_constraints_spec.rb +362 -0
  86. data/spec/osx/create_menu_spec.rb +14 -0
  87. data/spec/osx/create_via_extensions_spec.rb +63 -0
  88. data/spec/osx/deferred_spec.rb +85 -0
  89. data/spec/osx/frame_helper_spec.rb +1881 -0
  90. data/spec/osx/menu_extensions_spec.rb +376 -0
  91. data/spec/osx/menu_layout_spec.rb +157 -0
  92. data/spec/osx/menu_spec.rb +70 -0
  93. data/spec/osx/root_menu_spec.rb +15 -0
  94. metadata +166 -14
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a17092b88b64e247ac1e7b87d1b8b5dcf71ada84
4
+ data.tar.gz: 20f75375fac394bb02b1a4ae35396d5f27124224
5
+ SHA512:
6
+ metadata.gz: 2fa4c416fd99a1b2bb06e58c1235442a8773a0546d0acfc22cd1db4c3e8a06e4d6645b1c8990979f0a3266ada6cc192a89a9d6874d693b0c48171c0dcaad74bf
7
+ data.tar.gz: 7ad4893a25e08b397bc2575a002d7dde744617896185828723e7e7f8e1122da525489c52e0ec11d9497a403de5650f8e1c2cf18c7f230fe7ef4d8182673c73d9
data/README.md ADDED
@@ -0,0 +1,839 @@
1
+ # MotionKit
2
+
3
+ *The RubyMotion layout and styling gem.*
4
+
5
+ 1. Crossplatform compatibility: iOS, OSX
6
+ 2. Simple, easy to learn DSL
7
+ 3. Crossframework compatibility:
8
+ - [UIKit][readmore-uikit]
9
+ - [ApplicationKit][readmore-applicationkit]
10
+ - [AutoLayout][readmore-autolayout]
11
+ - [Frame geometry][readmore-frames]
12
+ - [CoreAnimation][readmore-coreanimation]
13
+ - [NSMenu/NSMenuItem][readmore-nsmenu]
14
+ - [Joybox][readmore-joybox] *TODO*
15
+ - [SpriteKit][readmore-spritekit] *TODO*
16
+ 4. Non-polluting
17
+ 5. ProMotion/RMQ/SugarCube-compatible (kind of goes hand-in-hand with non-polluting)
18
+ 6. Styles and layouts are "just code" (not hash-based like in Teacup)
19
+ 7. Written by [the authors][authors] of [ProMotion][] and [Teacup][]
20
+
21
+ [authors]: CONTRIBUTORS.md
22
+ [Colin]: https://github.com/colinta
23
+ [Jamon]: https://github.com/jamonholmgren
24
+ [ProMotion]: https://github.com/clearsightstudio/ProMotion
25
+ [RMQ]: https://github.com/infinitered/rmq
26
+ [Teacup]: https://github.com/rubymotion/teacup
27
+
28
+ [readmore-uikit]: https://github.com/rubymotion/motion-kit/blob/master/READMORE.md#uikit
29
+ [readmore-applicationkit]: https://github.com/rubymotion/motion-kit/blob/master/READMORE.md#applicationkit
30
+ [readmore-joybox]: https://github.com/rubymotion/motion-kit/blob/master/READMORE.md#joybox
31
+ [readmore-spritekit]: https://github.com/rubymotion/motion-kit/blob/master/READMORE.md#spritekit
32
+ [readmore-coreanimation]: https://github.com/rubymotion/motion-kit/blob/master/READMORE.md#coreanimation
33
+ [readmore-frames]: https://github.com/rubymotion/motion-kit/blob/master/READMORE.md#frames
34
+ [readmore-autolayout]: https://github.com/rubymotion/motion-kit/blob/master/READMORE.md#autolayout
35
+ [readmore-nsmenu]: https://github.com/rubymotion/motion-kit/blob/master/READMORE.md#nsmenu
36
+
37
+
38
+ ## What happened to Teacup??
39
+
40
+ You can [read all about](#goodbye-teacup) why Colin decided that Teacup needed to
41
+ be replaced with a new project, rather than upgraded or refactored.
42
+
43
+
44
+ ## Usage
45
+
46
+ From your controller you will instantiate a `MotionKit::Layout` instance, and
47
+ request views from it. `layout.view` is the root view, and it's common to
48
+ assign this to `self.view` in your `loadView` method. You'll also want to hook
49
+ up your instance variables, using `layout.get(:id)` or using instance variables.
50
+
51
+ ```ruby
52
+ class LoginController < UIViewController
53
+ def loadView
54
+ @layout = LoginLayout.new
55
+ self.view = @layout.view
56
+
57
+ @button = @layout.get(:button) # This will be created in our layout (below)
58
+ @button = @layout.button # Alternatively you can use instance variables and accessor methods
59
+ end
60
+
61
+ def viewDidLoad
62
+ @button.on(:touch) { my_code } # Mix with some SugarCube for sweetness!
63
+ rmq(@button).on(:touch) { my_code } # and of course RMQ works just as well
64
+ end
65
+ end
66
+ ```
67
+
68
+
69
+ ### Lay out your subviews with a clean DSL
70
+
71
+ In a layout class, the `layout` method is expected to create the view hierarchy,
72
+ and it should also take care of frames and styling. You can apply styles here,
73
+ and it's handy to do so when you are creating a quick mock-up, or a very small
74
+ app. But in a real application, you'll want to include a Stylesheet module, so
75
+ your layout isn't cluttered with all your styling code.
76
+
77
+ Here's a layout that just puts a label and a button in the middle of the screen:
78
+
79
+ ```ruby
80
+ class SimpleLayout < MotionKit::Layout
81
+ # this is a special attr method that calls `layout` if the view hasn't been
82
+ # created yet. So you can call `layout.button` before `layout.view` and you
83
+ # won't get nil, and layout.view will be built.
84
+ view :button
85
+
86
+ def layout
87
+ add UILabel, :label
88
+
89
+ @button = add UIButton, :button
90
+ end
91
+
92
+ def label_style
93
+ text 'Hi there! Welcome to MotionKit'
94
+ font UIFont.fontWithName('Comic Sans', size: 24)
95
+ sizeToFit
96
+
97
+ # note: there are better ways to set the center, see the frame helpers below
98
+ center [CGRectGetMidX(superview.bounds), CGRectGetMidY(superview.bounds)]
99
+ text_alignment UITextAlignmentCenter
100
+ text_color UIColor.whiteColor
101
+
102
+ # if you prefer to use shorthands from another gem, you certainly can!
103
+ background_color rmq.color.white # from RMQ
104
+ background_color :white.uicolor # from SugarCube
105
+ end
106
+
107
+ def button_style
108
+ # this will call 'setTitle(forState:)' via a UIButton helper
109
+ title 'Press it!'
110
+ # this shorthand is much better! More about frame helpers below.
111
+ center ['50%', '50% + 50']
112
+ end
113
+
114
+ end
115
+ ```
116
+
117
+ Nice, that should be pretty easy to follow, right? M'kay, in this next, more
118
+ complicated layout we'll create a login page, with a 'Login' button and inputs
119
+ for username and password. In this example, I will assign the frame in the
120
+ `layout` method, instead of in the `_style` methods. This is purely an
121
+ aesthetic choice. Some people like to have their frame code in the layout
122
+ method, others like to put it in the _style methods. I point it out only as an
123
+ available feature.
124
+
125
+ ```ruby
126
+ class LoginLayout < MotionKit::Layout
127
+ # we write our `_style` methods in a module
128
+ include LoginStyles
129
+
130
+ def layout
131
+ # we know it's easy to add a subview, with a stylename...
132
+ add UIImageView, :logo
133
+
134
+ # inside a block you can set properties on that view
135
+ add UIImageView, :logo do
136
+ frame [[0, 0], [320, 568]]
137
+ end
138
+ # hardcoded dimensions!? there's got to be a better way...
139
+
140
+ # This frame argument will be handed to the 'MotionKit::Layout#frame'
141
+ # method, which can accept lots of shorthands. Let's use one to scale the
142
+ # imageview so that it fills the width, but keeps its aspect ratio.
143
+ add UIImageView, :logo do
144
+ frame [[0, 0], ['100%', :scale]]
145
+ end
146
+ # 'scale' uses sizeToFit and the other width/height property to keep the
147
+ # aspect ratio the same. Neat, huh?
148
+
149
+ add UIView, :button_container do
150
+ # Like I said, the frame method is very powerful, there are lots of
151
+ # ways it can help with laying out your rects, and it will even try to
152
+ # apply the correct autoresizingMask for you; the from_bottom method will
153
+ # set the UIAutoresizingMask to "FlexibleTop", and using '100%' in the
154
+ # width will ensure the frame stays the width of its parent.
155
+ frame from_bottom(height: 50, width: '100%')
156
+ frame from_bottom(h: 50, w: '100%') # is fine, too
157
+
158
+ # same as above; assumes full width
159
+ frame from_bottom(height: 50)
160
+
161
+ # similar to Teacup, views added inside a block are added to that
162
+ # container. You can reference the container with 'superview', but then
163
+ # you're working on the object directly, so no method translation (foo_bar
164
+ # => fooBar) will be done for you.
165
+ add UIButton, :login_button do
166
+ background_color superview.backgroundColor
167
+
168
+ # 'parent' is not instance of a view, it's a special object that
169
+ # acts like a placeholder for various values; if you want to assign
170
+ # *any* superview property, use 'superview' instead. 'parent' is mostly
171
+ # useful for setting the frame.
172
+ frame [[ 10, 5 ], [ 50, parent.height - 10 ]]
173
+ end
174
+ end
175
+
176
+ add UIView, :inputs do
177
+ frame x: 0, y: 0, width: '100%', height: '100% - 50'
178
+
179
+ # setting autoresizing_mask should handle rotation events; this overrides
180
+ # any automatic mask settings that occurred in 'frame'
181
+ autoresizing_mask :pin_to_top, :flexible_height, :flexible_width
182
+
183
+ # we'll use 'sizeToFit' to calculate the height
184
+ add UITextField, :username_input do
185
+ frame [[10, 10], ['100% - 10', :auto]]
186
+ end
187
+ add UITextField, :password_input do
188
+ frame below(:username_input, margin: 8)
189
+ end
190
+ end
191
+ end
192
+ end
193
+ ```
194
+
195
+
196
+ ### Styles are compiled, simple, and clean
197
+
198
+ In MotionKit, when you define a method that has the same name as a view
199
+ stylename with the suffix "_style", that method is called and is expected to
200
+ style that view.
201
+
202
+ ```ruby
203
+ class LoginLayout < MK::Layout
204
+
205
+ def layout
206
+ add UIImageView, :logo do
207
+ # this can be moved into `logo_style` below:
208
+ frame [[0, 0], ['100%', :scale]]
209
+ end
210
+ add UIView, :button_container
211
+ end
212
+
213
+ def logo_style
214
+ frame [[0, 0], ['100%', :scale]]
215
+ image UIImage.imageNamed('logo')
216
+ end
217
+
218
+ def button_container_style
219
+ background_color UIColor.clearColor
220
+ end
221
+
222
+ # In case you're curious, the MK::Layout#initialize method takes no arguments.
223
+ # Just be sure to call `super`
224
+ def initialize
225
+ super
226
+ # ...
227
+ end
228
+
229
+ end
230
+ ```
231
+
232
+ So as an additional code-cleanup step, why not put those methods in a module,
233
+ and include them in your layout! Sounds clean and organized to me! You can
234
+ include multiple stylesheets this way, just be careful around name collisions.
235
+
236
+ ```ruby
237
+ module LoginStyles
238
+
239
+ def login_button_style
240
+ background_color '#51A8E7'.uicolor
241
+ title 'Log In'
242
+ # `layer` returns a CALayer, which in turn becomes the new context inside
243
+ # this block!
244
+ layer do
245
+ corner_radius 7.0
246
+ shadow_color '#000000'.cgcolor
247
+ shadow_opacity 0.9
248
+ shadow_radius 2.0
249
+ shadow_offset [0, 0]
250
+ end
251
+ end
252
+
253
+ end
254
+
255
+ # back in our LoginLayout class
256
+ class LoginLayout
257
+ include LoginStyles
258
+
259
+ def layout
260
+ add UIButton, :login_button
261
+ # ...
262
+ end
263
+
264
+ end
265
+ ```
266
+
267
+
268
+ ### How do styles get applied?
269
+
270
+ If you've used RMQ's Stylers, you'll recognize a very similar pattern here. In
271
+ RMQ the 'style' methods are handed a 'Styler' instance, which wraps access to
272
+ the view. In MotionKit we make use of `method_missing` to call these methods
273
+ indirectly. That takes care of most methods related to styling, except those
274
+ that take multiple arguments. Those can get "helper" methods.
275
+
276
+ ```ruby
277
+ def login_label_style
278
+ text 'Press me' # this gets delegated to UILabel#text
279
+ end
280
+
281
+ # It's not hard to add extensions for common tasks, like setting the "normal"
282
+ # title on a UIButton
283
+ def login_button_style
284
+ title 'Press me'
285
+ # this gets delegated to UIButtonLayout#title(title), which in turn calls
286
+ # button.setTitle(title, forState: UIControlStateNormal)
287
+ end
288
+ ```
289
+
290
+ MotionKit offers shortcuts and mini-DSLs for frames, auto-layout, and
291
+ miscellaneous helpers. But if a method is not defined, it is sent to the view
292
+ after a little introspection. If you call a method like `title_color value`, MotionKit
293
+ will try to call:
294
+
295
+ - `setTitle_color(value)`
296
+ - `title_color=(value)`
297
+ - `title_color(value)`
298
+ - (try again, converting to camelCase)
299
+ - `setTitleColor(value)`
300
+ - `titleColor=(value)`
301
+ - `titleColor(value)`
302
+ - (failure:) `raise NoMethodError`
303
+
304
+ ```ruby
305
+ def login_button_style
306
+ background_color UIColor.clearColor # this gets converted to `self.target.backgroundColor = ...`
307
+ end
308
+ ```
309
+
310
+ You can easily add your own helpers to MotionKit's existing Layout classes. They
311
+ are all named consistenly, e.g. `MotionKit::UIViewLayout`, e.g.
312
+ `MotionKit::UILabelLayout`. Just open up these classes and hack away.
313
+
314
+ ```ruby
315
+ module MotionKit
316
+ # these helpers will only be applied to instances of UILabel and UILabel
317
+ # subclasses
318
+ class UILabelLayout
319
+
320
+ # style methods can accept any number of arguments, and a block. The current
321
+ # view should be referred to via the method `target`
322
+ def color(color)
323
+ target.textColor = color
324
+ end
325
+
326
+ # If a block is passed it is your responsibility to call `context(val,
327
+ # &block)`, if that is appropriate. I'll use `UIView#layer` as an example,
328
+ # but actually if you pass a block to a method that returns an object, that
329
+ # block will be called with that object as the context.
330
+ def layer(&block)
331
+ context(target.layer, &block)
332
+ end
333
+
334
+ # Sure, you can add flow-control mechanisms if that's your thing!
335
+ #
336
+ # You can use the block to conditionally call code; on iOS there are
337
+ # orientation helpers `portrait`, `landscape`, etc that apply styles based
338
+ # on the current orientation.
339
+ def sometimes(&block)
340
+ if rand > 0.5
341
+ yield
342
+ end
343
+ end
344
+
345
+ end
346
+ end
347
+ ```
348
+
349
+ For your own custom classes, you can provide a Layout class by calling the
350
+ `targets` method in your class body.
351
+
352
+ ```ruby
353
+ # Be sure to extend an existing Layout class, otherwise you'll lose a lot of
354
+ # functionality. Often this will be `MK::UIViewLayout` on iOS and
355
+ # `MK::NSViewLayout` on OS X.
356
+ class CustomLayout < MK::UIViewLayout
357
+ targets CustomView
358
+
359
+ def fore_color(value)
360
+ target.foregroundColor = value
361
+ end
362
+
363
+ end
364
+ ```
365
+
366
+
367
+ ### Frames
368
+
369
+ There are lots of frame helpers for NSView and UIView subclasses. It's cool
370
+ that you can set position and sizes as percents, but scroll down to see examples
371
+ of setting frames *based on any other view*. These are super useful! Most of
372
+ the ideas, method names, and some code came straight out of
373
+ [geomotion](https://github.com/clayallsopp/geomotion). It's not *quite as
374
+ powerful* as geomotion, but it's close!
375
+
376
+ One advantage over geomotion is that many of these frame helpers accept a view
377
+ or view name, so that you can place the view relative to that view.
378
+
379
+ ```ruby
380
+ # most direct way to set the frame, using pt values
381
+ frame [[0, 0], [320, 568]]
382
+
383
+ # using sizes relative to superview
384
+ frame [[5, 5], ['100% - 10pt', '100% - 10pt']]
385
+ # the 'pt' suffix is optional, and ignored. in the future we could add support
386
+ # for other suffixes - would that even be useful? probably not...
387
+
388
+ # other available methods:
389
+ origin [5, 5]
390
+ x 5 # aka left(..)
391
+ right 5 # right side of the view is 5px from the left side of the superview
392
+ bottom 5 # bottom of the view is 5px from the top of the superview
393
+ size ['100% - 10', '100% - 10']
394
+ width '100% - 10' # aka w(...)
395
+ height '100% - 10' # aka h(...)
396
+
397
+ size ['90%', '90%']
398
+ center ['50%', '50%']
399
+
400
+ ########
401
+ # +--------------------------------------------------+
402
+ # |from_top_left from_top from_top_right|
403
+ # | |
404
+ # |from_left from_center from_right|
405
+ # | |
406
+ # |from_bottom_left from_bottom from_bottom_right|
407
+ # +--------------------------------------------------+
408
+ ```
409
+
410
+ You can position the view *relative to other views*, either the superview or any
411
+ other view. You must pass the return value to `frame`.
412
+
413
+ ```ruby
414
+ # If you don't specify a view to base off of, the view is positioned relative to
415
+ # the superview:
416
+ frame from_bottom_right(size: [100, 100]) # 100x100 in the BR corner
417
+ frame from_bottom(size: ['100%', 32]) # full width, 32pt height
418
+ frame from_top_right(left: 5)
419
+
420
+ # But if you pass a view or symbol as the first arg, the position will be
421
+ # relative to that view
422
+ from_top_right(:info_container, left: 5)
423
+
424
+
425
+ ########
426
+ # above
427
+ # +---+
428
+ # left_of | | right_of
429
+ # (before) | | (after)
430
+ # +---+
431
+ # below
432
+
433
+ # these methods *require* another view.
434
+ frame above(:foo, up: 8)
435
+
436
+ frame above(:foo, up: 8)
437
+ frame before(:foo, left: 8)
438
+ frame relative_to(:foo, down: 5, right: 5)
439
+
440
+ # it's not common, but you can also pass a view to any of these methods
441
+ foo = self.get(:foo)
442
+ frame from_bottom_left(foo, up: 5, left: 5)
443
+ ```
444
+
445
+
446
+ ### Autoresizing mask
447
+
448
+ You can pass symbols like `autoresizing_mask :flexible_width`, or use
449
+ symbols that have more intuitive meaning than the usual
450
+ `UIViewAutoresizingFlexible*` constants. These work in iOS and OS X.
451
+
452
+ ```ruby
453
+ autoresizing_mask :flexible_right, :flexible_bottom, :flexible_width
454
+ autoresizing_mask :pin_to_left, :rigid_top # 'rigid' undoes a 'flexible' setting
455
+ autoresizing_mask :pin_to_bottom, :flexible_width
456
+ autoresizing_mask :fill_top
457
+
458
+ flexible_left: Sticks to the right side
459
+ flexible_width: Width varies with parent
460
+ flexible_right: Sticks to the left side
461
+ flexible_top: Sticks to the bottom
462
+ flexible_height: Height varies with parent
463
+ flexible_bottom: Sticks to the top
464
+
465
+ rigid_left: Left side stays constant (undoes :flexible_left)
466
+ rigid_width: Width stays constant (undoes :flexible_width)
467
+ rigid_right: Right side stays constant (undoes :flexible_right)
468
+ rigid_top: Top stays constant (undoes :flexible_top)
469
+ rigid_height: Height stays constant (undoes :flexible_height)
470
+ rigid_bottom: Bottom stays constant (undoes :flexible_bottom)
471
+
472
+ fill: The size increases with an increase in parent size
473
+ fill_top: Width varies with parent and view sticks to the top
474
+ fill_bottom: Width varies with parent and view sticks to the bottom
475
+ fill_left: Height varies with parent and view sticks to the left
476
+ fill_right: Height varies with parent and view sticks to the right
477
+
478
+ pin_to_top_left: View stays in top-left corner, size does not change.
479
+ pin_to_top: View stays in top-center, size does not change.
480
+ pin_to_top_right: View stays in top-right corner, size does not change.
481
+ pin_to_left: View stays centered on the left, size does not change.
482
+ pin_to_center: View stays centered, size does not change.
483
+ pin_to_right: View stays centered on the right, size does not change.
484
+ pin_to_bottom_left: View stays in bottom-left corner, size does not change.
485
+ pin_to_bottom: View stays in bottom-center, size does not change.
486
+ pin_to_bottom_right: View stays in bottom-left corner, size does not change.
487
+ ```
488
+
489
+
490
+ ### Constraints / Auto Layout
491
+
492
+ Inside a `constraints` block you can use similar helpers as above, but you'll
493
+ be using Cocoa's Auto Layout system instead. This is the recommended way to set
494
+ your frames, now that Apple is introducing multiple display sizes. But beware,
495
+ Auto Layout can be frustrating... :-/
496
+
497
+ One pain point in working with constraints is determining when to add them to
498
+ your views. We tried really hard to figure out a way to automatically add them,
499
+ but it's just an untenable problem (Teacup suffers from a similar conundrum).
500
+
501
+ Essentially, the problem comes down to this: you will often want to set
502
+ constraints that are related to the view controller's `view`, but those must be
503
+ created/set *after* `controller.view = @layout.view`. Without doing some crazy
504
+ method mangling on NS/UIView we just can't do this automatically
505
+
506
+ Long story short: If you need to create constraints that refer to the controller
507
+ view, you need to use a separate method that is called after the view hierarchy
508
+ is created.
509
+
510
+ ```ruby
511
+ class MainLayout < UIViewLayout
512
+
513
+ def layout
514
+ add UILabel, :label do
515
+ constraints do
516
+ x 0
517
+ width('100%')
518
+ end
519
+ end
520
+ end
521
+
522
+ def add_constraints(controller)
523
+ unless @layout_constraints_added
524
+ @layout_constraints_added = true
525
+ constraints(:label) do
526
+ top.equals(controller.topLayoutGuide)
527
+ end
528
+ end
529
+ end
530
+
531
+ end
532
+
533
+ class MainController < UIViewController
534
+
535
+ def loadView
536
+ @layout = MainLayout.new
537
+ self.view = @layout
538
+ end
539
+
540
+ # for the constraints to work reliably they should be added in this method:
541
+ def updateViewConstraints
542
+ @layout.add_constraints(self) # !!!
543
+ super
544
+ end
545
+
546
+ end
547
+ ```
548
+
549
+ OK, with that hack out of the way, on to the examples!
550
+
551
+ ```ruby
552
+ constraints do
553
+ top_left x: 5, y: 10
554
+ # the MotionKit::Constraint class has lots of aliases and "smart" methods,
555
+ # so you can write very literate code:
556
+ top_left.equals([5, 10])
557
+ top_left.is([5, 10])
558
+ top_left.is.equal_to(x: 5, y: 10)
559
+ top_left.is == { x: 5, y: 10 }
560
+ top_left.is >= { x: 5, y: 10 }
561
+ top_left.is <= { x: 5, y: 10 }
562
+
563
+ # this is all the same as setting these two constraints:
564
+ x 5 # aka `left 5`
565
+ y 10 # aka `top 10`
566
+
567
+ # You can have multiple constraints on the same property, and if the
568
+ # priorities are set appropriately you can easily have minimum margins,
569
+ # minimum widths, that kind of thing:
570
+ x.is.at_least(10).priority(:required)
571
+ x.is(15).priority(:low)
572
+ width.is.at_least(100).priority(:required)
573
+ width.is(150).priority(:low)
574
+
575
+ # using the `Constraint#is` method you can even use ==, <= and >=
576
+ x.is >= 10
577
+ x.is == 15
578
+
579
+ # setting the priority:
580
+ (x.is >= 10).priority(:required)
581
+ (x.is == 15).priority(:low)
582
+ end
583
+ ```
584
+
585
+ But of course with AutoLayout you set up *relationships* between views. Using
586
+ the element-id as a placeholder for a view works especially well here.
587
+
588
+ ```ruby
589
+ constraints do
590
+ top_left.equals x: 5, y:5
591
+ width.equals(:foo).minus(10) # searches for a view named :foo
592
+ height.equals(:foo).minus(10)
593
+ # that's repetitive, so just set 'size'
594
+ size.equals(:foo).minus(10)
595
+ size.equals(:foo).minus([10, 15]) # 10pt thinner, 15pt shorter
596
+ end
597
+ ```
598
+
599
+ Just like with frame helpers you can use the `:element_id` to refer to another
600
+ view, but get this: the view need not be created yet! This is because when you
601
+ setup a constraints block, it isn't resolved immediately; the symbols are
602
+ resolved at the end. This feature uses the `deferred` method behind the scenes
603
+ to accomplish this.
604
+
605
+ ```ruby
606
+ add UIView, :foo do
607
+ constraints do
608
+ width.equals(:bar).plus(10) # :bar has not been added yet!
609
+ end
610
+ end
611
+
612
+ add UIView, :bar do
613
+ constraints do
614
+ width.equals(:foo).minus(10)
615
+ width.equals(100).minus(10)
616
+ # believe it or not, this ^ code works! AutoLayout is a strange beast; it's
617
+ # not an "imperative" system, it solves a system of equations. In this
618
+ # case, :bar will have width 110, and :foo will have width 100, because
619
+ # those values solve these equations:
620
+ # foo.width = 100
621
+ # foo.width = bar.width - 10
622
+ # foo.width = bar.width + 10
623
+ # If you have constraints that conflict you'll get error messages or
624
+ # nonsensical values.
625
+
626
+ # There are helpers that act as placeholders for views, if you have multiple
627
+ # views with the same name:
628
+ # first, last, nth
629
+ width.equals(last(:foo))
630
+ width.equals(first(:foo))
631
+ width.equals(nth(:foo, 5))
632
+ end
633
+ end
634
+ ```
635
+
636
+ ### Frames
637
+
638
+ There are lots of frame helpers for NSView and UIView subclasses:
639
+
640
+ ```ruby
641
+ # most direct
642
+ frame [[0, 0], [320, 568]]
643
+ # using relative sizes (relative to superview)
644
+ frame [[5, 5], ['100% - 10', '100% - 10']]
645
+
646
+ # same, but using separate methods
647
+ origin [5, 5]
648
+ x 5
649
+ y 5
650
+ size ['100% - 10', '100% - 10']
651
+ width '100% - 10'
652
+ height '100% - 10'
653
+
654
+ size ['90%', '90%']
655
+ center ['50%', '50%']
656
+
657
+ # you can position the view *relative to other views*, either the superview or
658
+ # *any* other view.
659
+ from_bottom_right size: [100, 100] # 100x100pt in the BR corner
660
+ from_bottom size: ['100%', 32] # full width, 32pt height
661
+ from_top_right left: 5
662
+
663
+ # from_top_left from_top from_top_right
664
+ # from_left from_center from_right
665
+ # from_bottom_left from_bottom from_bottom_right
666
+
667
+ # these require another view
668
+ foo = self.get(:foo)
669
+ above foo, up: 8
670
+ # above
671
+ # before after
672
+ # left_of right_of
673
+ # below
674
+ relative_to foo, down: 5, right: 5
675
+ from_bottom_left foo, up: 5, left: 5
676
+ ```
677
+
678
+ ### Constraints / AutoLayout
679
+
680
+ Inside a `constraints` block you can use the same helpers as above, but you'll
681
+ be using AutoLayout instead!
682
+
683
+ ```ruby
684
+ constraints do
685
+ from_top_left x: 5, y: 5
686
+ end
687
+ ```
688
+
689
+ But of course with constraints you can setup *relationships* between views.
690
+
691
+ ```ruby
692
+ foo = self.get(:foo)
693
+ constraints do
694
+ from_top_left x: 5, y:5
695
+ from_top_left down: 5, right:5
696
+ width.equals(foo).minus(10)
697
+ height.equals(foo).minus(10)
698
+ # that's repetitive, so just set 'size'
699
+ size.equals(foo).minus(10)
700
+ size.equals(foo).minus([10, 15]) # 10pt thinner, 15pt shorter
701
+ end
702
+ ```
703
+
704
+
705
+ ### Some handy tricks
706
+
707
+ #### Orientation specific styles
708
+
709
+ These are available on iOS.
710
+
711
+ ```ruby
712
+ add UIView, :container do
713
+ portrait do
714
+ frame from_top(width: '100%', height: 100)
715
+ end
716
+ landscape do
717
+ frame from_top_left(width: 300, height: 100)
718
+ end
719
+ end
720
+ ```
721
+
722
+ #### Update views via 'initial', 'reapply', and 'deferred'
723
+
724
+ If you call 'layout.reapply!', your style methods will be called again (but
725
+ NOT the `layout` method). You very well might want to control what methods get
726
+ called on later invocations, or only on the *initial* layout.
727
+
728
+ This is more for being able to initialize values, or to handle orientation, than
729
+ anything else. There is not much performance increase/decrease if you just
730
+ reapply styles every time, but you might not want to have your frame or colors
731
+ reset, if you've done some animation.
732
+
733
+ ```ruby
734
+ def login_button_style
735
+ # only once, when the `layout` is created
736
+ initial do
737
+ end
738
+
739
+ # on later invocations
740
+ reapply do
741
+ end
742
+
743
+ # style every time
744
+ title 'Press Me'
745
+ end
746
+ ```
747
+
748
+ Or, you might need to set a frame or other property based on a view that hasn't
749
+ been created yet. In this case, you can use `deferred` to have a block of code
750
+ run after the current layout is completed.
751
+
752
+ ```ruby
753
+ def login_button_style
754
+ deferred do
755
+ frame below(last(:label), height: 20)
756
+ end
757
+ end
758
+ ```
759
+
760
+ #### Apply styles via module
761
+
762
+ ```ruby
763
+ module AppStyles
764
+
765
+ def rounded_button
766
+ layer do
767
+ corner_radius 7
768
+ masks_to_bounds true
769
+ end
770
+ end
771
+
772
+ end
773
+
774
+ class LoginLayout < MotionKit::Layout
775
+ include AppStyles
776
+
777
+ def layout
778
+ add button, :login_button
779
+ end
780
+
781
+ def login_button_style
782
+ self.rounded_button
783
+ title 'Login'
784
+ end
785
+
786
+ end
787
+ ```
788
+
789
+ # Contributing
790
+
791
+ We welcome your contributions! Please be sure to run the specs before you do,
792
+ and consider adding support for both iOS and OS X.
793
+
794
+ To run the specs for both platforms, you will need to run `rake spec` twice:
795
+
796
+ ```
797
+ > rake spec # runs iOS specs
798
+ > rake spec platform=osx # OS X specs
799
+ ```
800
+
801
+
802
+ # Goodbye Teacup
803
+ ###### by [colinta][Colin]
804
+
805
+ If you've worked with XIB/NIB files, you might know that while they can be
806
+ cumbersome to deal with, they have the great benefit of keeping your controllers
807
+ free of layout and styling concerns. [Teacup][] brought some of this benefit, in
808
+ the form of stylesheets, but you still built the layout in the body of your
809
+ controller file. This needed to be fixed.
810
+
811
+ Plus Teacup is a beast! Imported stylesheets, orientation change events,
812
+ auto-layout support. It's got a ton of features, but with that comes a lot of
813
+ complexity. This has led to an unfortunate situation - I'm the *only* person who
814
+ understands the code base! This was never the intention of Teacup. It started
815
+ out as, and was always meant to be, a community project, with contributions
816
+ coming from all of its users.
817
+
818
+ When [ProMotion][] and later [RMQ][] were released, they both included their own
819
+ styling mechanisms. Including Teacup as a dependency would have placed a huge
820
+ burden on their users, and they would have had to ensure compatibility. Since
821
+ Teacup does a lot of method swizzling on base classes, this is not a trivial
822
+ undertaking.
823
+
824
+ If you use RMQ or ProMotion already, you'll find that MotionKit fits right in.
825
+ We designed it to be something that can easily be brought into an existing
826
+ project, too; it does not extend any base classes, so it's completely opt-in.
827
+
828
+ Unlike Teacup, you won't have your styles reapplied due to orientation changes,
829
+ but it's *really* easy to set that up, as you'll see. Or, use AutoLayout (the
830
+ DSL is better than Teacup's, I think) and you'll get orientation changes for
831
+ free!
832
+
833
+ Big thanks to everyone who contributed on this project! I hope it serves you
834
+ as well as Teacup, and for even longer into the future.
835
+
836
+ Sincerely,
837
+
838
+ Colin T.A. Gray
839
+ Feb 13, 2014