README.md in rm-extensions-0.1.5 vs README.md in rm-extensions-0.1.6

- old
+ new

@@ -1,8 +1,205 @@ RMExtensions ----------------- -#### Extensions and helpers for dealing with various areas of rubymotion. +#### Extensions and helpers for dealing with various areas of rubymotion and iOS. + +## Equation-style AutoLayout Constraints + +AutoLayout is a great way to lay out views, but writing constraints manually is +confusing and verbose. + +Using Apple's "Visual Format Language" ASCII-inspired +strings can improve things, but it has drawbacks: **1)** It returns an array +of constraints. This means if you plan on altering one of them later, or removing +one in particular, you would have to loop through all of the constraints, testing +each one to see if its the one you want. **2)** The `options` argument adds additional +constraints. For example, if you specify NSLayoutAttributeCenterY to a horizontal string, +an additional constraint will be added for each view to set their centerY's equal to each +other. This compounds problem #1. The chances you will get an error that the layout system +cannot satisfy your constraints is probably because of these "extra" constraints. **3)** +It can't handle complex constraints, so you end up needing to supplement it with +verbose low-level constraint creation anyway. + +Apple makes note of how constraints can be thought of like a linear equation: + +http://developer.apple.com/library/ios/documentation/AppKit/Reference/NSLayoutConstraint_Class/NSLayoutConstraint/NSLayoutConstraint.html + +**Remember the formula:** + +```ruby +view1.attr1 == view2.attr2 * multiplier + constant @ priority +``` + +**We can actually use this super-simple formula to write ALL of our constraints, simple OR complex!** + +Once you get the hang of the formula and visualization of how the geometry works, it becomes easy to create complex layouts with little +effort. And, in my opinion, its only slightly more verbose than the visual format language, but much clearer, and you only end up +with the exact constraints you want. + +Available values for `attr1` and `attr2` are: + +- left +- right +- top +- bottom +- leading +- trailing +- width +- height +- centerX +- centerY +- baseline + +Available `relation` values are: + +- == +- <= +- >= + +Available `priority` values are: + +- required (1000 is the default) +- high (750) +- low (250) +- fit (50) +- or, you can use your own value between 1-1000 + + +### Examples + +Here is a real example. The Layout instance is created just like the motion-layout gem. `layout.view` sets the view +that will act as the 'superview' to the views set in `layout.subviews`. Thats where the similarities end with +motion-layout. With RMExtensions::Layout, there are two methods: `eq` and `eqs`, short for equation and equations. + +- `layout.eq` takes one string, and returns *one constraint* +- `layout.eqs` takes one string, assumes multiple constraints are separated by newlines, and returns an *array of constraints* + +```ruby +RMExtensions::Layout.new do |layout| + layout.view view + layout.subviews({ + "calendar" => calendarView, + "table" => tableView, + "shadow" => line + }) + + layout.eqs %Q{ + calendar.left == 0 + calendar.right == 0 + table.left == 7 + table.right == -7 + shadow.left == 0 + shadow.right == 0 + + calendar.top == 0 + table.top == calendar.bottom + table.bottom == 0 + shadow.top == table.top + } + + @calendar_height_constraint = layout.eq "calendar.height == 0" +end +``` + +Above, **calendar.left == 0** is short for **calendar.left == view.left * 1.0 + 0 @ 1000**. If no view2 is given, the superview ('view') is assumed. +If no multiplier is given, 1.0 is assumed. If no constant is given, 0 is assumed. If no priority is given, "required" (1000) is assumed. +The last constraint is created separately and stored in @calendar_height_constraint, because I want to be able to change the calendar's height +any time I want. + +Here is another example: + +```ruby +RMExtensions::Layout.new do |layout| + layout.view self + layout.subviews({ + "timeLabel" => @timeLabel, + "titleLabel" => @titleLabel, + "trackingImage" => @trackingImage, + "inOutStatusInImage" => @inOutStatusInImage, + "inOutStatusOutImage" => @inOutStatusOutImage, + "plannerImage" => @plannerImage, + "shareButton" => @shareButton, + "cancelledLabel" => @cancelledLabel, + "unreadImage" => @unreadImage + }) + + layout.eqs %Q{ + unreadImage.left == 6 + unreadImage.top == 6 + plannerImage.left == 14 + plannerImage.centerY == 0 + plannerImage.width == 30 + plannerImage.height == 30 + trackingImage.left == timeLabel.right + 5 + inOutStatusOutImage.left == trackingImage.right + 5 + inOutStatusInImage.left == inOutStatusOutImage.right + 5 + timeLabel.left == plannerImage.right + 5 + timeLabel.baseline == plannerImage.bottom + 1 + trackingImage.centerY == timeLabel.centerY + inOutStatusOutImage.centerY == timeLabel.centerY + inOutStatusInImage.centerY == timeLabel.centerY + titleLabel.left == cancelledLabel.right + cancelledLabel.left == plannerImage.right + 5 + titleLabel.top == plannerImage.top - 4 + cancelledLabel.centerY == titleLabel.centerY + shareButton.right == -10 + shareButton.centerY == 0 + titleLabel.resistH == low + shareButton.left >= titleLabel.right + 5 + timeLabel.resistH == low + shareButton.left >= inOutStatusInImage.right + 5 + } + +end +``` + +Keep in mind none of these lines are using the multiplier, and thats OK. I've only needed it on one constraint in my entire app so far, +so don't think its odd if you can't find a use for it. + +There are two special cases at the moment. **titleLabel.resistH == low** is not a "real" constraint. Its a shortcut to +`setContentCompressionResistancePriority`, and since its common when dealing with autolayout, its nice to include it +in our layout code. The same is done for `setContentHuggingPriority`. The full list of "special cases" at the moment: + +- view1.resistH == priority +- view1.resistV == priority +- view1.hugH == priority +- view1.hugV == priority + +"priority" can be one of the values listed earlier, or your own number between 1-1000. + +### Debugging constraints + +- You can include a **?** on any line, and debug output will be printed when that constraint is built: + +```ruby +layout.eqs %Q{ + label.left == photo.right + 5 ? +} +``` + +- Since layout.eqs allows you to write many constraints in one string, and sometimes its nice to +keep comments next to constraints, **comments are allowed**: + +```ruby +layout.eqs %Q{ + commentsCount.width == likesCount.width @ low # the widths of the labels prefer to be the same +} +``` + +### Things to remember + +- Remember you usually want negative constants for right and bottom. For example: label.right == -10 means "label's right should be 10 away from the right side of the superview". But if you accidentally said label.right == 10, you would have created "label's right should be 10 PAST the right side of the superview". It may require you to adjust your thinking. + +- The formula is just shorthand for `constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:`. You should **really** read up on constraints and +understand this method, to fully understand the power and simplicity the shorthand formula gives you. + + + + + + + ## Observation/KVO, Events #### Make observations without needing to clean up/unobserve