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 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