Created
November 17, 2014 11:54
-
-
Save DeFrenZ/93bf1b1b24006a956dd7 to your computer and use it in GitHub Desktop.
FiniteStateLayoutView
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
// | |
// 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() | |
} | |
} |
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
// | |
// 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]! | |
} |
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
// | |
// 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