Skip to content

Instantly share code, notes, and snippets.

@DeFrenZ
Created November 17, 2014 11:54
Show Gist options
  • Save DeFrenZ/93bf1b1b24006a956dd7 to your computer and use it in GitHub Desktop.
Save DeFrenZ/93bf1b1b24006a956dd7 to your computer and use it in GitHub Desktop.
FiniteStateLayoutView
//
// FiniteStateLayoutView.swift
//
// Copyright (c) 2014 Davide De Franceschi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
// using a negative value instead of nil because Objective-C (and Interface Builder uses that) don't support Optional types.
private let invalidState = -1
// A UIView subclass that changes it's rendering in Interface Builder depending on its currentState property. The proposed usage is to use it together with a Finite State Machine that controls the possible states and transitions. Using Int as the state identifier instead of an enum for Objective-C (and Interface Builder) compatibility.
class FiniteStateLayoutView: NibDesignable {
// You have to override this. Suggestion is to take the value from the defined FSM.
class func startingState() -> Int {
NSLog("ERROR: Trying to use a method of StateLayoutView. Subclass it and override \(__FUNCTION__)")
self.doesNotRecognizeSelector(__FUNCTION__)
return invalidState
}
class func isStateValid(state: Int) -> Bool {
return contains(self.allStates(), state)
}
// You have to override this. Suggestion is to take the value from the defined FSM.
class func allStates() -> [Int] {
NSLog("ERROR: Trying to use a method of StateLayoutView. Subclass it and override \(__FUNCTION__)")
self.doesNotRecognizeSelector(__FUNCTION__)
return []
}
// You have to override this. Suggestion is to take the value from the defined FSM.
class func canMoveFromState(fromState: Int, toState: Int) -> Bool {
NSLog("ERROR: Trying to use a method of StateLayoutView. Subclass it and override \(__FUNCTION__)")
self.doesNotRecognizeSelector(__FUNCTION__)
return false
}
// You have to override this. Suggestion is to define an [NSLayoutConstraint] for each state and return that.
func constraintsForState(state: Int) -> [NSLayoutConstraint]? {
NSLog("ERROR: Trying to use a method of StateLayoutView. Subclass it and override \(__FUNCTION__)")
self.doesNotRecognizeSelector(__FUNCTION__)
return .None
}
// Animatable.
@IBInspectable var currentState: Int = invalidState {
didSet {
if oldValue != invalidState {
if self.dynamicType.isStateValid(currentState) {
if self.dynamicType.canMoveFromState(oldValue, toState: currentState) {
updateStateConstraintsForTransitionFromState(oldValue, toState: currentState)
return
} else {
NSLog("Can't make transition from state \(oldValue) to state \(currentState)")
}
} else {
NSLog("Can't make transition to invalid state \(currentState)")
}
currentState = oldValue
}
}
}
// Currently only supports constraints where priority = 1000.
private func initializeStateConstraints() {
currentState = self.dynamicType.startingState()
for state in self.dynamicType.allStates() {
constraintsForState(state)!.map { $0.priority = 1 }
}
constraintsForState(currentState)!.map { $0.priority = 1000 }
self.layoutIfNeeded()
}
// Currently only supports constraints where priority = 1000.
private func updateStateConstraintsForTransitionFromState(fromState: Int, toState: Int) {
constraintsForState(fromState)!.map { $0.priority = 1 }
constraintsForState(toState)!.map { $0.priority = 1000 }
self.layoutIfNeeded()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeStateConstraints()
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeStateConstraints()
}
}
//
// FSLVExample.swift
// FSLVExample
//
// Created by Davide De Franceschi on 16/11/14.
// Copyright (c) 2014 Davide De Franceschi. All rights reserved.
//
import UIKit
enum FSM: Int {
case LayoutA = 1
case LayoutB = 2
static func startingState() -> FSM {
return LayoutA
}
static func allStates() -> [FSM] {
return [LayoutA, LayoutB]
}
static func canMoveFromState(fromState: FSM, toState: FSM) -> Bool {
switch (fromState, toState) {
case (LayoutA, _):
return true
default:
return false
}
}
}
class FSLVExample: FiniteStateLayoutView {
override func nibName() -> String {
return "NibTest"
}
override class func startingState() -> Int {
return FSM.startingState().rawValue
}
override class func allStates() -> [Int] {
return FSM.allStates().map { $0.rawValue }
}
override class func canMoveFromState(fromState: Int, toState: Int) -> Bool {
switch (FSM(rawValue: fromState), FSM(rawValue: toState)) {
case (.Some(let from), .Some(let to)):
return FSM.canMoveFromState(from, toState: to)
default:
return false
}
}
override func constraintsForState(state: Int) -> [NSLayoutConstraint]? {
switch state {
case 1:
return layoutAConstraints
case 2:
return layoutBConstraints
default:
return nil
}
}
@IBOutlet var layoutAConstraints: [NSLayoutConstraint]!
@IBOutlet var layoutBConstraints: [NSLayoutConstraint]!
}
//
// NibDesignable.swift
//
// Copyright (c) 2014 Morten Bøgh
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
@IBDesignable
public class NibDesignable: UIView {
// MARK: - Initializer
override init(frame: CGRect) {
super.init(frame: frame)
self.setupNib()
}
// MARK: - NSCoding
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupNib()
}
// MARK: - Nib loading
/**
Called in init(frame:) and init(aDecoder:) to load the nib and add it as a subview.
*/
private func setupNib() {
var view = self.loadNib()
view.frame = self.bounds
view.autoresizingMask = .FlexibleWidth | .FlexibleHeight
self.addSubview(view)
}
/**
Called to load the nib in setupNib().
:returns: UIView instance loaded from a nib file.
*/
public func loadNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: self.nibName(), bundle: bundle)
return nib.instantiateWithOwner(self, options: nil)[0] as UIView
}
/**
Called in the default implementation of loadNib(). Default is class name.
:returns: Name of a single view nib file.
*/
public func nibName() -> String {
return self.dynamicType.description().componentsSeparatedByString(".").last!
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment