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