Last active
October 17, 2016 14:10
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: [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