Skip to content

Instantly share code, notes, and snippets.

@jakehawken
Last active August 1, 2018 22:36
Show Gist options
  • Select an option

  • Save jakehawken/6c9f1716c4848c88be222d50b03886bd to your computer and use it in GitHub Desktop.

Select an option

Save jakehawken/6c9f1716c4848c88be222d50b03886bd to your computer and use it in GitHub Desktop.
A way to easily transition between displaying two different sets of subviews in a UIStackView.
import Foundation
import UIKit
class ToggleableStackView: UIStackView {
enum Cohort {
case primary
case secondary
var next: Cohort {
switch self {
case .primary:
return .secondary
case .secondary:
return .primary
}
}
}
typealias TransitionCompletion = ()->()
internal var fadeSpeed: TimeInterval = 0.2
private(set) var isAnimating: Bool = false
private(set) var currentCohort: Cohort?
private var primaryCohort: Set<UIView>?
internal func setPrimaryCohort(_ cohort: [UIView]) {
let newPrimary: Set<UIView> = Set(cohort)
guard allAreLocalSubviews(newPrimary) else {
fatalError("All views in a cohort must be subviews of the stackview.")
}
primaryCohort = newPrimary
}
internal func toggle(animated: Bool = true, callback: TransitionCompletion? = nil) {
guard let current = currentCohort else {
return
}
switchTo(cohort: current.next, animate: animated, callback: callback)
}
internal func switchTo(cohort: Cohort, animate: Bool, callback: TransitionCompletion? = nil) {
guard isAnimating == false else {
return
}
guard let primary = primaryCohort else {
return
}
// Secondary is all subviews that are not in primary.
let secondary = Set(subviews.filter { !primary.contains($0) })
if animate == false {
switchCohortsInstantly(primary: primary, secondary: secondary, cohort: cohort, callback: callback)
}
else {
switchCohortsAnimated(primary: primary, secondary: secondary, cohort: cohort, callback: callback)
}
}
//MARK: private
private func allAreLocalSubviews(_ set: Set<UIView>) -> Bool {
let localSet: Set<UIView> = Set(subviews)
return set.isSubset(of: localSet)
}
private func switchCohortsInstantly(primary: Set<UIView>, secondary: Set<UIView>, cohort: Cohort, callback: TransitionCompletion?) {
let hidePrimary = (cohort == .secondary)
primary.forEach { $0.alpha = hidePrimary ? 0 : 1 }
primary.forEach { $0.isHidden = hidePrimary }
secondary.forEach { $0.alpha = hidePrimary ? 1 : 0 }
secondary.forEach { $0.isHidden = !hidePrimary }
currentCohort = cohort
callback?()
}
private func switchCohortsAnimated(primary: Set<UIView>, secondary: Set<UIView>, cohort: Cohort, callback: TransitionCompletion?) {
isAnimating = true
let hidePrimary = (cohort == .secondary)
//Makes sure that you're not animating the alpha of a hidden view.
let cohortToFadeOut = hidePrimary ? primary : secondary
let cohortToFadeIn = hidePrimary ? secondary : primary
fadeOutAndHide(views: cohortToFadeOut) { [weak self] in
self?.unhideAndFadeIn(views: cohortToFadeIn) {
self?.currentCohort = cohort
self?.isAnimating = false
callback?()
}
}
}
private func fadeOutAndHide(views: Set<UIView>, transitionCompletion: @escaping TransitionCompletion) {
UIView.animate(withDuration: fadeSpeed, animations: {
views.forEach { $0.alpha = 0 }
}) { (_) in
views.forEach { $0.isHidden = true }
transitionCompletion()
}
}
private func unhideAndFadeIn(views: Set<UIView>, transitionCompletion: @escaping TransitionCompletion) {
views.forEach {
$0.alpha = 0
$0.isHidden = false
}
UIView.animate(withDuration: fadeSpeed, animations: {
views.forEach { $0.alpha = 1 }
}) { (_) in
transitionCompletion()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment