Skip to content

Instantly share code, notes, and snippets.

@SmatchyLaPaglia
Last active October 17, 2016 14:10
Show Gist options
  • Save SmatchyLaPaglia/dd8fd9f53edb9c7bbf7662c8f77dc959 to your computer and use it in GitHub Desktop.
Save SmatchyLaPaglia/dd8fd9f53edb9c7bbf7662c8f77dc959 to your computer and use it in GitHub Desktop.
A Swift 2 XCode Playground demonstrating the ViewSpec protocol for rapidly generating UI elements. Uses some custom extensions that are not included, so almost certainly won't run in a standard Playground. Also requires an image named "puppy1.jpg" to be in the Resources folder.
//: [Previous](@previous)
import Foundation
import XCPlayground
import UIKit
let liveVC = UIViewController()
XCPlaygroundPage.currentPage.liveView = liveVC
/** View specification protocol. Can be used to create a UIView that has the given specs.*/
public protocol ViewSpec {
var frame: CGRect? { get set }
var backgroundColor: UIColor? { get set }
var contentMode: UIViewContentMode? { get set }
var cornerRadius: CGFloat? { get set }
var blurStyle: UIBlurEffectStyle? { get set }
var text: String? { get set }
var textColor: UIColor? { get set }
var fontSize: CGFloat? { get set }
var imageName: String? { get set }
var subviewSpecs: [ViewSpec]? { get set }
func build()->UIView
}
/** ViewSpec extension that creates a UIView.*/
extension ViewSpec {
/** Build a view from the spec. Only defined parameters are applied. If no frame is defined, a default frame of (0, 0, 200, 200) is used. Always returns a UIView, but if text is defined, it is actually a UILabel under the hood. If imageName is defined, it's a UIImageView. If both are defined, it returns a UIView that has a UILabel and UIImageView in a default arrangement: centered horizontally and stacked vertically, image on top. For more precise layout control, define each element as its own ViewSpec in subviewSpecs.*/
public func build()->UIView {
//Copy self to avoid needing a mutating func.
var values = self
//Declare a UIView and assign it the given frame or a default frame.
var mainView = UIView()
if let frm = frame {
mainView.frame = frm
} else {
mainView.frame = CGRectMake(0, 0, 200, 200)
}
var label: UILabel?
var imageView: UIImageView?
//If text is set, make the label = UILabel with it.
if let text = values.text {
label = UILabel()
label!.text = text
label!.textAlignment = .Center
if let txColor = textColor {
label!.textColor = txColor
}
if let fsize = fontSize {
label!.font = label!.font.fontWithSize(fsize)
}
label!.contentMode =? contentMode
}
//If imageName is set, make the imageView = UIImageView with it.
if let imageName = imageName {
let image = UIImage(named: imageName)
imageView = UIImageView(image: image)
imageView!.contentMode =? contentMode
}
//If both a label and an imageView have been made, make them subviews with default positioning: centered horizontally and stacked vertically, image on top.
if label != nil && imageView != nil {
//Give the image most of the frame height.
let imageHeight = frame!.height / 5 * 3.85
imageView?.frame = CGRectMake(0, 0, frame!.width, imageHeight)
//Give the label the rest.
let labelHeight = frame!.height - imageHeight
label?.frame = CGRectMake(0, imageHeight, frame!.width, labelHeight)
mainView.layoutVerticallyAtCenter([imageView!, label!])
} else {
//If either label or imageView is non-nil, assign it to step in as mainView.
mainView =? label
mainView =? imageView
}
//Apply the values that can exist on every UIView.
mainView.frame =? values.frame
mainView.backgroundColor =? values.backgroundColor
mainView.contentMode =? contentMode
if let subs = values.subviewSpecs {
subs.forEach({
let subCopy = $0
let newSubview = subCopy.build()
mainView.addSubview(newSubview)
})
}
//Add a blur effect behind everything, if defined.
if let bStyle = blurStyle {
let existingMain = mainView
existingMain.backgroundColor = nil
mainView = UIVisualEffectView(effect: UIBlurEffect(style: bStyle))
mainView.frame = existingMain.frame
existingMain.frame = existingMain.frame.atOrigin(0, 0)
mainView.addSubview(existingMain)
}
//Round corners, if defined.
if let cr = values.cornerRadius {
mainView.layer.cornerRadius = cr
mainView.clipsToBounds = true
}
//Return the modified view.
return mainView
}
}
/** Standard ViewSpec implementation.*/
public struct ViewSpecStruct: ViewSpec {
public var frame: CGRect?
public var backgroundColor: UIColor?
public var contentMode: UIViewContentMode?
public var cornerRadius: CGFloat?
public var blurStyle: UIBlurEffectStyle?
public var text: String?
public var textColor: UIColor?
public var fontSize: CGFloat?
public var imageName: String?
public var subviewSpecs: [ViewSpec]?
public init(frame: CGRect? = nil,
backgroundColor: UIColor? = nil,
contentMode: UIViewContentMode? = nil,
cornerRadius: CGFloat? = nil,
blurStyle: UIBlurEffectStyle? = nil,
text: String? = nil,
textColor: UIColor? = nil,
fontSize: CGFloat? = nil,
imageName: String? = nil,
subviewSpecs: [ViewSpec]? = nil) {
self.frame =? frame
self.backgroundColor =? backgroundColor
self.contentMode =? contentMode
self.cornerRadius =? cornerRadius
self.blurStyle =? blurStyle
self.text =? text
self.textColor =? textColor
self.fontSize =? fontSize
self.imageName =? imageName
self.subviewSpecs =? subviewSpecs
}
}
/** Abstraction of view constants, to allow sizes to define themselves relatively. */
let VC_WIDTH: CGFloat = liveVC.view.width
let BLOCK_SIDE = VC_WIDTH / 2.5
let BLOCK_FRAME = CGRectMake(0, 0, BLOCK_SIDE, BLOCK_SIDE)
let STANDARD_INCREMENT = VC_WIDTH / 28
let SMALL_BLOCK_SIDE = (BLOCK_SIDE / 2) - (STANDARD_INCREMENT * 1.5)
let SMALL_FRAME = CGRectMake(0, 0, SMALL_BLOCK_SIDE, SMALL_BLOCK_SIDE)
let COLUMN_1_X = STANDARD_INCREMENT
let COLUMN_2_X = (STANDARD_INCREMENT * 2) + BLOCK_SIDE
let ROW_1_Y = STANDARD_INCREMENT
let ROW_2_Y = ROW_1_Y + STANDARD_INCREMENT + BLOCK_SIDE
let SMALL_BLOCK_X = STANDARD_INCREMENT
let SMALL_ROW_1_Y = STANDARD_INCREMENT
let SMALL_ROW_2_Y = (STANDARD_INCREMENT * 2) + SMALL_BLOCK_SIDE
let SMALL_ROW_WIDTH = BLOCK_SIDE - (STANDARD_INCREMENT * 2)
/** Create a simple red box using a ViewSpecStruct.*/
let redBoxSpec =
ViewSpecStruct(frame: BLOCK_FRAME.atOrigin(COLUMN_1_X, ROW_1_Y),
backgroundColor: .redColor(),
cornerRadius: STANDARD_INCREMENT)
liveVC.view.addSubview(redBoxSpec.build())
/** Create a blue box with subviews using a ViewSpecStruct.*/
//small blocks
let orangeSpec =
ViewSpecStruct(frame: SMALL_FRAME.atOrigin(SMALL_BLOCK_X, SMALL_ROW_1_Y),
backgroundColor: .orangeColor(),
cornerRadius: STANDARD_INCREMENT)
let yellowSpec =
ViewSpecStruct(frame: SMALL_FRAME.atOrigin(SMALL_BLOCK_X, SMALL_ROW_2_Y),
backgroundColor: .yellowColor(),
cornerRadius: STANDARD_INCREMENT)
//big block
let blueBoxSpec =
ViewSpecStruct(frame: BLOCK_FRAME.atOrigin(COLUMN_2_X, ROW_1_Y),
backgroundColor: .blueColor(),
cornerRadius: STANDARD_INCREMENT,
subviewSpecs: [orangeSpec, yellowSpec])
liveVC.view.addSubview(blueBoxSpec.build())
/** Using seperate ViewSpecStructs for each, create a green box containing a label and an image.*/
//image
let labelSpec =
ViewSpecStruct(frame: CGRectMake(SMALL_BLOCK_X, SMALL_ROW_1_Y, SMALL_ROW_WIDTH, SMALL_BLOCK_SIDE),
backgroundColor: .orangeColor(),
cornerRadius: STANDARD_INCREMENT,
text: "test text",
fontSize: STANDARD_INCREMENT * 2)
let imageSpec =
ViewSpecStruct(frame: CGRectMake(SMALL_BLOCK_X, SMALL_ROW_2_Y, SMALL_ROW_WIDTH, SMALL_BLOCK_SIDE),
backgroundColor: .yellowColor(),
cornerRadius: STANDARD_INCREMENT,
imageName: "puppy1.jpg",
contentMode: .ScaleAspectFill)
let greenBoxSpec =
ViewSpecStruct(frame: BLOCK_FRAME.atOrigin(COLUMN_1_X, ROW_2_Y),
backgroundColor: .greenColor(),
cornerRadius: STANDARD_INCREMENT,
subviewSpecs: [labelSpec, imageSpec])
liveVC.view.addSubview(greenBoxSpec.build())
/** Create a purple box spec that includes both a text value and an imageName but no subviews. */
let purpleBoxSpec =
ViewSpecStruct(frame: BLOCK_FRAME.atOrigin(COLUMN_2_X, ROW_2_Y),
backgroundColor: .purpleColor(),
cornerRadius: STANDARD_INCREMENT,
text: "test text",
fontSize: STANDARD_INCREMENT * 1.5,
textColor: .whiteColor(),
imageName: "puppy1.jpg",
contentMode: .ScaleAspectFill)
liveVC.view.addSubview(purpleBoxSpec.build())
/** Create each of those again but as a BlurView.*/
func recreateAllAsBlurView() {
//The formerly-red view.
var simpleBlurSpec = redBoxSpec
simpleBlurSpec.frame?.origin.y = (BLOCK_SIDE * 2) + (STANDARD_INCREMENT * 3)
simpleBlurSpec.blurStyle = .ExtraLight
liveVC.view.addSubview(simpleBlurSpec.build())
//The formerly-blue view.
var blurWithSimpleSubview = blueBoxSpec
blurWithSimpleSubview.frame?.origin.y = simpleBlurSpec.frame!.origin.y
blurWithSimpleSubview.blurStyle = .ExtraLight
liveVC.view.addSubview(blurWithSimpleSubview.build())
//The formerly-green view.
var blurWithLabelAndImageUsingSubviewSpecs = greenBoxSpec
blurWithLabelAndImageUsingSubviewSpecs.frame?.origin.y = simpleBlurSpec.frame!.origin.y + BLOCK_SIDE + STANDARD_INCREMENT
blurWithLabelAndImageUsingSubviewSpecs.blurStyle = .ExtraLight
liveVC.view.addSubview(blurWithLabelAndImageUsingSubviewSpecs.build())
//The formerly-purple view.
var blurWithLabelAndImageNotFromSubviewSpecs = purpleBoxSpec
blurWithLabelAndImageNotFromSubviewSpecs.frame?.origin.y = simpleBlurSpec.frame!.origin.y + BLOCK_SIDE + STANDARD_INCREMENT
blurWithLabelAndImageNotFromSubviewSpecs.blurStyle = .ExtraLight
liveVC.view.addSubview(
blurWithLabelAndImageNotFromSubviewSpecs.build())
}
recreateAllAsBlurView()
//: [Next](@next)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment