11
\$\begingroup\$

Assuming an app has many display styles, fonts, colors, etc., and that we never want to hardcode values, I've created an AppVariables object that houses properties for all of the necessary styles in my app:

class AppVariables: NSObject {
 /**
 * Fonts
 */
 let UIFontPrimary10 = UIFont(name: "Heiti TC", size: 10)
 let UIFontPrimary12 = UIFont(name: "Heiti TC", size: 12)
 let UIFontPrimary14 = UIFont(name: "Heiti TC", size: 14)
 var attribsFont10: NSDictionary!
 var attribsFont12: NSDictionary!
 var attribsFont14: NSDictionary!
 var paragraphStyleFont10 = NSMutableParagraphStyle()
 var paragraphStyleFont12 = NSMutableParagraphStyle()
 var paragraphStyleFont14 = NSMutableParagraphStyle()
 /**
 * Colors
 */
 let UIColorWhiteOff = UIColor(red: 250.0/255.0, green: 250.0/255.0, blue: 250.0/255.0, alpha: 1)
 let UIColorPurple = UIColor(red: 134.0/255.0, green: 129.0/255.0, blue: 204.0/255.0, alpha: 1)
 let UIColorPurpleLight = UIColor(red: 178.0/255.0, green: 176.0/255.0, blue: 217.0/255.0, alpha: 1)
 let UIColorBlueDark = UIColor(red: 40.0/255.0, green: 35.0/255.0, blue: 61.0/255.0, alpha: 1)
 let UIColorBlueDarkAlpha6 = UIColor(red: 40.0/255.0, green: 35.0/255.0, blue: 61.0/255.0, alpha: 0.6)
 let UIColorBlueDarkAlpha95 = UIColor(red: 40.0/255.0, green: 35.0/255.0, blue: 61.0/255.0, alpha: 0.95)
 /**
 * Dimensions and margins
 */
 var screenWidth: CGFloat!
 var screenHeight: CGFloat!
 var viewPadding: CGFloat!
 var viewPaddingSmall: CGFloat!
 var viewWidth: CGFloat!
 init() {
 super.init()
 self.paragraphStyleFont10.lineSpacing = 4
 self.paragraphStyleFont12.lineSpacing = 5
 self.paragraphStyleFont14.lineSpacing = 6
 self.attribsFont10 = [NSFontAttributeName: self.UIFontPrimary10, NSParagraphStyleAttributeName: self.paragraphStyleFont10]
 self.attribsFont12 = [NSFontAttributeName: self.UIFontPrimary12, NSParagraphStyleAttributeName: self.paragraphStyleFont12]
 self.attribsFont14 = [NSFontAttributeName: self.UIFontPrimary14, NSParagraphStyleAttributeName: self.paragraphStyleFont14]
 self.screenWidth = UIScreen.mainScreen().bounds.width
 self.screenHeight = UIScreen.mainScreen().bounds.height
 self.viewPadding = (screenWidth * 0.1) / 2
 self.viewPaddingSmall = 10.0
 self.viewWidth = screenWidth - (viewPadding * 2)
 }
}

Assuming I've instantiated a copy of AppVariables into a property of AppDelegate, I can use them like so:

class Foo {
 func bar() {
 var someLabel = UILabel()
 someLabel.textColor = self.appDelegate.appVariables.UIColorPurpleLight
 }
}

Is there a better way of doing this? It seems like the simplest approach, since these are more than mere constants, but I'm open to thoughts and opinions.

200_success
145k22 gold badges190 silver badges478 bronze badges
asked Jun 18, 2014 at 15:29
\$\endgroup\$
7
  • \$\begingroup\$ Why not static so you don't create them everytime? \$\endgroup\$ Commented Jun 19, 2014 at 0:34
  • \$\begingroup\$ @MarcoAcierno can you explain that a bit more? How do we define static in Swift? Also, why would they be created every time (I assume you mean each time a class uses .appVariables property) if that property is only initialized once into appDelegate? \$\endgroup\$ Commented Jun 19, 2014 at 16:13
  • \$\begingroup\$ @MarcoAcierno swift does not support class-level variables currently. \$\endgroup\$ Commented Jun 20, 2014 at 21:37
  • 1
    \$\begingroup\$ I don't have Xcode 6 yet, so I haven't been able to play around yet, but 1) why don't you just create global variables? 2) I think this would be better as a struct or enum... though I'm 100% certain you could get a Swift enum to handle this (I think you can though). \$\endgroup\$ Commented Jun 20, 2014 at 22:59
  • \$\begingroup\$ I agree with @nhgrif; I'm pretty sure structs and enums would be a better fit for a lot of this stuff - but I'm certainly not a Swift expert yet \$\endgroup\$ Commented Jun 22, 2014 at 14:26

1 Answer 1

