teacup 1.3.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. data/Gemfile +1 -1
  2. data/Gemfile.lock +3 -3
  3. data/README.md +1172 -319
  4. data/Rakefile +8 -1
  5. data/app/app_delegate.rb +1 -1
  6. data/app/controllers/appearance_controller.rb +13 -0
  7. data/app/controllers/landscape_only_controller.rb +1 -1
  8. data/app/controllers/{first_controller.rb → main_controller.rb} +4 -3
  9. data/app/controllers/motion_layout_controller.rb +22 -0
  10. data/app/styles/appearance.rb +24 -0
  11. data/app/styles/main_styles.rb +8 -6
  12. data/app/views/custom_view.rb +1 -0
  13. data/lib/teacup/calculations.rb +2 -2
  14. data/lib/teacup/{z_core_extensions → core_extensions}/ca_layer.rb +0 -0
  15. data/lib/teacup/core_extensions/view_getters.rb +61 -0
  16. data/lib/teacup/handler.rb +14 -14
  17. data/lib/teacup/layout.rb +94 -17
  18. data/lib/teacup/stylesheet.rb +61 -26
  19. data/lib/teacup/stylesheet_extensions/transform.rb +88 -0
  20. data/lib/teacup/teacup_controller.rb +122 -0
  21. data/lib/teacup/teacup_util.rb +12 -7
  22. data/lib/teacup/teacup_view.rb +329 -0
  23. data/lib/teacup/version.rb +1 -1
  24. data/lib/teacup-ios/appearance.rb +96 -0
  25. data/lib/teacup-ios/core_extensions/teacup_handlers.rb +183 -0
  26. data/lib/teacup-ios/core_extensions/ui_view.rb +30 -0
  27. data/lib/teacup-ios/core_extensions/ui_view_controller.rb +110 -0
  28. data/lib/{dummy.rb → teacup-ios/dummy.rb} +2 -6
  29. data/lib/teacup-ios/handler.rb +23 -0
  30. data/lib/{teacup → teacup-ios}/style.rb +9 -10
  31. data/lib/teacup-ios/stylesheet_extensions/autoresize.rb +169 -0
  32. data/lib/{teacup/stylesheet_extensions/geometry.rb → teacup-ios/stylesheet_extensions/device.rb} +0 -0
  33. data/lib/teacup-osx/core_extensions/ns_view.rb +39 -0
  34. data/lib/teacup-osx/core_extensions/ns_view_controller.rb +21 -0
  35. data/lib/teacup-osx/core_extensions/ns_window.rb +39 -0
  36. data/lib/teacup-osx/core_extensions/ns_window_controller.rb +29 -0
  37. data/lib/{teacup/z_core_extensions/z_handlers.rb → teacup-osx/core_extensions/teacup_handlers.rb} +30 -47
  38. data/lib/teacup-osx/dummy.rb +80 -0
  39. data/lib/teacup-osx/handler.rb +16 -0
  40. data/lib/teacup-osx/style.rb +83 -0
  41. data/lib/teacup-osx/style_extensions/autoresize.rb +169 -0
  42. data/lib/teacup.rb +12 -11
  43. data/samples/Tweets/Gemfile +4 -0
  44. data/samples/Tweets/Gemfile.lock +16 -0
  45. data/samples/Tweets/README +7 -0
  46. data/samples/Tweets/Rakefile +9 -0
  47. data/samples/Tweets/app/app_delegate.rb +18 -0
  48. data/samples/Tweets/app/data_parser.rb +10 -0
  49. data/samples/Tweets/app/json_parser.rb +12 -0
  50. data/samples/Tweets/app/main_window.rb +99 -0
  51. data/samples/Tweets/app/menu.rb +108 -0
  52. data/samples/Tweets/app/stylesheet.rb +21 -0
  53. data/samples/Tweets/app/tweet.rb +11 -0
  54. data/samples/Tweets/resources/Credits.rtf +29 -0
  55. data/samples/Tweets/spec/main_spec.rb +9 -0
  56. data/samples/teacup-osx/.gitignore +1 -0
  57. data/samples/teacup-osx/Gemfile +4 -0
  58. data/samples/teacup-osx/Gemfile.lock +16 -0
  59. data/samples/teacup-osx/Rakefile +9 -0
  60. data/samples/teacup-osx/app/app_delegate.rb +23 -0
  61. data/samples/teacup-osx/app/controller.rb +11 -0
  62. data/samples/teacup-osx/app/menu.rb +108 -0
  63. data/samples/teacup-osx/app/window.rb +12 -0
  64. data/samples/teacup-osx/resources/Credits.rtf +29 -0
  65. data/samples/teacup-osx/resources/teacup.png +0 -0
  66. data/samples/teacup-osx/spec/main_spec.rb +9 -0
  67. data/spec/ios/appearance_spec.rb +18 -0
  68. data/spec/{calculations_spec.rb → ios/calculations_spec.rb} +0 -0
  69. data/spec/{constraints_spec.rb → ios/constraints_spec.rb} +0 -0
  70. data/spec/{custom_class_spec.rb → ios/custom_class_spec.rb} +0 -0
  71. data/spec/{gradient_spec.rb → ios/gradient_spec.rb} +1 -1
  72. data/spec/ios/layout_module_spec.rb +54 -0
  73. data/spec/ios/layout_spec.rb +50 -0
  74. data/spec/{main_spec.rb → ios/main_spec.rb} +52 -13
  75. data/spec/ios/motion_layout_spec.rb +44 -0
  76. data/spec/{present_modal_spec.rb → ios/present_modal_spec.rb} +0 -0
  77. data/spec/{style_spec.rb → ios/style_spec.rb} +1 -1
  78. data/spec/ios/stylesheet_extensions/autoresize_spec.rb +50 -0
  79. data/spec/{stylesheet_spec.rb → ios/stylesheet_spec.rb} +12 -0
  80. data/spec/{ui_view_getters_spec.rb → ios/ui_view_getters_spec.rb} +0 -0
  81. data/spec/{uiswitch_spec.rb → ios/uiswitch_spec.rb} +0 -0
  82. data/spec/{view_spec.rb → ios/view_spec.rb} +23 -2
  83. metadata +85 -35
  84. data/lib/teacup/stylesheet_extensions/autoresize.rb +0 -39
  85. data/lib/teacup/stylesheet_extensions/rotation.rb +0 -37
  86. data/lib/teacup/z_core_extensions/ui_view.rb +0 -262
  87. data/lib/teacup/z_core_extensions/ui_view_controller.rb +0 -263
  88. data/lib/teacup/z_core_extensions/ui_view_getters.rb +0 -58
data/README.md CHANGED
@@ -1,19 +1,23 @@
1
1
  Teacup
2
2
  ======
3
3
 
4
- A community-driven DSL for creating user interfaces on the iphone.
4
+ A community-driven DSL for creating user interfaces on iOS.
5
5
 
