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