iOS layouts for web developers #4 - CSS properties replacements
This is the fourth post in the iOS layouts for web developers series. The previous ones were about the controls, control positioning and managing the appearance. This time something more lightweight, I hope. We’ll go through various visual aspects of the controls and see how we can set it up, compared to CSS. Let’s start with the basics - margin and padding.
Box model & controlling controls spacing
There was a lot of confusion in the past in the web world around the box model, i.e. whether the size of an element should be calculated including or excluding its padding and border sizes. Finally, by default, the HTML element size excludes padding and borders (unless we decide to change that using box-sizing
CSS property). How does it work in iOS?
Paddings and margins are not useful at all within frame-based positioning - you place the content where you place it, full stop. In the constraints-based layouts (auto layout), though, paddings and margins make a lot of sense. In most cases in iOS these concepts are known as inset and outset and are actually a single value - we get the margin (outset) effect when we use negative value, padding (inset) for positive.
CSS padding <—> inset, -outset
CSS margin <—> outset, -inset
In the context of auto layout, most of the controls has some natural margins (insets) set that are used automatically whenever we’re constraining to the control’s leading space (contrary to the edge). From iOS 8, new UIView
property was introduced to control the inset/outset value - layoutMargins
. It allows to control the inset (or outset, if value is negative) for each side of the control separately.
CSS padding, margin <—>
UIView
’slayoutMargins
property
Next thing in the box model is the border. There is no such property directly available on UIView
(except for UITextField
’s borderStyle
property). In order to draw a border around the view, we need to rely on the lower level CALayer
and use its borderWidth
and borderColor
properties.
CSS
border-color
<—>layer.borderColor
CSS
border-width
<—>layer.borderWidth
So, how about the box model? In iOS, the size comes from the frame property, either assigned or calculated with constraints, regardless of the inset (layoutMargins
) values or border size. Both margins and borders are drawn within a frame, so the box model on iOS actually is the same like on IE6, not like in the web nowadays.
box-sizing: border-box
<—> the default box model in iOS
the web’s default
box-sizing: content-box
<—> N/A in iOS
There is one more thing specific to iOS layouts and controls sizing that should be mentioned here. Apart from the frame, the view has the sibling bounds
property. While frame is about the control’s size and location within the superview’s coordinate system, bounds are the values within the control’s own system. It means that in most cases the control’s origin is (0,0), but it changes with rotation, scaling or translations. It makes sense to use the frame
property from the context of superview when dealing with its children (when our code runs from the top - like from the view controller - looking down in the view hierarchy). On the other hand, it makes sense to use bounds
when dealing with the control’s own view (for example when we’re writing the custom control), as bounds are decoupled from the specifics about where and how the view is rendered at the given point of time.
Other positioning aspects
Overflow is used in the web to control what happens when the content in the element is larger than the element’s expected dimensions. When no dimensions is set, the elements grow with the content, so no overflow applied. Otherwise, one of the tree options applies - either the excess content is visible outside the element’s dimensions (overflow: visible
, the default), cut off and invisible (overflow: hidden
) or the scrollbars are added to the side of a control to enable scrolling independent from the main window (overflow: scroll
and overflow: auto
). The two first options are represented directly in iOS with clipsToBounds
property of UIView
- when set to YES
, the content is clipped (cut off), the default is NO
, like in the web. There is no direct replacement for automatic scrolls - in case we need that, we need to take care of it manually, creating an additional UIScrollView
.
overflow: visible
<—>clipsToBounds = NO
overflow: hidden
<—>clipsToBounds = YES
overflow: scroll
andoverflow: auto
<—>UIScrollView
Z-Index. The position of a control on the Z-axis is quite important when we use the absolute positioning (or frame-based positioning in iOS), as controls may arbitrarily cover and be covered by other controls and relying on the order of setting up the control is too brittle to be trusted. That’s why we use z-index
value in CSS. In iOS, we have the same thing hidden in the CALayer
as zPosition
. To avoid the need to go to that level and deal with the fact that view hierarchy is actually not the same as layers hierarchy, we should be using [superview bringSubviewToFront:subview]
and [superview:sendSubviewToBack:subview]
to achieve the same in imperative, but more powerful manner.
z-index
CSS property <—>layer.zPosition
, exposed throughbringSubviewToFront
andsendSubviewToBack
Most of the display CSS property, as known in the web, doesn’t have a lot of sense in iOS layouts, as there is no distinction between inline and block elements and there’s no document flow, as we’ve already learned. The only display
value that makes any sense for iOS is none
that allows to hide the element without actually removing it from the view. The UIView
’s property for this purpose is conveniently called hidden
.
display: none
<—>hidden
Backgrounds
In iOS, there is concise backgroundColor
property that encapsulates most of background styling, much like background
in CSS. At a first glance, it seems to be simple and intended for setting plain-colored backgrounds. But it’s actually much more powerful than that. UIColor
class can be initialized using colorWithPatternImage
method that takes an UIImage
instance and the “color” it creates is actually an image that can fill our view’s background with repetition - however strange it might sound. In case we need more control over stretching, repetition etc., we need to defer to “manual” way. We need to add UIImageView
as a subview to our view and ensure it is displayed at the bottom on Z-axis.
background-color
CSS property <—>backgroundColor
with plain color
background-image
CSS property withbackground-repeat: repeat
<—>backgroundColor
withUIColor
created throughcolorWithPatternImage
background-image
CSS property with stretching or custom positioning <—>UIImageView
subview
Moreover, for a reason unknown to me, UITextField
has background
property specialized for setting and stretching the background image for a text field, so there’s no need to fall back to adding a subview in this case.
background-image
CSS property with stretching on <input> <—>background
onUITextField
Text properties
As we discussed in the first post the textual content in iOS may appear only within specialized controls like UILabel
or controls supporting NSAttributedString
textual values.
NSAttributesString
has plenty of options for customizing text appearance, ranging from colors, through underlines and strikethroughs, to effects such as strokes and shadows. Some of the properties are encapsulated within NSParagraphStyle
available under NSParagraphStyleAttributeName
key in the attributed string.
font
CSS properties combined <—>NSAttributedString
withNSFontAttributeName
text-align
CSS property <—>alignment
property ofNSParagraphStyleAttributeName
value inNSAttributedString
text-indent
CSS property <—>firstLineHeadIntent
property ofNSParagraphStyleAttributeName
value inNSAttributedString
padding-left
CSS property <—>headIndent
property ofNSParagraphStyleAttributeName
value inNSAttributedString
padding-right
CSS property <—>tailIndent
property ofNSParagraphStyleAttributeName
value inNSAttributedString
line-height
CSS property <—>lineSpacing
property ofNSParagraphStyleAttributeName
value inNSAttributedString
word-wrap
CSS property <—>lineBreakMode
property ofNSParagraphStyleAttributeName
value inNSAttributedString
color
CSS property <—>NSAttributedString
withNSForegroundColorAttributeName
background-color
CSS property <—>NSAttributedString
withNSBackgroundColorAttributeName
text-decoration: line-through
CSS value <—>NSAttributedString
withNSStrikethroughStyleAttributeName
set toNSUnderlineStyleSingle
text-decoration: underline
CSS value <—>NSAttributedString
withNSUnderlineStyleAttributeName
set toNSUnderlineStyleSingle
text-stroke
CSS property (not widely supported yet) <—>NSAttributedString
withNSStrokeColorAttributeName
andNSStrokeWidthAttributeName
text-shadow
CSS property <—>NSAttributedString
withNSShadowAttributeName
UILabel
, the control dedicated for text display, supports attributedText
, so can handle all of the features for NSAttributedString
, but additionally exposes some properties that affect the whole label content, like textAlignment
.
font
CSS properties combined <—>UILabel
’sfont
color
CSS property <—>UILabel
’stextColor
text-align
CSS property <—>UILabel
’stextAlignment
word-wrap
CSS property <—>UILabel
’slineBreakMode
Fancy effects language: en
Some of the “fancy” text effects like text shadows or strokes are available through NSAttributedString
we’ve just gone through. There are few more available, though. I think “fancy effects" is not a perfect heading, anyway, but I needed a place to describe few more properties that are available in iOS and are well-known in CSS but I couldn’t fit it anywhere else. So here we go:
opacity
CSS property <—>alpha
animation
CSS properties family <—> whole bunch ofUIView
’s specialized methods
border-radius
CSS property for <input> <—>UITextField
’sborderStyle
set toUITextBorderStyleRoundedRect
border-radius
CSS property in general <—>layer.cornerRadius
That’s all I found useful and worth mentioning here, but of course the full list of styling-related features of UIKit, and especially when going down to CALayers is vast, likewise the full list of features in CSS, so I don’t think it’s even feasible to cover everything here.
Next time I’ll conclude the series with the post about events handling.