teacup 2.2.2 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9d908853bb431306cc83a100710414f0ec8c06c0
4
- data.tar.gz: f727586d4aa2f2b0c0d8723f69d7b8b09b55b643
3
+ metadata.gz: cfe17cc290b5832599be8594c185cf67699aa667
4
+ data.tar.gz: 6a6c4ac9230c4d1292b5b81feb344035de230a13
5
5
  SHA512:
6
- metadata.gz: 4677483dc33b8a921e5f9f524b3d5d9551c986b296c859b3d927152cdab7aec821e617111b1a7f673663f676f8796146bee0b3e43a5ad8657889fe76cb4ef503
7
- data.tar.gz: f17d8e8c76f28c73b70b0ef5725c716a9d848989b679171f7e522cde37e185b192ddab5e8e9b4ddb2ff5386871a7e81b778f0306a8191993dc220cb0ae476386
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
+ [![Build Status](https://travis-ci.org/colinta/teacup.png)](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