6
6
  [![Build Status](https://travis-ci.org/rubymotion/teacup.png)](https://travis-ci.org/rubymotion/teacup)
7
7
 
8
- Using teacup, you can easily create and style layouts while keeping your code
9
- dry. The goal is to offer a rubyesque (well, actually a rubymotion-esque) way
10
- to create interfaces programmatically.
8
+ Using Teacup, you can create and style layouts and keeping your code dry. The
9
+ goal is to offer a rubyesque (well, actually a rubymotion-esque) way to create
10
+ interfaces programmatically.
11
11
 
12
- **Check out a working sample app [here][Hai]!**
12
+ **Check out some sample apps!**
13
13
 
14
- [Hai]: https://github.com/rubymotion/teacup/tree/master/samples/Hai
14
+ * “[Hai][Hai]”
15
+ * “[AutoLayout][AutoLayout]”
16
+ * “[OnePage][OnePage]”
15
17
 
16
- #### Installation
18
+ [Hai]: https://github.com/rubymotion/teacup/tree/master/samples/Hai
19
+ [AutoLayout]: https://github.com/rubymotion/teacup/tree/master/samples/AutoLayout
20
+ [OnePage]: https://github.com/rubymotion/teacup/tree/master/samples/OnePage
17
21
 
18
22
  **Quick Install**
19
23
 
@@ -25,16 +29,6 @@ and in your Rakefile
25
29
  require 'teacup'
26
30
  ```
27
31
 
28
- **Better Install**
29
-
30
- However, it is recommended that you use [Bundler][] and [rvm][] to manage your
31
- gems on a per-project basis, using a gemset. See the
32
- [Installation wiki page][Installation] for more help on doing that.
33
-
34
- [Bundler]: http://gembundler.com/
35
- [rvm]: https://rvm.io/
36
- [Installation]: https://github.com/rubymotion/teacup/wiki/Installation
37
-
38
32
  #### 10 second primer
39
33
 
40
34
  1. Create a `UIViewController` subclass:
@@ -55,441 +49,1302 @@ gems on a per-project basis, using a gemset. See the
55
49
  stylesheet :main_screen
56
50
 
57
51
  layout do
58
- subview(UIButton, :finished_button)
52
+ subview(UIButton, :hi_button)
59
53
  end
60
54
  end
61
55
  ```
62
- 4. Create the stylesheet (Usually in app/styles/)
56
+ 4. Create the stylesheet (in `app/styles/` or somewhere near the controller)
63
57
 
64
58
  ```ruby
65
59
  Teacup::Stylesheet.new :main_screen do
66
- style :finished_button,
60
+ style :hi_button,
67
61
  origin: [10, 10],
68
62
  title: 'Hi!'
69
63
  end
70
64
  ```
71
65
 
72
- Teacup implements the `viewDidLoad` method and instantiates any views you
73
- declare in the `layout` block. Make sure to call `super` if you implement
74
- `viewDidLoad`, or you can use the "teacup-esque" `layoutDidLoad` method.
75
66
 
76
- #### Styling Without Implicitly Adding the View
77
- In the situation where you'd like to utilize the styles of Teacup without having your view
78
- automatically added in `viewDidLoad`. You can accomplish this by calling the `layout` method instead of the block.
67
+ Teacup
68
+ ------
69
+
70
+ Teacup's goal is to facilitate the creation and styling of your `UIViews`
71
+ hierarchy. Say "Goodbye!" to Xcode & XIB files!
72
+
73
+ Teacup is composed of two systems:
74
+
75
+ - Layouts
76
+ A DSL to create `UIViews` and to organize them in a hierarchy. You assign the
77
+ style name and style classes from these methods.
78
+
79
+ - Stylesheets
80
+ Store the "styles" that get applied to your views. The stylesheet DSL is
81
+ meant to resemble CSS, but is targeted at iOS, and so the precedence rules are
82
+ very different.
83
+
84
+ Teacup supports [Pixate][] and [NUI][], too, so you can use those systems for
85
+ styling and Teacup to manage your view hierarchy and apply auto-layout
86
+ constraints. Teacup can also integrate with the [motion-layout][] gem!
87
+
88
+ ### Table of Contents
89
+
90
+ * [Layouts](#layouts)
91
+ * [Stylesheets](#stylesheets)
92
+ * [Using and re-using styles in a Stylesheet](#using-and-re-using-styles-in-a-stylesheet)
93
+ * [Style via Stylename](#style-via-stylename)
94
+ * [Extending Styles](#extending-styles)
95
+ * [Style via UIView Class](#style-via-uiview-class)
96
+ * [Importing stylesheets](#importing-stylesheets)
97
+ * [Style via UIAppearance](#style-via-uiappearance)
98
+ * [More Teacup features](#more-teacup-features)
99
+ * [Styling View Properties](#styling-view-properties)
100
+ * [Orientation Styles](#orientation-styles)
101
+ * [UIView animation additions](#uiview-animation-additions)
102
+ * [Style Handlers](#style-handlers)
103
+ * [Frame Calculations](#frame-calculations)
104
+ * [Auto-Layout](#auto-layout)
105
+ * [Motion-Layout](#motion-layout)
106
+ * [Stylesheet extensions](#stylesheet-extensions)
107
+ * [Autoresizing Masks](#autoresizing-masks)
108
+ * [Device detection](#device-detection)
109
+ * [Rotation helpers](#rotation-helpers)
110
+ * [Showdown](#showdown)
111
+ * [The Nitty Gritty](#the-nitty-gritty)
112
+ * [Advanced Teacup Tricks](#advanced-teacup-tricks)
113
+ * [UITableViewCell](#uitableviewcell)
114
+ * [Sweettea](#sweettea)
115
+ * [Misc notes](#misc-notes)
116
+ * [The Dummy](#the-dummy)
117
+
118
+ Layouts
119
+ -------
120
+
121
+ The `Teacup::Layout` module is mixed into `UIViewController` and `UIView` so
122
+ that these two classes can take advantage of the view-hierarchy DSL.
123
+
124
+ You saw an example in the primer, using the `UIViewController` class method
125
+ `layout`. This is a helper function that stores the layout code. A more direct
126
+ example might look like this:
79
127
 
80
128
  ```ruby
81
- # Custom Navigation Title still styled by Teacup
82
- @custom_label = layout(UILabel, :custom_title)
83
- self.navigationItem.titleView = @custom_label
129
+ # controller example
130
+ class MyController < UIViewController
131
+
132
+ def viewDidLoad
133
+ # we will modify the controller's `view`, assigning it the stylename `:root`
134
+ layout(self.view, :root) do
135
+ # these subviews will be added to `self.view`
136
+ subview(UIToolbar, :toolbar)
137
+ subview(UIButton, :hi_button)
138
+ end
139
+ end
140
+
141
+ end
84
142
  ```
85
143
 
86
- Additionally, you can use the `style` method on a view to apply your styling inline (e.g. `@label.style(styles)`).
144
+ You can use very similar code in your `UIView` subclasses.
87
145
 
88
- #### Showdown
146
+ ```ruby
147
+ # view example
148
+ #
149
+ # if you use teacup in all your projects, you can bundle your custom views with
150
+ # their own stylesheets
151
+ def MyView < UIView
152
+
153
+ def initWithFrame(frame)
154
+ super.tap do
155
+ self.stylesheet = :my_stylesheet
156
+ subview(UIImageView, :image)
157
+ end
158
+ end
159
+
160
+ end
161
+ ```
89
162
 
90
- Cocoa
163
+ The `layout` and `subview` methods are the work horses of the Teacup view DSL.
164
+
165
+ * `layout(uiview|UIViewClass, stylename, style_classes, additional_styles, &block)`
166
+ - `uiview|UIViewClass` - You can layout an existing class or you can have
167
+ teacup create it for you (it just calls `new` on the class, nothing
168
+ special). This argument is required.
169
+ - `stylename` (`Symbol`) - This is the name of a style in your stylesheet. It
170
+ is optional
171
+ - `style_classes` (`[Symbol,...]`) - Other stylenames, they have lower
172
+ priority than the `stylename`.
173
+ - `additional_styles` (`Hash`) - You can pass other styles in here as well,
174
+ either to override or augment the settings from the `Stylesheet`. It is
175
+ common to use this feature to assign the `delegate` or `dataSource`.
176
+ - `&block` - See discussion below
177
+ - Returns the `uiview` that was created or passed to `layout`.
178
+ - only the `uiview` arg is required. You can pass any combination of
179
+ stylename, style_classes, and additional_styles (some, none, or all).
180
+ * `subview(uiview|UIViewClass, stylename, style_classes, additional_styles, &block)`
181
+ - Identical to `layout`, but adds the view to the current target
182
+
183
+ The reason it is so easy to define view hierarchies in Teacup is because the
184
+ `layout` and `subview` methods can be "nested" by passing a block.
91
185
 
92
186
  ```ruby
93
- class SomeController < UIViewController
187
+ subview(UIView, :container) do # create a UIView instance and give it the stylename :container
188
+ subview(UIView, :inputs) do # create another container
189
+ @email_input = subview(UITextField, :email_input)
190
+ @password_input = subview(UITextField, :password_input)
191
+ end
192
+ subview(UIButton.buttonWithType(UIButtonTypeRoundedRect), :submit_button)
193
+ end
194
+ ```
94
195
 
95
- def viewDidLoad
196
+ These methods are defined in the `Layout` module. And guess what!? It's easy
197
+ to add your *own view helpers*! I refer to this as a "partials" system, but
198
+ really it's just Ruby code (and isn't that the best system?).
96
199
 
97
- @field = UITextField.new
98
- @field.frame = [[10, 10], [200, 50]]
99
- @field.textColor = UIColor.redColor
100
- view.addSubview(@field)
200
+ ```ruby
201
+ # the methods you add here will be available in UIView, UIViewController, and
202
+ # any of your own classes that `include Teacup::Layout`
203
+ module Teacup::Layout
101
204
 
102
- @search = UITextField.new
103
- @search.frame = [[10, 70], [200, 50]]
104
- @search.placeholder = 'Find something...'
105
- @search.textColor = UIColor.redColor
106
- view.addSubview(@search)
205
+ # creates a button and assigns a default stylename
206
+ def button(*args, &block)
207
+ # apply a default stylename
208
+ args = [:button] if args.empty?
107
209
 
108
- true
210
+ # instantiate a button and give it a style class
211
+ subview(UIButton.buttonWithType(UIButtonTypeCustom), *args, &block)
109
212
  end
110
213
 
111
- # code to enable orientation changes
112
- def shouldAutorotateToInterfaceOrientation(orientation)
113
- if orientation == UIInterfaceOrientationPortraitUpsideDown
114
- return false
115
- end
116
- true
214
+ # creates a button with an icon image and label
215
+ def button_with_icon(icon, title)
216
+ label = UILabel.new
217
+ label.text = title
218
+ label.sizeToFit
219
+
220
+ image_view = UIImageView.new
221
+ image_view.image = icon
222
+ image_view.sizeToFit
223
+
224
+ button = UIButton.buttonWithType(UIButtonTypeCustom)
225
+ button.addSubview(image_view)
226
+ button.addSubview(label)
227
+
228
+ # code could go here to position the icon and label, or at could be handled
229
+ # by the stylesheet
230
+
231
+ subview(button)
117
232
  end
118
233
 
119
- # perform the frame changes depending on orientation
120
- def willAnimateRotationToInterfaceOrientation(orientation, duration:duration)
121
- case orientation
122
- when UIInterfaceOrientationLandscapeLeft, UIInterfaceOrientationLandscapeRight
123
- @field.frame = [[10, 10], [360, 50]]
124
- @search.frame = [[10, 70], [360, 50]]
125
- else
126
- @field.frame = [[10, 10], [200, 50]]
127
- @search.frame = [[10, 70], [200, 50]]
128
- end
234
+ end
235
+ ```
236
+ ###### example use of the helper methods
237
+ ```
238
+ class MyController < UIViewController
239
+
240
+ layout do
241
+ @button1 = button()
242
+ @button2 = button(:blue_button)
243
+ @button3 = button_with_icon(UIImage.imageNamed('email_icon'), 'Email')
129
244
  end
130
245
 
131
246
  end
132
247
  ```
133
248
 
134
- Teacup
249
+ The `UIViewController##layout` method that has been used so far is going to be
250
+ the first or second thing you add to a controller when you are building an app
251
+ with Teacup. It's method signature is
135
252
 
136
253
  ```ruby
137
- # Stylesheet
254
+ UIViewController.layout(stylename=nil, styles={}, &block)
255
+ ```
138
256
 
139
- Teacup::Stylesheet.new(:some_view) do
257
+ * `stylename` is the stylename you want applied to your controller's `self.view`
258
+ object.
259
+ * `styles` are *rarely* applied here, but one common use case is when you assign
260
+ a custom view in `loadView`, and you want to apply settings to it. I find it
261
+ cleaner to move this code into the body of the `layout` block, though.
262
+ * `&block` is the most important - it is the layout code that will be called
263
+ during `viewDidLoad`.
264
+
265
+ After the views have been added and styles have been applied Teacup calls the
266
+ `layoutDidLoad` method. If you need to perform some additional initialization
267
+ on your views, you should do it in this method. If you use the `layout` block
268
+ the styles have not yet been applied. Frames will not be set, text and titles
269
+ will be empty, and images will not have images. This all happens at the *end*
270
+ of the `layout` block.
140
271
 
141
- style :root,
142
- landscape: true # enable landscape rotation (otherwise only portrait is enabled)
143
- # this must be on the root-view, to indicate that this view is
144
- # capable of handling rotations
272
+ Stylesheets
273
+ -----------
145
274
 
146
- style :field,
147
- left: 10,
148
- top: 10,
149
- width: 200,
150
- height: 50,
151
- landscape: {
152
- width: 360 # make it wide in landscape view
153
- }
275
+ This is where you will store your styling-related code. Migrating code from your
276
+ controller or custom view into a stylesheet is very straightforward. The method
277
+ names map 1::1.
154
278
 
155
- style :search, extends: :field,
156
- left: 10,
157
- top: 70,
158
- placeholder: 'Find something...'
279
+ ```ruby
280
+ # classic Cocoa/UIKit
281
+ def viewDidLoad
282
+ self.view.backgroundColor = UIColor.grayColor
283
+ # ^.............^
284
+ end
159
285
 
160
- style UITextField, # Defining styles based on view class instead
161
- textColor: UIColor.redColor # of style name.
286
+ # in Teacup
287
+ def viewDidLoad
288
+ self.stylesheet = :main
289
+ self.view.stylename = :root
290
+ end
162
291
 
292
+ Teacup::Stylesheet.new :main do
293
+ style :root,
294
+ backgroundColor: UIColor.grayColor
295
+ # ^.............^
163
296
  end
297
+ ```
164
298
 
165
- # Controller
299
+ Nice! We turned three lines of code into nine! Well, obviously the benefits
300
+ come in when we have *lots* of style code, and when you need to do app-wide
301
+ styling.
166
302
 
167
- class SomeController < UIViewController
303
+ You can store stylesheets in any file. It is common to use `app/styles.rb` or
304
+ `app/styles/main.rb`, if you have more than a few of 'em. The
305
+ `Teacup::Stylesheet` constructor accepts a stylesheet name and a block, which
306
+ will contain your style declarations.
168
307
 
169
- # the stylesheet determines the placement and design of your views. You can
170
- # also implement a stylesheet method, or assign the stylesheet name to the
171
- # UIViewController later.
172
- stylesheet :some_view
308
+ ```ruby
309
+ Teacup::Stylesheet.new :main_menu do
310
+ style :ready_to_play_button,
311
+ backgroundColor: UIColor.blackColor,
312
+ frame: [[20, 300], [50, 20]]
313
+ end
173
314
 
174
- # think of this as a nib file that you are declaring in your UIViewController.
175
- # it is styled according to the :root styles, and can add and style subviews
176
- layout :root do
177
- subview(UITextField, :field)
178
- @search = subview(UITextField, :search)
179
- end
315
+ Teacup::Stylesheet[:main_menu] # returns this stylesheet
316
+ ```
317
+
318
+ Any method that accepts a single value can be assigned in a stylesheet. Please
319
+ don't abuse this by hiding application logic in your stylesheets - these are
320
+ meant for *design*, not behavior.
321
+
322
+ ### Using and re-using styles in a Stylesheet
180
323
 
181
- # you have to enable the auto-rotation stuff by implementing a
182
- # shouldAutorotateToInterfaceOrientation method
183
- def shouldAutorotateToInterfaceOrientation(orientation)
184
- # but don't worry, we made that painless, too!
185
- autorotateToOrientation(orientation)
324
+ - Styles are be applied via stylename (`style :label`) or class (`style UILabel`)
325
+ - Styles can extend other styles (`style :big_button, extends: :button`)
326
+ - A stylesheet can import other stylesheets (`import :app`)
327
+ - The special Appearance stylesheet can be used to apply styles to `UIAppearance`
328
+ (`Teacup::Appearance.new`)
329
+
330
+ Let's look at each in turn.
331
+
332
+ ### Style via Stylename
333
+
334
+ This is the most common way to apply a style.
335
+
336
+ ```ruby
337
+ class MainController < UIViewController
338
+
339
+ stylesheet :main # <= assigns the stylesheet named :main to this controller
340
+
341
+ layout do
342
+ subview(UILabel, :h1) # <= :h1 is the stylename
186
343
  end
187
344
 
345
+ end
346
+
347
+ Teacup::Stylesheet.new :main do # <= stylesheet name
348
+
349
+ style :h1, # <= style name
350
+ font: UIFont.systemFontOfSize(20) # <= and this style is applied
351
+
188
352
  end
189
353
  ```
190
354
 
191
- The orientation styling is really neat. I think you'll find that you will be
192
- more inspired to enable multiple orientations because the code is so much more
193
- painless.
355
+ When the stylesheet is applied (at the end of the `layout` block, when all the
356
+ views have been added), its `font` property will be assigned the value
357
+ `UIFont.systemFontOfSize(20)`.
194
358
 
195
- Stylesheets
196
- -----------
359
+ But we didn't assign any text!
197
360
 
198
- The basic format for a `style` is a name and a dictionary of "styles". These
199
- are usually just methods that get called on the target (a `UIView` or `CALayer`,
200
- most likely), but they can also perform introspection, using "handlers".
361
+ We can tackle this a couple ways. You can apply "last-minute" styles in the
362
+ `layout` and `subview` methods:
201
363
 
202
- Basics
203
- ======
364
+ ```ruby
365
+ layout do
366
+ subview(UILabel, :h1,
367
+ # the `subview` and `layout` methods can apply styles
368
+ text: "Omg, it's full of stars"
369
+ )
370
+ end
371
+ ```
204
372
 
205
- Create a stylesheet in any code file, usually `styles.rb` or `styles/main.rb`,
206
- if you have a ton of 'em. The `Teacup::Stylesheet` constructor accepts a
207
- stylesheet name and a block, which will contain your style declarations.
373
+ In this case though we just have static text, so you can assign the text using
374
+ the stylesheet:
208
375
 
209
376
  ```ruby
210
- Teacup::Stylesheet.new :main_menu do
211
- style :ready_to_play_button,
212
- backgroundColor: UIColor.blackColor,
213
- frame: [[20, 300], [50, 20]] # [[x, y], [w, h]]
377
+ Teacup::Stylesheet.new :main do
378
+
379
+ style :h1,
380
+ font: UIFont.systemFontOfSize(20)
381
+
382
+ style :main_header,
383
+ text: "Omg, it's full of stars",
384
+ font: UIFont.systemFontOfSize(20)
385
+
214
386
  end
215
387
  ```
216
388
 
217
- Any method that accepts a single value can be assigned here. Please don't abuse
218
- this by hiding application logic in your stylesheets - these are meant for
219
- *design*, not behavior. That said, if you're coding by yourself - go for it! ;)
389
+ ### Extending Styles
220
390
 
221
- Stylesheets
222
- ===========
391
+ Not very DRY though is it!? We have to use a new style (`:main_header`) because
392
+ not all our labels say "OMG", but we want to use our font from the `:h1` style.
393
+ We can tell the `:main_header` style that it `extends` the `:h1` style:
394
+
395
+ ```ruby
396
+ layout do
397
+ subview(UILabel, :main_header)
398
+ end
399
+
400
+ Teacup::Stylesheet.new :main do
401
+
402
+ style :h1,
403
+ font: UIFont.systemFontOfSize(20)
404
+
405
+ style :main_header, extends: :h1,
406
+ text: "Omg, it's full of stars"
407
+
408
+ end
409
+ ```
410
+
411
+ A common style when writing stylesheets is to use instance variables to store
412
+ settings you want to tweak.
413
+
414
+ ```ruby
415
+ Teacup::Stylesheet.new :main do
416
+ @hi_font = UIFont.systemFontOfSize(20)
223
417
 
224
- The `Teacup::Stylesheet` class has methods that create a micro-DSL in the
225
- context of a `style` declaration. You can view the source (most of them are
226
- very short methods) in `lib/teacup/stylesheet_extensions/*.rb`.
418
+ style :h1,
419
+ font: @hi_font
420
+ style :main_header, extends: :h1,
421
+ text: "Omg, it's full of stars"
422
+ end
423
+ ```
424
+
425
+ ### Style via UIView Class
426
+
427
+ If you need to apply styles to *all* instances of a `UIView` subclass, you can
428
+ do so by applying styles to a class name instead of a symbol. This feature is
429
+ handy at times when you might otherwise use `UIAppearance` (which teacup also
430
+ supports!).
227
431
 
228
432
  ```ruby
229
- # autoresizingMask
230
- style :spinner,
231
- center: [320, 460],
232
- autoresizingMask: flexible_left|flexible_right|flexible_top|flexible_bottom
433
+ Teacup::Stylesheet.new :app do
233
434
 
435
+ style UILabel,
436
+ font: UIFont.systemFontOfSize(20)
234
437
 
235
- # device-specific geometries - app_size, screen_size, and device/device? methods
236
- style :root,
237
- origin: [0, 0],
238
- size: app_size # doesn't include the status bar - screen_size does
438
+ style UITableView,
439
+ backgroundColor: UIColor.blackColor
239
440
 
240
- if device? iPhone
241
- style :image, image: UIImage.imageNamed "my iphone image"
242
- elsif device? iPad
243
- style :image, image: UIImage.imageNamed "my ipad image"
244
441
  end
442
+ ```
443
+
444
+ ### Importing stylesheets
445
+
446
+ We've touched on the ability to write styles, extend styles, and apply styles to
447
+ a class. Now we can introduce another feature that is even more useful for
448
+ applying styles to your entire app: import a stylesheet.
449
+
450
+ When you import a stylesheet, you receive all of its `style`s *and* you gain
451
+ access to its instance variables. This way you can define colors and margins and
452
+ such in a "parent" stylesheet.
453
+
454
+ ```ruby
455
+ Teacup::Stylesheet.new :app do
456
+
457
+ @header_color = UIColor.colorWithRed(7/255.0, green:16/255.0, blue:95/255.0, alpha: 1)
458
+ @background_color = UIColor.colorWithRed(216/255.0, green:226/255.0, blue:189/255.0, alpha: 1)
459
+
460
+ style :root,
461
+ backgroundColor: @background_color
462
+
463
+ style :header,
464
+ textColor: @header_color
245
465
 
246
- # the device method can be OR'd with the devices. The return value is always a
247
- # number, so make sure to compare to 0
248
- if device|iPhone > 0
249
- # ...
250
466
  end
251
- # so yeah, you might as well use `device? iPhone`, in my opinion.
252
467
 
468
+ Teacup::Stylesheet.new :main do
469
+ import :app
253
470
 
254
- # rotations - use the identity and pi methods
255
- style :button,
256
- layer: {
257
- transform: spin identity, 2*pi
258
- }
471
+ style :subheader, extends: :header # <= the :header style is imported from the :app stylesheet
472
+
473
+ style :button,
474
+ titleColor: @header_color # <= @header_color is imported, too
475
+ end
259
476
  ```
260
477
 
261
- Orientations
262
- ============
478
+ ### Style via UIAppearance
263
479
 
264
- Teacup stylesheets can be given orientation hashes. The supported orientations
265
- are:
480
+ And lastly, the `UIAppearance protocol` is supported by creating an instance of
481
+ `Teacup::Appearance`. There is debatable benefit to using [UIAppearance][],
482
+ because it will apply styles to views that are outside your control, like the
483
+ camera/image pickers and email/message controllers. Using `import` instead will
484
+ not apply styles to those views.
485
+
486
+ But, it does come in handy sometimes... so here it is!
487
+
488
+ ```ruby
489
+ Teacup::Appearance.new do
266
490
 
267
- - `portrait` - upright or upside down
268
- - `upside_up`
269
- - `upside_down`
270
- - `landscape` - on either side
271
- - `landscape_left` - "left" refers to the home button, e.g. the button is on the left.
272
- - `landscape_right` - home button is on the right
491
+ # UINavigationBar.appearance.setTintColor(UIColor.blackColor)
492
+ style UINavigationBar,
493
+ tintColor: UIColor.blackColor
273
494
 
274
- An example should suffice:
495
+ # UINavigationBar.appearanceWhenContainedIn(UINavigationBar, nil).setTintColor(UIColor.blackColor)
496
+ style UIBarButtonItem, when_contained_in: UINavigationBar,
497
+ tintColor: UIColor.blackColor
498
+
499
+ # UINavigationBar.appearanceWhenContainedIn(UIToolbar, UIPopoverController, nil).setTintColor(UIColor.blackColor)
500
+ style UIBarButtonItem, when_contained_in: [UIToolbar, UIPopoverController],
501
+ tintColor: UIColor.blackColor
502
+
503
+ end
504
+ ```
505
+
506
+ That block is called using the `UIApplicationDidFinishLaunchingNotification`,
507
+ but that notification is not called until the *end* of the
508
+ `application(application,didFinishLaunchingWithOptions:launchOptions)` method.
509
+ This is sometimes after your views have been created, and so they will not be
510
+ styled. If that is the case, call `Teacup::Appearance.apply` before creating
511
+ your `rootViewController`.
512
+
513
+ ### Now go use Teacup!
514
+
515
+ You have enough information *right now* to go play with Teacup. Check out the
516
+ example apps, write your own, whatever. But read on to hear about why Teacup is
517
+ more than just writing `layouts` and applying styles.
518
+
519
+ Teacup as a utility
520
+ -------------------
521
+
522
+ When you are prototyping an app it is useful to bang out a bunch of code
523
+ quickly, and here are some ways that Teacup might help.
524
+
525
+ You can use all the methods above without having to rely on the entirety of
526
+ Teacup's layout and stylesheet systems. By that I mean *any* time you are
527
+ creating a view hierarchy don't be shy about using Teacup to do it.
528
+
529
+ `UIView` has the `style` method, which can be used to group a bunch of
530
+ customizations anywhere in your code. You don't *have* to pull out a stylesheet
531
+ to do it.
275
532
 
276
533
  ```ruby
277
- style :ready_to_play_button,
278
- portrait: {
279
- frame: [[20, 300], [50, 20]]
280
- },
281
- landscape: {
282
- frame: [[60, 300], [50, 20]] # button moves over 40 pixels because of the wider screen
283
- }
534
+ # Custom Navigation Title created and styled by Teacup
535
+ self.navigationItem.titleView = layout(UILabel,
536
+ text:'Title',
537
+ font: UIFont.systemFontOfSize(12),
538
+ )
539
+
540
+ # Customize contentView in a UITableViewCell dataSource method
541
+ def tableView(table_view, cellForRowAtIndexPath:index_path)
542
+ cell_identifier = 'MyController - cell'
543
+ cell = table_view.dequeueReusableCellWithIdentifier(cell_identifier)
544
+
545
+ unless cell
546
+ cell = UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault,
547
+ reuseIdentifier: cell_identifier)
548
+ layout(cell.contentView) do
549
+ subview(UIImageView, :image)
550
+ end
551
+ end
552
+
553
+ return cell
554
+ end
555
+
556
+ # Use the `style` method on a view to apply your styling. This is a one-shot
557
+ # styling.
558
+ @label.style(textColor: UIColor.blueColor, text: 'Blue Label')
284
559
  ```
285
560
 
286
- That code is repetive, though, let's shorten it up by using precedence and some
287
- aliases for setting the `frame`:
561
+ More Teacup features
562
+ --------------------
563
+
564
+ There are a few (OK, a bunch) more features that Teacup provides that deserve
565
+ discussion:
566
+
567
+ - Styling View Properties
568
+ - Orientation Styles
569
+ - UIView Additions
570
+ - Style Handlers
571
+ - Frame Calculations
572
+ - Auto-Layout & [Motion-Layout][motion-layout]
573
+ - Stylesheet Extensions
574
+
575
+ ### Styling View Properties
576
+
577
+ Styling a UIView is fun, but a UIView is often composed of many objects, like
578
+ the `layer`, or maybe an `imageView` or `textLabel` and so on. You can style
579
+ those, too!
288
580
 
289
581
  ```ruby
290
- style :ready_to_play_button,
291
- top: 300,
292
- width: 50,
293
- height: 20,
294
- portrait: {
295
- left: 20
582
+ # UITableViewCells have a contentView, a backgroundView, imageView, textLabel,
583
+ # detailTextLabel, and a layer! whew!
584
+ style :tablecell,
585
+ layer: { # style the layer!
586
+ shadowRadius: 3
296
587
  },
297
- landscape: {
298
- left: 60
588
+ backgroundView: { # style the background!
589
+ backgroundColor: UIColor.blackColor
590
+ },
591
+ imageView: { # style the imageView!
592
+ contentMode: UIViewContentModeScaleAspectFill
299
593
  }
300
594
  ```
301
595
 
302
- Styles declared in an orientation hash will override the "generic" styles
303
- declared directly above it, so the above could also be written as:
596
+ ### Orientation Styles
597
+
598
+ There's more to stylesheets than just translating `UIView` setters. Teacup can
599
+ also apply orientation-specific styles. These are applied when the view is
600
+ created (using the current device orientation) and when a rotation occurs.
304
601
 
305
602
  ```ruby
306
- style :ready_to_play_button,
307
- top: 300,
308
- width: 50,
309
- height: 20,
310
- left: 20
311
- landscape: {
312
- left: 60 # overrides left: 20
313
- }
603
+ Teacup::Stylesheet.new :main do
604
+
605
+ # this label hides when the orientation is landscape (left or right)
606
+ style :label,
607
+ landscape: {
608
+ hidden: true
609
+ },
610
+ portrait: {
611
+ hidden: false
612
+ }
613
+
614
+ end
314
615
  ```
315
616
 
316
- Handlers
317
- ========
617
+ Combine these styles with [Frame Calculations][calculations] to have you view
618
+ frame recalculated automatically.
619
+
620
+ ### UIView animation additions
621
+
622
+ We've already seen the Teacup related properties:
623
+
624
+ - `stylename`, the primary style name
625
+ - `style_classes`, secondary style names
626
+ - `style`, apply styles directly
627
+
628
+ Each of these has a corresponding method that you can use to facilitate
629
+ animations.
630
+
631
+ - `animate_to_stylename(stylename)`
632
+ - `animate_to_styles(style_classes)`
633
+ - `animate_to_style(properties)`
634
+
635
+ ### Style Handlers
636
+
637
+ *This feature is used extensively by [sweettea][] to make a more intuitive
638
+ stylesheet DSL*
318
639
 
319
- Above, we saw that we can assign `view.frame.x` by using the `left` property.
320
- There *is* no `UIView#left` method, so this must be handled somewhere special...
640
+ Teacup is, by itself, pretty useful, but it really does little more than map
641
+ Hash keys to `UIView` setters. That's great, because it keeps the system easy
642
+ to understand. But there are some methods in UIKit that take more than one
643
+ argument, or could benefit from some shorthands.
321
644
 
322
- Not **that** special, it turns out. This used to be an internal translation,
323
- but the list of "translations" was getting out of hand, and we realized that we
324
- could break this out into a new feature. **Handlers**. Here is the `handler`
325
- for the `left` property:
645
+ This is where Teacup's style handlers come in. They are matched against a
646
+ `UIView` subclass and one or more stylenames, and they are used to apply that
647
+ style when you use it in your stylesheet.
326
648
 
327
649
  ```ruby
328
- Teacup.handler UIView, :left { |view, x|
329
- f = view.frame
330
- f.origin.x = x
331
- view.frame = f
332
- }
650
+ # this handler adds a `:title` handler to the UIButton class (and subclasses).
651
+ Teacup.handler UIButton, :title do |target, title|
652
+ target.setTitle(title, forState: UIControlStateNormal)
653
+ end
654
+
655
+ # ...
656
+ subview(UIButton,
657
+ title: 'This is the title' # <= this will end up being passed to the handler above
658
+ )
659
+
660
+ layout(UINavigationItem,
661
+ title: 'This is the title' # <= but this will not! the handler above is restricted to UIButton subclasses
662
+ )
333
663
  ```
334
664
 
335
- How about setting the title of a `UIButton`?
665
+ [Other built-in handlers][other-handlers] are defined in `z_handlers.rb`.
666
+ Another useful one is the ability to make view the same size as its parent, and
667
+ located at the origin.
336
668
 
337
669
  ```ruby
338
- Teacup.handler UIButton, :title { |target, title|
339
- target.setTitle(title, forState: UIControlStateNormal)
340
- }
670
+ style :container,
671
+ frame: :full # => [[0, 0], superview.frame.size]
341
672
  ```
342
673
 
343
- You can also make aliases for long method names, or to shorten a style you use a
344
- lot. You can alias two ways - give multiple names in a `Teacup.handler` method,
345
- or use `Teacup.alias`.
674
+ ### Frame Calculations
675
+
676
+ *These are super cool, just don't forget your autoresizingMasks*
677
+
678
+ When positioning views you will often have situations where you want to have a
679
+ view centered, or 8 pixels to the right of center, or full width/height. All of
680
+ these relationships can be described using the `Teacup.calculate` method, which
681
+ is called automatically in any method that modifies the `frame` or `center`.
682
+
683
+ frame, origin, size
684
+ top/y, left/x, right, bottom, width, height
685
+ center_x/middle_x, center_y/middle_y, center
346
686
 
347
687
  ```ruby
348
- # the actual `left` handler offers an alias `x`
349
- Teacup, handler UIView, :left, :x { |view, x|
350
- f = view.frame
351
- f.origin.x = x
352
- view.frame = f
353
- }
688
+ Teacup::Stylesheet.new :main do
689
+
690
+ style :button,
691
+ left: 8, top: 8, # easy enough!
692
+ width: '100% - 16', # woah! (O_o)
693
+ height: 22
694
+
695
+ style :top_half,
696
+ frame: [[0, 0], ['100%', '50%']]
697
+ style :bottom_half,
698
+ frame: [[0, '50%'], ['100%', '50%']]
354
699
 
355
- # but I speak japanese, and I want it to be called "hidari" instead, and I want
356
- # top to be "ue".
357
- Teacup.alias UIView, :hidari => :left, :ue => :top
700
+ end
358
701
  ```
359
702
 
360
- extends:
361
- =======
703
+ When this code executes, the string `'100% - 16'` is translated into the formula
704
+ `1.00 * target.superview.frame.size.width - 16`. If the property is related to
705
+ the height or y-position, it will be calculated based on the height.
362
706
 
363
- You might have a view where all the buttons or text fields have similar colors
364
- or font. You can have them extend a common style declaration.
707
+ The frame calculations must be a string of the form `/[0-9]+% [+-] [0-9]+/`. If
708
+ you need more "math-y-ness" than that, you can construct strings using interpolation.
365
709
 
366
710
  ```ruby
711
+ margin = 8
712
+
367
713
  style :button,
368
- font: UIFont.systemFontOfSize(20)
714
+ left: margin, top: margin,
715
+ width: "100% - #{margin * 2}",
716
+ height: 22
717
+
718
+ # just for fun, let's see what it would take to add a margin between these two views.
719
+ style :top_half,
720
+ frame: [[0, 0], ['100%', "50% - #{margin / 2}"]]
721
+ style :bottom_half,
722
+ frame: [[0, "50% + #{margin / 2}"], ['100%', "50% - #{margin / 2}"]]
723
+ ```
369
724
 
370
- style :ok_button, extends: :button,
371
- title: "OK"
725
+ One more example: The real power of the frame calculations comes when you
726
+ remember to set springs and struts. You can have a view "pinned" to the bottom
727
+ if you remember to set the `autoresizingMask`.
372
728
 
373
- style :cancel_button, extends: :button,
374
- title: "Cancel"
729
+ ```ruby
730
+ Teacup::Stylesheet.new :main do
731
+
732
+ style :button,
733
+ # fixed width / height
734
+ height: 22, width: 200,
735
+ center_x: '50%',
736
+ top: '100% - 30', # includes an 8px margin from the bottom
737
+ autoresizingMask: (UIViewAutoresizingFlexibleLeftMargin |
738
+ UIViewAutoresizingFlexibleRightMargin |
739
+ UIViewAutoresizingFlexibleTopMargin)
740
+ # see the autoresizing extension below for an even better way to write this.
741
+ end
375
742
  ```
376
743
 
377
- Precedence is important. We said that "orientation" overrides "generic", but
378
- that is only at the local `style` declaration level. If you declare a property
379
- in `style` that is set in an orientation hash in an extended style, *your*
380
- property will win.
744
+ ### Auto-Layout
745
+
746
+ *This is another much bigger topic than it is given space for here*
747
+
748
+ Teacup includes an Auto-Layout constraint DSL that you can use in your
749
+ stylesheets. These methods are added to the `Stylesheet` class, so unless you
750
+ are in the context of a stylesheet, you will have to create your constraints in
751
+ longhand (you can still use the `Teacup::Constraint` class to help you!).
752
+
753
+ I won't sugar-coat it: Auto-Layout is hard. Much harder than using frames and
754
+ springs and struts. And honestly, I recommend you try using the
755
+ `Teacup.calculate` features mentioned above, they will take you far.
756
+
757
+ But at the end of the day, once you really understand the auto-layout system
758
+ that Apple released in iOS 6, you can build your UIs to be responsive to
759
+ different devices, orientations, and sizes. UIs built with auto-layout do not
760
+ usually need to adjust anything during a rotation. The constraints take *care*
761
+ of it all. It's impressive.
762
+
763
+ Here's a quick example that creates this shape. The edges are bound to the
764
+ superview's frame.
765
+
766
+ +-----+----------------+
767
+ | | |
768
+ | A | B |
769
+ | | +-----| <\
770
+ | | | C | |_ 50% of B's height, minus 10 pixels
771
+ +-----+----------+-----+ </
772
+ ^--+--^ ^--+--^
773
+ |_fixed (100) |_fixed (100)
381
774
 
382
775
  ```ruby
383
- style :button,
384
- portrait: {
385
- width: 40
386
- },
387
- landscape: {
388
- width: 45
389
- }
776
+ Teacup::Stylesheet.new do
777
+ style :A,
778
+ constraints: [
779
+ # these first three are all fixed, so super easy
780
+ constrain_left(0),
781
+ constrain_width(100),
782
+ constrain_top(0),
783
+ # here we go, here's a real constraint
784
+ constrain(:bottom).equals(:superview, :bottom),
785
+ ]
786
+
787
+ style :B,
788
+ constraints: [
789
+ # B.left == A.right
790
+ constrain(:left).equals(:A, :right),
791
+ # B.height == A.height
792
+ constrain(:height).equals(:A, :height),
793
+ constrain(:right).equals(:superview, :right),
794
+ ]
795
+
796
+ style :C, # <= this looks like a very grumpy style :C
797
+ constraints: [
798
+ constrain_width(100),
799
+ # pin to bottom-right corner
800
+ constrain(:right).equals(:superview, :right),
801
+ constrain(:bottom).equals(:superview, :bottom),
802
+ # 50% B.height - 10
803
+ constrain(:height).equals(:B, :height).times(0.5).minus(10),
804
+ ]
805
+
806
+ end
807
+ ```
808
+
809
+ Writing views this way will either make your brain hurt, or make the math-nerd
810
+ in you chuckle with glee. In this example you could go completely with just
811
+ frame calculation formulas and springs and struts. Your frame code would still
812
+ be cluttered, just cluttered in a different way.
813
+
814
+ ### Motion-Layout[motion-layout]
390
815
 
391
- style :ok_button, extends: :button,
392
- title: "OK",
393
- width: 40 # width will always be 40, even in landscape
816
+ If you are using [Nick Quaranto][qrush]'s [motion-layout][] gem, you can use it from within
817
+ any class that includes `Teacup::Layout`. Then benefit is that the Teacup
818
+ stylenames assigned to your views will be used in the dictionary that the
819
+ ASCII-based system relies on.
820
+
821
+ ```ruby
822
+ layout do
823
+ subview(UIView, :view_a)
824
+ subview(UIView, :view_b)
825
+ subview(UIView, :view_c)
826
+
827
+ # if you need to apply these to a different view, or if you want to assign
828
+ # different names to use in the ASCII strings
829
+ # auto(layout_view=self.view, layout_subviews={}, &layout_block)
830
+
831
+ auto do
832
+ metrics 'margin' => 20
833
+ vertical "|-[view_a]-margin-[view_b]-margin-[view_c]-|"
834
+ horizontal "|-margin-[view_a]-margin-|"
835
+ horizontal "|-margin-[view_b]-margin-|"
836
+ horizontal "|-margin-[view_c]-margin-|"
837
+ end
838
+ end
394
839
 
395
- style :cancel_button, extends: :button,
396
- title: "Cancel"
397
- # width will be 40 or 45, depending on orientation
398
840
  ```
399
841
 
400
- import
401
- ======
842
+ ### Stylesheet extensions
402
843
 
403
- Each `UIView` or `UIViewController` can have only one stylesheet attached to it.
404
- If you want to break up a stylesheet into multiple sheets, you will use `import`
405
- to do it.
844
+ Auto-Layout is just one Stylesheet extension, there are a few others. And if
845
+ you want to write your own, just open up the `Teacup::Stylesheet` class and
846
+ start adding methods.
847
+
848
+ #### Autoresizing Masks
849
+
850
+ If you've used the SugarCube `uiautoresizingmask` methods, you'll recognize
851
+ these. They are handy, and hopefully intuitive, shorthands for common springs
852
+ and struts.
853
+
854
+ In previous versions of Teacup these were available without needing the
855
+ `autoresize` prefix. The old methods are still available, but deprecated.
406
856
 
407
857
  ```ruby
408
- Teacup::Stylesheet.new :base do
409
- style :button,
410
- font: UIFont.systemFontOfSize(20)
858
+ # keeps the width and height in proportion to the parent view
859
+ style :container,
860
+ autoresizingMask: autoresize.flexible_width | autoresize.flexible_height
861
+
862
+ # the same, but using block syntax
863
+ style :container,
864
+ autoresizingMask: autoresize { flexible_width | flexible_height }
865
+
866
+ # the same again, using a shorthand
867
+ style :container,
868
+ autoresizingMask: autoresize.fill
869
+ ```
870
+
871
+ The autoresize methods are grouped into four categories: `flexible, fill, fixed,
872
+ and float`. The flexible methods correspond 1::1 with the `UIViewAutoresizing*`
873
+ constants.
874
+
875
+ The `fill` methods (`fill,fill_top,fill_bottom,fill_left,fill_right`) will
876
+ stretch the width, or height, or both. The location specifies where the view is
877
+ pinned, so `fill_top` will stretch the width and bottom margin, but keep it the
878
+ same distance from the top (not necessarily *at* the top, but a fixed distance).
879
+ `fill_right` will pin it to the right side, stretch the height, and have a
880
+ flexible left margin.
881
+
882
+ The `fixed` methods pin the view to one of nine locations:
883
+
884
+ top_left | top_middle | top_right
885
+ ------------+---------------+-------------
886
+ middle_left | middle | middle_right
887
+ ------------+---------------+-------------
888
+ bottom_left | bottom_middle | bottom_right
889
+
890
+ The `float` methods fill in the last gap, when you don't want your view pinned
891
+ to any corner, and you don't want it to change size.
892
+
893
+ float_horizontal | float_vertical == fixed_middle
894
+
895
+ #### Device detection
896
+
897
+ Because the stylesheets are defined in a block, you can perform tests for device
898
+ and screen size before setting styles. For instance, on an ipad you might want
899
+ to have a larger margin than on the iphone.
900
+
901
+ The `Stylesheet` device methods will help you create these conditions:
902
+
903
+ ```ruby
904
+ Teacup::Stylesheet.new do
905
+ if device_is? iPhone
906
+ margin = 8
907
+ elsif device_is? iPad
908
+ margin = 20
909
+ end
910
+
911
+ style :container,
912
+ frame: [[margin, margin], ["100% - #{margin * 2}", "100% * #{margin * 2}"]]
411
913
  end
914
+ ```
412
915
 
413
- Teacup::Stylesheet.new :main do
414
- import :base
916
+ Multiple calls to `style` will *add* those styles, not replace. So this code
917
+ works just fine:
415
918
 
416
- style :ok_button, extends: :button,
417
- top: 10
919
+ ```ruby
920
+ Teacup::Stylesheet.new do
921
+ style :logo,
922
+ origin: [0, 0]
923
+
924
+ if device_is? iPhone
925
+ style :logo, image: UIImage.imageNamed "small logo"
926
+ elsif device_is? iPad
927
+ style :logo, image: UIImage.imageNamed "big logo"
928
+ end
418
929
  end
930
+ ```
931
+
932
+ #### Rotation helpers
419
933
 
420
- Teacup::Stylesheet.new :register do
421
- import :base
934
+ Because you can animate changes to the stylename or style_classes, you can make
935
+ it pretty easy to apply rotation effects to a `UIView` or `CALayer`. The
936
+ `style_classes` property is especially useful for this purpose.
937
+
938
+ ```ruby
939
+ style :container,
940
+ frame: :full
941
+
942
+ # UIView transforms
943
+
944
+ style :rotated,
945
+ transform: transform_view.rotate(pi / 2) # pi and transform_view are methods on Stylesheet
946
+
947
+ style :not_rotated,
948
+ transform: transform_view.rotate(0)
949
+
950
+ # CALayer transforms
951
+
952
+ style :rotated,
953
+ layer: { transform: transform_layer.rotate(pi / 2) }
954
+
955
+ style :not_rotated,
956
+ layer: { transform: transform_layer.rotate(0) }
957
+ ```
958
+
959
+ These work even better when used with the [geomotion][] methods that extend
960
+ `CGAffineTransform` and `CATransform3D`.
961
+
962
+ ```ruby
963
+ style :goofy,
964
+ transform: CGAffineTransform.rotate(pi / 2).translate(100, 0).scale(2)
965
+ style :regular,
966
+ transform: CGAffineTransform.identity
967
+
968
+ # CALayer uses CATransform3D objects
969
+ style :regular,
970
+ layer: {
971
+ transform: CATransform3D.rotate(pi / 2)
972
+ }
973
+ ```
974
+
975
+ ### Showdown
976
+
977
+ As a recap, here is a translation of traditional Cocoa code done using Teacup.
978
+
979
+ No cool tricks here, just some plain ol' Cocoa.
980
+
981
+ ```ruby
982
+ #
983
+ # Traditional Cocoa
984
+ #
985
+ class SomeController < UIViewController
986
+
987
+ def viewDidLoad
988
+ @field = UITextField.new
989
+ @field.frame = [[10, 10], [200, 50]]
990
+ @field.textColor = UIColor.redColor
991
+ view.addSubview(@field)
992
+
993
+ @search = UITextField.new
994
+ @search.frame = [[10, 70], [200, 50]]
995
+ @search.placeholder = 'Find something...'
996
+ @search.textColor = UIColor.redColor
997
+ view.addSubview(@search)
998
+ end
999
+
1000
+ # perform the frame changes depending on orientation
1001
+ def willAnimateRotationToInterfaceOrientation(orientation, duration:duration)
1002
+ case orientation
1003
+ when UIInterfaceOrientationLandscapeLeft, UIInterfaceOrientationLandscapeRight
1004
+ @field.frame = [[10, 10], [360, 50]]
1005
+ @search.frame = [[10, 70], [360, 50]]
1006
+ else
1007
+ @field.frame = [[10, 10], [200, 50]]
1008
+ @search.frame = [[10, 70], [200, 50]]
1009
+ end
1010
+ end
1011
+
1012
+ end
1013
+
1014
+ #
1015
+ # Teacup
1016
+ #
1017
+
1018
+ class SomeController < UIViewController
1019
+
1020
+ stylesheet :some_view
1021
+
1022
+ layout :root do
1023
+ subview(UITextField, :field)
1024
+ @search = subview(UITextField, :search)
1025
+ end
1026
+
1027
+ end
1028
+
1029
+ Teacup::Stylesheet.new(:some_view) do
1030
+
1031
+ style :root, # enable landscape rotation (otherwise only portrait is enabled)
1032
+ landscape: true # this must be on the root-view, to indicate that this view is
1033
+ # capable of handling rotations
1034
+
1035
+ style :field,
1036
+ left: 10,
1037
+ top: 10,
1038
+ width: 200,
1039
+ height: 50,
1040
+ landscape: {
1041
+ width: 360 # make it wide in landscape view
1042
+ }
1043
+
1044
+ style :search, extends: :field,
1045
+ left: 10,
1046
+ top: 70,
1047
+ placeholder: 'Find something...'
1048
+
1049
+ style UITextField, # Defining styles based on view class instead
1050
+ textColor: UIColor.redColor # of style name.
422
1051
 
423
- style :submit_button, extends: :button,
424
- top: 50
425
1052
  end
426
1053
  ```
427
1054
 
428
- UIView classes
429
- ==============
1055
+ The Nitty Gritty
1056
+ ----------------
1057
+
1058
+ #### Regarding Style Precedence
1059
+
1060
+ You need to be careful when extending styles and using orientation styles
1061
+ because the precedence rules take some getting used to. The goal is that you
1062
+ can have all your style code in the stylesheets. But you also need to be able
1063
+ to animate your views, and rotating the device should not go reseting
1064
+ everything.
1065
+
1066
+ So here's what happens.
1067
+
1068
+ When your controller is loaded, `viewDidLoad` is called, and that's where Teacup
1069
+ creates the view hierarchy and applies the styles. It is at the *end* of the
1070
+ method that the styles are applied - not until all the views have been added.
1071
+ The current device orientation will be used so that orientation-specific styles
1072
+ will be applied.
1073
+
1074
+ Now Teacup goes quiet for a while. Your app chugs along... until the user
1075
+ rotates the device.
1076
+
1077
+ If you have orientation-specific styles, they will get applied. But the
1078
+ *original* styles (the "generic" styles) **will not**.
1079
+
1080
+ However, there's a way around *that, too.* If you call `restyle!` on a
1081
+ `UIView`, that will reapply all the original stylesheet styles - orientation
1082
+ *and* generic styles.
1083
+
1084
+ With me so far? Orientation styles are reapplied whenever the device is
1085
+ rotated. But generic styles are only applied in `viewDidLoad` and when
1086
+ `restyle!` is called explicitly.
1087
+
1088
+ How does the `:extends` property affect things?
1089
+
1090
+ If your stylesheet defines orientation-specific styles and "generic" styles, the
1091
+ orientation-specific styles win. But if you *extend* a style that has
1092
+ orientation-specific styles, your local generic styles will win.
1093
+
1094
+ The more "local" styles always win - and that applies to styles that you add
1095
+ using the `subview/layout` methods, too. The only time it doesn't really apply
1096
+ is if you apply styles using `UIView#style` or `UIView#apply_stylename`. Those
1097
+ are one-shot (they can get overwritten when `restyle!` is called).
430
1098
 
431
- You can style entire classes of `UIView`s! These get merged in last (lowest
432
- precedence), but they are a great way to get site-wide styles. Put these in a
433
- base stylesheet, and import that stylesheet everywhere. The Apple
434
- `UIAppearance` protocol/classes do this same thing, but in much more code ;-)
1099
+ There are also times when you either want (or must) override (or add to) the
1100
+ stylesheet styles. For instance, if you want to assign the `delegate` or
1101
+ `dataSource` properties, this cannot be done from a `Stylesheet`. But that's
1102
+ okay, because we have a chance to add these styles in the `subview` and `layout`
1103
+ methods.
435
1104
 
436
1105
  ```ruby
437
- Teacup::Stylesheet.new :base do
438
- style UIButton,
439
- font: UIFont.systemFontOfSize(20)
1106
+ layout do
1107
+ subview(UITableView, delegate: self)
440
1108
  end
1109
+ ```
441
1110
 
442
- Teacup::Stylesheet.new :main do
443
- style :ok_button, # no need to use extends
444
- top: 10
1111
+ Styles applied here are one-shot. It is the exact same as assigning the
1112
+ `stylename` and `style_classes` and then calling `style`. Because the
1113
+ stylesheet is not necessarily applied immediately, these styles could be
1114
+ overwritten before they take effect.
1115
+
1116
+ ```ruby
1117
+ layout do
1118
+ table_view = subview(UITableView, :tableview, delegate: self,
1119
+ font: UIFont.boldSystemFontOfSize(10) # the stylesheet could override this during rotation
1120
+ )
445
1121
  end
446
1122
 
447
- Teacup::Stylesheet.new :register do
448
- style :submit_button, # no need to use extends
449
- top: 50
1123
+ def layoutDidLoad
1124
+ table_view.apply_stylename(:tableview_init) # this will only get applied once
450
1125
  end
451
1126
  ```
452
1127
 
453
- Precedence, and Style
454
- ==========
1128
+ The idea here is that the closer the style setting is to where the view is
1129
+ instantiated, the higher the precedence.
455
1130
 
456
- 1. Within a `style` declaration, orientation-specific properties override generic properties
457
- 2. Imported properties will be merged in, but no values from (1) will be overridden
458
- 3. Extended styles will be merged in, but no values from (1) or (2) will be overridden
459
- 4. Styles applied to an ancestor will be merged in, but no values from (1) or (2) or (3) will be overridden
1131
+ More examples!
460
1132
 
461
- These rules are maintained in the `Style` class. It starts by creating a Hash
462
- based on the `style` declaration, then merges the orientation styles, if
463
- applicable (orientation styles override). Next it merges imports, then
464
- extends, and finally the class ancestors. At each "merge" except orientation
465
- merges, it is a recursive-soft-merge, meaning that Hashes will be merged, but
466
- existing keys will be left alone.
1133
+ ```ruby
1134
+ class MyController < UIViewController
1135
+ stylyesheet :my_sheet
1136
+ layout do
1137
+ subview(UILabel, :label, text: 'overrides')
1138
+ end
1139
+ end
1140
+ Teacup::Stylesheet.new :my_sheet do
1141
+ style :generic_label,
1142
+ text: 'portrait',
1143
+ # these get applied initially, but after being rotated they will not get
1144
+ # applied again
1145
+ font: UIFont.boldSystemFontOfSize(10),
1146
+ textColor: UIColor.grayColor,
1147
+ landscape: {
1148
+ font: UIFont.boldSystemFontOfSize(12),
1149
+ textColor: UIColor.whiteColor,
1150
+ } # this style should add a `portrait` setting that restores the font and color
1151
+
1152
+ style :label, extends: :generic_label,
1153
+ font: UIFont.systemFontOfSize(10), # this will override all the font settings
1154
+ end
1155
+ ```
467
1156
 
468
- Unless you are going to be hacking on teacup, you do not need to understand the
469
- nitty-gritty - just remember the precedence rules.
1157
+ Advanced Teacup Tricks
1158
+ ----------------------
470
1159
 
471
- Development
472
- -----------
1160
+ There are times when you might wish teacup "just worked", but please remember:
1161
+ Teacup is not a "blessed" framework built by Apple engineers. We have access to
1162
+ the same APIs that you do. That said, here are some use-cases where you can most
1163
+ definitely *use* teacup, but you'll need to do a little more leg work.
473
1164
 
474
- *Current version*: v0.2.0 (or see `lib/teacup/version.rb`)
1165
+ ### Trust your parent view - by using springs and struts
475
1166
 
476
- *Last milestone*: Release layout and stylesheet DSL to the world.
1167
+ *...not autolayout*
477
1168
 
478
- *Next milestone*: Provide default styles, that mimic Interface Builder's object library
1169
+ It's been mentioned a few times in this document that Teacup will create & style
1170
+ views in the `viewDidLoad` method. That means that the `superview` property of
1171
+ the controller's view will, necessarily, *not* be set yet. `viewDidLoad` is
1172
+ called after the view is instantiated (in `loadView`), and it hasn't been added
1173
+ as a subview yet.
479
1174
 
480
- **Changelog v0.2.0:**
1175
+ Auto-Layout is based on the relationship between two views - often a container
1176
+ and child view. It's an amazing system, but if that parent view *isn't
1177
+ available*, well, you're not gonna have much success.
481
1178
 
482
- - Stylesheets are no longer constants. Instead, they can be fetched by name: `Teacup::Stylesheet[:iphone]`
483
- - Stylesheets can be assigned by calling the `stylesheet :stylesheet_name` inside a view controller.
484
- - Ability to style based on view class.
485
- - Support for orientation-based styles.
1179
+ In the case of a UIViewController your "container" is the `self.view` property,
1180
+ which by default has sensible springs setup so that it stretches to fill the
1181
+ superview. It's not until you go messing with the `self.view` property, or are
1182
+ not in the context of a `UIViewController` that things get hairy.
486
1183
 
1184
+ If this is the case, you should get some pretty obvious warning messages,
1185
+ something along the lines of `Could not find :superview`.
487
1186
 
488
- teacup, being a community project, moves in "spurts" of decision making and
489
- coding. We will announce when we are in "proposal mode". That's a good time to
490
- jump into the project and offer suggestions for its future.
1187
+ ### UITableViewCell
491
1188
 
492
- And we're usually hanging out over at the `#teacuprb` channel on `irc.freenode.org`.
1189
+ If you are using your controller as your tableview dataSource this is not a big
1190
+ deal, the `subview` and `layout` methods continue to work as you expect them to.
1191
+
1192
+ I don't know about you, but I often write helper classes for tableviews that
1193
+ appear on many screens in an app. You should not shy away from adding teacup's
1194
+ `Layout` module to these helper classes.
1195
+
1196
+ ```ruby
1197
+ class TableHelper
1198
+ include Teacup::Layout
1199
+
1200
+ stylesheet :table_helper
1201
+
1202
+ def tableView(table_view, cellForRowAtIndexPath:index_path)
1203
+ cell_identifier = 'MyController - cell'
1204
+ cell = table_view.dequeueReusableCellWithIdentifier(cell_identifier)
1205
+
1206
+ unless cell
1207
+ cell = UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault
1208
+ reuseIdentifier: cell_identifier)
1209
+
1210
+ layout(cell.contentView) do
1211
+ subview(UIImageView, :image)
1212
+ end
1213
+ # cell.contentView and all child classes will "inherit" the :table_helper stylesheet
1214
+ end
1215
+
1216
+ return cell
1217
+ end
1218
+
1219
+ end
1220
+ ```
1221
+
1222
+ ### [Sweettea][]
1223
+
1224
+ *SugarCube + Teacup = Sweettea*
1225
+
1226
+ SugarCube was born of a desire to make Teacup stylesheets more readable, less
1227
+ cluttered with Apple's verbose method names and constants. Sweettea takes this
1228
+ a step further, by implementing a wealth of Teacup handlers that translate
1229
+ Symbols to constants and provide useful shorthands.
1230
+
1231
+ ```ruby
1232
+ style :button,
1233
+ normal: { image: 'button-white' },
1234
+ highlighted: { image: 'button-white-pressed' },
1235
+ title: 'Submit',
1236
+ shadow: {
1237
+ opacity: 0.5,
1238
+ radius: 3,
1239
+ offset: [3, 3],
1240
+ color: :black,
1241
+ },
1242
+ font: 'Comic Sans'
1243
+
1244
+ style :label,
1245
+ font: :bold,
1246
+ alignment: :center,
1247
+ color: :slateblue
1248
+ ```
1249
+
1250
+ Sweettea also offers some convenient styles that you can extend in your base
1251
+ class. You might want to either specify the Sweettea version you are using in
1252
+ your Gemfile, or copy the stylesheet so that changes to Sweettea don't affect
1253
+ your project. Once that projet is at 1.0 you can rely on the styles not
1254
+ changing.
1255
+
1256
+ ```ruby
1257
+ # buttons! :tan_button, :black_button, :green_button, :orange_button,
1258
+ # :blue_button, :white_button, :gray_button
1259
+ style :submit_button, extends: :white_button
1260
+
1261
+ # label sets more sensible defaults than a "raw" UILabel (like clear background)
1262
+ style :header, extends: :label
1263
+
1264
+ # inputs! these are not styled, they just setup keyboard and autocomplete
1265
+ # settings
1266
+ # :name_input, :ascii_input, :email_input, :url_input, :number_input,
1267
+ # :phone_input, :secure_input
1268
+ style :login_input, extends: :email_input
1269
+ style :password_input, extends: :secure_input
1270
+ ```
1271
+
1272
+ Misc notes
1273
+ ----------
1274
+
1275
+ Multiple calls to `style` with the same stylename combines styles, it doesn't
1276
+ replace the styles.
1277
+
1278
+ ------
1279
+
1280
+ Styles are not necessarily applied immediately. They are applied at the end of
1281
+ the outermost `layout/subview` method, including the `UIViewController##layout`
1282
+ block. If you call `stylename=` or `stylesheet=` *outside* a `layout/subview`
1283
+ block, your view will be restyled immediately.
1284
+
1285
+ ------
1286
+
1287
+ Restyling a view calls `restyle!` on all child views, all the way down the tree.
1288
+ Much care has been taken to call this method sparingly within Teacup.
1289
+
1290
+ ------
1291
+
1292
+ Any styles that you apply in a `layout/subview` method are *not* retained, they
1293
+ are applied immediately, and so the stylesheet can (and usually do) override
1294
+ those styles if there is a conflict. Only styles stored in a stylesheet are
1295
+ reapplied (during rotation or in `restyle!`).
1296
+
1297
+ ------
1298
+
1299
+ Stylesheets should not be modified once they are created - they cache styles by
1300
+ name (per orientation).
1301
+
1302
+ ------
1303
+
1304
+ You can add and remove a `style_class` using `add_style_class` and
1305
+ `remove_style_class`, which will call `restyle!` for you if `style_classes`
1306
+ array was changed.
1307
+
1308
+ ------
1309
+
1310
+ If you need to do frame calculations outside of the stylesheet code, you should
1311
+ do so in the `layoutDidLoad` method. This is not necessary, though! It is
1312
+ usually cleaner to do the frame calculations in stylesheets, either using
1313
+ [geomotion][], frame calculations, or auto-layout.
1314
+
1315
+ ------
1316
+
1317
+ Within a `subview/layout` block views are added to the last object in
1318
+ `Layout#superview_chain`. Views are pushed and popped from this array in the
1319
+ `Layout#layout` method, starting with the `top_level_view`. If you include
1320
+ `Teacup::Layout` on your own class, you do not *have* to implement
1321
+ `top_level_view` unless you want to use the `subview` method to add classes to a
1322
+ "default" target.
1323
+
1324
+ ------
1325
+
1326
+ When `UIView` goes looking for its `stylesheet` it does so by going up the
1327
+ responder chain. That means that if you define the stylesheet on a parent view
1328
+ or controller, all the child views will use that same stylesheet by default. It
1329
+ also means you can assign a stylesheet to a child view without worrying what the
1330
+ parent view's stylesheet is.
1331
+
1332
+ Caveat! If you implement a class that includes `Teacup::Layout`, you can assign
1333
+ it a `stylesheet`. *That* stylesheet will be used by views created using
1334
+ `layout` or `subview` even though your class is probably not part of the
1335
+ responder chain. Saying that `UIView` inherits its `stylesheet` from the
1336
+ responder chain is not accurate; it actually uses `teacup_responder`, which
1337
+ defaults to `nextResponder`, but it is assigned to whatever object calls the
1338
+ `layout` method on the view.
1339
+
1340
+ ------
1341
+
1342
+ If you use `Teacup::Appearance` but it is not styling the first screen of your
1343
+ app (but, strangely, *does* style all other screens), try calling
1344
+ `Teacup::Appearance.apply` before creating you create the `rootViewController`
1345
+ (in your `AppDelegate`)..
1346
+
1347
+ ------
493
1348
 
494
1349
  The Dummy
495
1350
  ---------
@@ -500,30 +1355,28 @@ If you get an error that looks like this:
500
1355
  precompiled. Make sure you properly link with the framework or library that
501
1356
  defines this message.
502
1357
 
503
- You need to add your method to dummy.rb. This is a compiler issue, nothing we
504
- can do about it except build up a huge dummy.rb file that has just about every
505
- method that you would want to style.
1358
+ You probably need to add your method to [dummy.rb][]. This is a compiler issue,
1359
+ nothing we can do about it except build up a huge dummy.rb file that has just
1360
+ about every method that you would want to style.
506
1361
 
507
- Example
508
- =======
1362
+ # Teacup is a Community Project!
509
1363
 
510
- The error above was from trying to style the
511
- `UIActivityIndicatorView#hidesWhenStopped` property. Make a new `Dummy` class,
512
- subclass the class *where the method is defined* (not a subclass, please) and
513
- add that method to a `dummy` method. You should assign it `nil` so that the
514
- method signature looks right. And you should mark it private, too.
1364
+ Teacup was born out of the #rubymotion irc chatroom in the early days of
1365
+ RubyMotion. Its design, direction, and priorities are all up for discussion!
515
1366
 
516
- ```ruby
517
- class DummyActivityIndicatorView < UIActivityIndicatorView
518
- private
519
- def dummy
520
- setHidesWhenStopped(nil)
521
- end
522
- end
523
- ```
1367
+ I'm [Colin T.A. Gray][colinta], the maintainer of the Teacup project. I hope this
1368
+ tool helps you build great apps!
524
1369
 
525
- Bugs
526
- ----
1370
+ [advanced]: https://github.com/rubymotion/teacup/#advanced-teacup-tricks
1371
+ [calculations]: https://github.com/rubymotion/teacup/#frame-calculations
1372
+ [dummy.rb]: https://github.com/rubymotion/teacup/tree/master/lib/dummy.rb
1373
+ [other-handlers]: https://github.com/rubymotion/teacup/tree/master/lib/teacup/z_core_extensions/z_handlers.rb
527
1374
 
528
- Please report any bugs you find with our source at the
529
- [Issues](https://github.com/rubymotion/teacup/issues) page.
1375
+ [Pixate]: http://www.pixate.com
1376
+ [NUI]: https://github.com/tombenner/nui
1377
+ [geomotion]: https://github.com/clayallsopp/geomotion
1378
+ [UIAppearance]: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAppearance_Protocol/Reference/Reference.html#//apple_ref/occ/intf/UIAppearance
1379
+ [motion-layout]: https://github.com/qrush/motion-layout
1380
+ [sweettea]: https://github.com/colinta/sweettea
1381
+ [qrush]: https://github.com/qrush
1382
+ [colinta]: https://github.com/colinta