Talk:Wikimedia Apps/Team/iOS/BestPractices
I hope to add a section here on how to improve testability w/ DI and class extensions. BGerstle (WMF) (talk)
UI code responsibilities
editExpected behaviors/contracts
editViews (and related types, like layout implementations) have a few implicit—but expected—behaviors that are usually derived from conventions set by Apple in UIKit or other widely-used open source libraries. The more important and commonly-encountered ones are listed below to help engineers make design decisions about the views they create or modify:
On DRYness and reinventing of wheels
editWhen at all possible, leverage existing APIs (e.g. UIKit) to implement the desired effect, such as alpha
, CALayer
properties, etc. There are many reasons for this:
- Duplicating state with another property requires extra code (read: maintenance cost) to manually maintain consistent state the new property and its "native" counterparts
- Using existing APIs makes code easier to compose and reuse (e.g. code that manipulates frames, transforms, etc. can be composed to create a new "higher order" functionality)
- Leveraging existing APIs allows your component to hook into other abstractions provided by UIKit (e.g. the
+[UIView animateWithDuration:...]
API)
No side effects
editUnless made explicit, changes to one property should not effect others. For example, if I hide a view, then show it again, it should have the same appearance when redisplayed. Here's an example of what not to do with a custom UIView
:
- As a user, configure the view with a specific alpha so it has a desired appearance
- Hide and show the view (animated or not)
- When hidden/shown, the custom view's implementation changes its alpha, thereby overriding my configuration and breaking the "no side effects" contract
Writable properties
editWritable properties on a UI type should be idempotent, lazy, and reactive. Typically this is done by overriding the setter of your new property, checking for a change, and flagging it for later update:
- (void)setProperty:(id)value {
if ([_value isEqual:value]) {
// idempotent, only causes an update if the value is actually different
return;
}
_value = value;
// be reactive, but lazy by using the "setNeedsXXXX" methods, and let the caller decide if
// updates should be strict (by making a subsequent call to xxxxIfNeeded) or lazy (updated at the next runloop iteration)
[self setNeedsLayout]; // or setNeedsDisplay or invalidateLayout if using autolayout
}
Animatable properties
editAs mentioned above, this can sometimes be functionality your view gets "for free" by using APIs that are themselves animatable using the +[UIView animateWithDuration:....]
API. In this case, there's no need to provide anything other than a settable property that can be used within an animation block. However, in cases where you have custom drawing code related to certain properties, you'll often need to use lower-level APIs to make them animatable and provide a suitable high-level abstraction in order to explicitly animate them. objc.io has a great article covering how to implement animations for custom properties using CoreAnimation.[1]