4
\$\begingroup\$
  1. I think that you would be better off using static methods on a struct or class. This removes the need for you to access your style through the App Delegate. The App Delegate should only be concerned with handling the delegate callbacks from the OS, storing globally accessible values in it messes with the principle of separation of concerns and creates a spider web of dependencies hurting the reusability and understandability of your code.

  2. Lumping Fonts, Colors, Dimensions, Paragraph styles, etc. all into a single level is not very discoverable. I think it would be better to separate them into their own types.

  3. In my experience a single color is not enough to define a style. Colors almost always come associated with other colors like foreground v.s. background, or normal v.s. highlighted.

  4. It doesn't make sense to create multiple constants for a single font of different sizes. Better to have a method that takes in a size and returns the special font with the given size.

I recently started creating a collection of "style" classes for myself. This is what I did for colors:

extension UIColor {
 convenience init(hex : Int) {
 let blue = CGFloat(hex & 0xFF)
 let green = CGFloat((hex >> 8) & 0xFF)
 let red = CGFloat((hex >> 16) & 0xFF)
 self.init(red: red / 255.0, green: green / 255.0, blue: blue / 255.0, alpha: 1)
 }
}
struct DepthColor {
 enum Depth {
 case Foreground, Background
 }
 let foreground: UIColor
 let background: UIColor
 init(foreground: Int, background: Int) {
 self.foreground = UIColor(hex: foreground)
 self.background = UIColor(hex: background)
 }
 init() {
 self.foreground = UIColor(hex: 0xFFFFFF)
 self.background = UIColor(hex: 0)
 }
 func colorForDepth(depth : Depth) -> UIColor {
 switch(depth) {
 case .Foreground:
 return self.foreground
 case .Background:
 return self.background
 }
 }
}
struct StyleColor {
 enum Purpose {
 case Normal, Highlighted, Disabled
 }
 let normal : DepthColor
 let highlighted : DepthColor
 let disabled : DepthColor
 init(normal: DepthColor = DepthColor(), highlighted: DepthColor = DepthColor(), disabled: DepthColor = DepthColor()) {
 self.normal = normal
 self.highlighted = highlighted
 self.disabled = disabled
 }
 func depthColorForPurpose(purpose : Purpose) -> DepthColor {
 switch(purpose) {
 case .Normal:
 return self.normal
 case .Highlighted:
 return self.highlighted
 case .Disabled:
 return self.disabled
 }
 }
 func color(_ purpose : Purpose = .Normal, _ depth: DepthColor.Depth = .Foreground) -> UIColor {
 return self.depthColorForPurpose(purpose).colorForDepth(depth)
 }
 static func positiveAction() -> StyleColor {
 return StyleColor(
 normal: DepthColor(foreground: 0x45A332, background: 0xFFFFFF),
 highlighted: DepthColor(foreground: 0x45A332, background: 0xFFFFFF),
 disabled: DepthColor(foreground: 0x888888, background: 0xFFFFFF)
 )
 }
 static func deleteAction() -> StyleColor {
 return StyleColor(
 normal: DepthColor(foreground: 0xF31717, background: 0xFFFFFF),
 highlighted: DepthColor(foreground: 0xD91414, background: 0xFFFFFF),
 disabled: DepthColor(foreground: 0x888888, background: 0xFFFFFF)
 )
 }
 static func neutralAction() -> StyleColor {
 return StyleColor(
 normal: DepthColor(foreground: 0x666666, background: 0xFFFFFF),
 highlighted: DepthColor(foreground: 0x555555, background: 0xFFFFFF),
 disabled: DepthColor(foreground: 0x888888, background: 0xFFFFFF)
 )
 }
}

I can then get a color from a style like so:

var color = StyleColor.positiveAction().color()
color = StyleColor.positiveAction().color(.Highlighted)
color = StyleColor.positiveAction().color(.Highlighted, .Background)

This also allowed me to create subclasses of common UIKit components that allow me to simply set a StyleColor on them to fully configure them to a style:

class StyleButton: UIButton {
 var style: StyleColor = StyleColor() {
 didSet {
 self._configureView()
 }
 }
 init(coder aDecoder: NSCoder!, style : StyleColor) {
 self.style = style
 super.init(coder: aDecoder)
 self._configureView()
 }
 init() {
 super.init(frame: CGRect())
 self._configureView()
 }
 func _configureView() {
 self.showsTouchWhenHighlighted = false
 self.adjustsImageWhenHighlighted = false
 self.adjustsImageWhenDisabled = false
 self.reversesTitleShadowWhenHighlighted = false
 self.setTitleColor(style.color(.Normal, .Background), forState: .Normal)
 self.setTitleColor(style.color(.Disabled, .Background), forState: .Disabled)
 self.setTitleColor(style.color(.Highlighted, .Background), forState: .Highlighted)
 self.setBackgroundImage(UIImage(color: style.color()), forState: .Normal)
 self.setBackgroundImage(UIImage(color: style.color(.Disabled)), forState: .Disabled)
 self.setBackgroundImage(UIImage(color: style.color(.Highlighted)), forState: .Highlighted)
 }
}

As I need it, I will probably build similar structures for other types of styles and potentially some higher level style classes like StyleText that includes a StyleColor, StyleFont, and potentially StyleDimensions for padding.

answered Jul 26, 2014 at 21:56
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.