Doing It All in UIAppearance
Exploring the Line Between Possible and a Good Idea
In putting together code examples for my talk on making working with UITableView swift-ier, I'm attempting to remove all of the styling code from the example view controllers, and that led to a lot of testing out just how much I can set up in UIAppearance proxies without breaking everything.
private extension AppStyle {
func setupAppearance() {
setupNavBarAppearance()
setupTableViewCellAppearance()
setupTableAppearance()
}
func setupNavBarAppearance() {
let navBarAppearance = UINavigationBar.appearanceWhenContainedInInstancesOfClasses([NavigationController.self])
navBarAppearance.translucent = false
navBarAppearance.shadowImage = UIImage.solidImage(color: colors.tintColor)
let barImage = UIImage.solidImage(color: colors.navigationBackgroundColor)
navBarAppearance.setBackgroundImage(barImage, forBarMetrics: .Default)
let textStyle: [String:AnyObject] = [
NSForegroundColorAttributeName: colors.navbarTextColor
]
navBarAppearance.titleTextAttributes = textStyle
}
func setupTableViewCellAppearance() {
let cellAppearance = UITableViewCell.appearanceWhenContainedInInstancesOfClasses([NavigationController.self])
cellAppearance.backgroundColor = colors.backgroundColor
let selectedView = UIView()
selectedView.backgroundColor = colors.selectedBackgroundColor
cellAppearance.selectedBackgroundView = selectedView
cellAppearance.forceInsets(UIEdgeInsets())
let nestedClasses: [AnyObject.Type] = [UITableViewCell.self, NavigationController.self]
let cellLabelAppearance = UILabel.appearanceWhenContainedInInstancesOfClasses(nestedClasses)
cellLabelAppearance.forceFont(UIFont.preferredFontForTextStyle(UIFontTextStyleBody))
cellLabelAppearance.highlightedTextColor = colors.selectedTextColor
cellLabelAppearance.forceTextColor(colors.textColor)
}
func setupTableAppearance() {
let tableViewAppearance = UITableView.appearanceWhenContainedInInstancesOfClasses([NavigationController.self])
tableViewAppearance.backgroundColor = colors.backgroundColor
tableViewAppearance.tableFooterView = UIView()
tableViewAppearance.separatorColor = colors.textColor
tableViewAppearance.forceInsets(UIEdgeInsets())
}
}
//MARK: - Functions to force configuration of properties not exposed to UIApperarance by default
private extension UILabel {
@objc func forceTextColor(color: UIColor) {
textColor = color
}
@objc func forceFont(font: UIFont) {
self.font = font
}
}
private extension UITableView {
@objc func forceInsets(edgeInsets: UIEdgeInsets) {
separatorInset = edgeInsets
layoutMargins = edgeInsets
}
}
private extension UITableViewCell {
@objc func forceInsets(edgeInsets: UIEdgeInsets) {
separatorInset = edgeInsets
layoutMargins = edgeInsets
}
}
This is by no means a complete style for my application, but it's getting styled text into the table view cells, custom cell selection highlights, making the table cell insets extend to full bleed, and a few other things.
The first thing to notice is that pretty much everything is only styled if it's wrapped in a NavigationController
which is a custom UINavigationController
subclass. That's because UIAppearance
can bleed over into some but not all elements of certain system view controllers like the contacts picker and email composer.
After that, notice all of the private extensions, those are an end-run around properties that aren't exposed to UIAppearance
. Those are a big part of why I'm being very careful with just where I set my proxies. So far I've run into issues when forcing text colors on labels too broadly doing strange things to the system navigation bar animations, and I'm sure I'll run into more interesting edge cases along the way. If I can make this work for the entire table demo project, I'm hoping to pull the pattern into actual production projects to reduce certain bits of repetitive and mistake prone code, particularly things like setting the cell insets for table views.