Skip to content

Instantly share code, notes, and snippets.

@chunkyguy
Created December 14, 2022 19:42
Show Gist options
  • Save chunkyguy/39a8e0c2151e0b13955141544627ec46 to your computer and use it in GitHub Desktop.
Save chunkyguy/39a8e0c2151e0b13955141544627ec46 to your computer and use it in GitHub Desktop.
UIViewController transitions
import UIKit
class ContainerViewController: UIViewController {
private var childVwCtrl: UIViewController
var contentViewController: UIViewController {
get { childVwCtrl }
set { set(contentViewController: newValue, animationDuration: nil) }
}
func set(contentViewController toVwCtrl: UIViewController, animationDuration: Double?) {
let fromVwCtrl = childVwCtrl
guard let duration = animationDuration else {
remove(viewController: fromVwCtrl)
add(viewController: toVwCtrl)
childVwCtrl = toVwCtrl
return
}
addChild(toVwCtrl)
toVwCtrl.view.frame = contentFrame
fromVwCtrl.willMove(toParent: nil)
beginAnimation(fromView: fromVwCtrl.view, toView: toVwCtrl.view)
transition(
from: fromVwCtrl,
to: toVwCtrl,
duration: duration,
options: [.curveEaseOut],
animations: {
self.endAnimation(
fromView: fromVwCtrl.view,
toView: toVwCtrl.view
)
},
completion: { _ in
toVwCtrl.didMove(toParent: self)
fromVwCtrl.removeFromParent()
self.childVwCtrl = toVwCtrl
}
)
}
private func beginAnimation(fromView: UIView, toView: UIView) {
fromView.transform = CGAffineTransform.identity
toView.transform = CGAffineTransform(translationX: 500, y: 0)
}
private func endAnimation(fromView: UIView, toView: UIView) {
fromView.transform = CGAffineTransform(translationX: -500, y: 0)
toView.transform = CGAffineTransform.identity
}
init(contentViewController: UIViewController) {
childVwCtrl = contentViewController
super.init(nibName: nil, bundle: nil)
add(viewController: contentViewController)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
}
private var contentFrame: CGRect {
let edge = view.bounds.size.minEdge - 100
return CGRect.sized(edge).offsetBy(dx: 50, dy: 200)
}
private var contentEdgeInsets: UIEdgeInsets {
return UIEdgeInsets.from(outer: view.bounds, inner: contentFrame)
}
private func add(viewController: UIViewController) {
addChild(viewController)
view.addSubview(viewController.view)
viewController.view.frame = contentFrame
viewController.didMove(toParent: self)
}
private func remove(viewController: UIViewController) {
viewController.willMove(toParent: nil)
viewController.view.removeFromSuperview()
viewController.removeFromParent()
}
}
import UIKit
class SomeViewController: UIViewController {
let desc: String
init(desc: String) {
self.desc = desc
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(hue: CGFloat.random(in: 0...1),
saturation: 0.8,
brightness: 0.8,
alpha: 1)
let label = UILabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 80)))
view.addSubview(label)
view.addConstraints(to: label)
label.text = desc
label.font = UIFont.systemFont(ofSize: 24, weight: .bold)
label.textAlignment = .center
}
override var description: String {
return desc
}
}
import UIKit
extension CGSize {
var minEdge: CGFloat { min(width, height) }
func offset(_ val: CGFloat) -> CGSize {
return CGSize(width: width - val, height: height - val)
}
}
extension CGRect {
static func sized(_ value: CGFloat) -> CGRect {
return CGRect(origin: .zero, size: CGSize(width: value, height: value))
}
}
extension UIEdgeInsets {
static func from(outer: CGRect, inner: CGRect) -> UIEdgeInsets {
return UIEdgeInsets(
top: inner.minY - outer.minY,
left: inner.minX - outer.minX,
bottom: outer.maxY - inner.maxY,
right: outer.maxX - inner.maxX)
}
static func all(_ value: CGFloat) -> UIEdgeInsets {
return UIEdgeInsets(top: value, left: value, bottom: value, right: value)
}
}
extension UIView {
func addConstraints(to subview: UIView, insets: UIEdgeInsets = .zero) {
subview.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(
withVisualFormat: "V:|-(\(insets.top))-[vw]-(\(insets.bottom))-|",
metrics: nil,
views: ["vw": subview]
))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(
withVisualFormat: "H:|-(\(insets.left))-[vw]-(\(insets.right))-|",
metrics: nil,
views: ["vw": subview]
))
}
}
class ViewController: UIViewController {
var containerVwCtrl: ContainerViewController!
var vwCtrls: [SomeViewController] = []
var selectedIndex = 0
var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
vwCtrls = (0..<5).map { SomeViewController(desc: "Child \($0)") }
containerVwCtrl = ContainerViewController(contentViewController: vwCtrls[selectedIndex])
containerVwCtrl.modalPresentationStyle = .fullScreen
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
present(containerVwCtrl, animated: false)
timer = Timer.scheduledTimer(
timeInterval: 3,
target: self, selector: #selector(displayNext),
userInfo: nil, repeats: true)
}
@objc func displayNext() {
selectedIndex = (selectedIndex + 1) % vwCtrls.count
containerVwCtrl.set(contentViewController: vwCtrls[selectedIndex], animationDuration: 1)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment