Last active
June 19, 2018 21:50
-
-
Save shaps80/3cf7b91fd7e65fc58d1e6f4991ffe47e to your computer and use it in GitHub Desktop.
A simple workspace builder using simple UIStackView's and a clean DSL for describing the layout.
This file contains hidden or 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
| // | |
| // ViewController.swift | |
| // Workspace | |
| // | |
| // Created by Shaps Benkau on 12/27/2016. | |
| // Copyright (c) 2016 Snippex Ltd. All rights reserved. | |
| // | |
| import UIKit | |
| class DemoViewController: UIViewController { | |
| override func viewDidLoad() { | |
| super.viewDidLoad() | |
| let content = PaneViewController(title: "Content") | |
| content.view.backgroundColor = UIColor(white: 0.106, alpha: 1) | |
| let preview = PaneViewController(title: "Preview") | |
| preview.view.backgroundColor = UIColor(white: 0.086, alpha: 1) | |
| let timeline = PaneViewController(title: "Timeline") | |
| timeline.view.backgroundColor = UIColor(white: 0.106, alpha: 1) | |
| let toolbar = PaneViewController(title: "Toolbar") // 0.122 | |
| toolbar.view.backgroundColor = UIColor(white: 0.2, alpha: 1) | |
| let workspace = WorkspaceController(nibName: nil, bundle: nil) | |
| addChildViewController(workspace) | |
| view.addSubview(workspace.view) | |
| workspace.didMove(toParentViewController:self) | |
| workspace.view.autoresizingMask = [.flexibleHeight, .flexibleWidth] | |
| /* This is where we define the workspace | |
| ----- ------------- | |
| | | | | | |
| | C | | Preview | | |
| | o | |------------ | |
| | n | | Toolbar | | |
| | t | |------------ | |
| | e | | | | |
| | n | | Timeline | | |
| | t | | | | |
| | | | | | |
| ----- ------------- | |
| */ | |
| let rightPane = WorkspacePane(axis: .vertical) | |
| .add(child: .controller(preview), size: .fixed(length: 320)) | |
| .add(child: .controller(toolbar), size: .fixed(length: 44)) | |
| .add(child: .controller(timeline), size: .fixed(length: 100), priority: UILayoutPriorityDefaultLow) | |
| let mainPane = WorkspacePane(axis: .horizontal) | |
| .add(child: .controller(content), size: .fixed(length: 320)) | |
| .add(child: .pane(rightPane), size: .flexible) | |
| workspace.invalidate(with: mainPane) | |
| // To demonstrate animation -- we can hide the left hand view initially | |
| content.view.isHidden = true | |
| DispatchQueue.main.asyncAfter(deadline: .now() + 1) { | |
| // Now we can un-hide the view and watch everything animate nicely ;) | |
| UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: .beginFromCurrentState, animations: { | |
| content.view.isHidden = false | |
| }, completion: nil) | |
| } | |
| } | |
| } | |
| final class PaneViewController: UIViewController { | |
| init(title: String) { | |
| super.init(nibName: nil, bundle: nil) | |
| self.title = title | |
| } | |
| required init?(coder aDecoder: NSCoder) { | |
| fatalError("init(coder:) has not been implemented") | |
| } | |
| override func viewDidLoad() { | |
| super.viewDidLoad() | |
| let label = UILabel() | |
| label.text = title | |
| label.textColor = .white | |
| label.textAlignment = .center | |
| label.translatesAutoresizingMaskIntoConstraints = false | |
| view.addSubview(label) | |
| NSLayoutConstraint.activate([ | |
| label.centerXAnchor.constraint(equalTo: view.centerXAnchor), | |
| label.centerYAnchor.constraint(equalTo: view.centerYAnchor), | |
| ]) | |
| } | |
| } |
This file contains hidden or 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
| // | |
| // WorkspaceController.swift | |
| // Workspace | |
| // | |
| // Created by Shaps Benkau on 27/12/2016. | |
| // Copyright © 2016 Snippex Ltd. All rights reserved. | |
| // | |
| import UIKit | |
| public class WorkspaceController: UIViewController { | |
| var viewControllers: [UIViewController] = [] | |
| public func invalidate(with pane: WorkspacePane) { | |
| for controller in viewControllers { | |
| controller.willMove(toParentViewController: nil) | |
| controller.view.removeFromSuperview() | |
| controller.removeFromParentViewController() | |
| } | |
| viewControllers.removeAll() | |
| view.subviews.forEach { | |
| $0.removeFromSuperview() | |
| } | |
| pane.view.frame = view.bounds | |
| pane.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] | |
| view.backgroundColor = .black | |
| view.addSubview(pane.view) | |
| pane.invalidate(in: self) | |
| } | |
| } |
This file contains hidden or 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
| // | |
| // WorkspacePane.swift | |
| // Workspace | |
| // | |
| // Created by Shaps Benkau on 01/01/2017. | |
| // Copyright © 2017 Snippex Ltd. All rights reserved. | |
| // | |
| import UIKit | |
| public final class WorkspacePane { | |
| public enum Axis { | |
| case horizontal | |
| case vertical | |
| } | |
| public enum SizeMode { | |
| case flexible | |
| case fixed(length: CGFloat) | |
| } | |
| public enum Child { | |
| case controller(UIViewController) | |
| case pane(WorkspacePane) | |
| } | |
| public var view: UIView { | |
| return stackView | |
| } | |
| // MARK: Public | |
| public init(axis: Axis) { | |
| let stackView = UIStackView() | |
| stackView.axis = axis == .horizontal ? .horizontal : .vertical | |
| stackView.alignment = .fill | |
| stackView.distribution = .fill | |
| stackView.spacing = 2 | |
| self.stackView = stackView | |
| self.axis = axis | |
| } | |
| @discardableResult public func add(child: Child, size mode: SizeMode, priority: UILayoutPriority = UILayoutPriorityDefaultHigh) -> WorkspacePane { | |
| children.append((child, mode, priority)) | |
| return self | |
| } | |
| // MARK: Internal | |
| func invalidate(in workspace: WorkspaceController) { | |
| let maxLength = max(UIScreen.main.bounds.width, UIScreen.main.bounds.height) | |
| for (child, mode, priority) in children { | |
| switch child { | |
| case .controller(let controller): | |
| addChild(controller: controller, to: workspace) | |
| applyConstraint(to: controller.view, maxLength: maxLength, size: mode, priority: priority) | |
| case .pane(let pane): | |
| addChild(pane: pane, to: workspace) | |
| applyConstraint(to: pane.view, maxLength: maxLength, size: mode, priority: priority) | |
| } | |
| } | |
| } | |
| // MARK: Private | |
| private let axis: Axis | |
| private let stackView: UIStackView | |
| private var children: [(Child, SizeMode, UILayoutPriority)] = [] | |
| private func sizeForMode(_ mode: SizeMode, maxLength: CGFloat) -> CGSize { | |
| switch mode { | |
| case .fixed(let length): | |
| return CGSize(width: axis == .horizontal ? length : maxLength, | |
| height: axis == .vertical ? length : maxLength) | |
| default: | |
| return CGSize(width: maxLength, height: maxLength) | |
| } | |
| } | |
| private func addChild(pane: WorkspacePane, to workspace: WorkspaceController) { | |
| stackView.addArrangedSubview(pane.view) | |
| pane.invalidate(in: workspace) | |
| } | |
| private func addChild(controller: UIViewController, to workspace: WorkspaceController) { | |
| guard !workspace.viewControllers.contains(controller) else { fatalError("This controller already exists in the Workspace") } | |
| workspace.addChildViewController(controller) | |
| stackView.addArrangedSubview(controller.view) | |
| controller.didMove(toParentViewController:workspace) | |
| workspace.viewControllers.append(controller) | |
| } | |
| private func applyConstraint(to view: UIView, maxLength: CGFloat, size mode: SizeMode, priority: UILayoutPriority) { | |
| let size = sizeForMode(mode, maxLength: maxLength) | |
| if axis == .horizontal && size.width != maxLength { | |
| let constraint = view.widthAnchor.constraint(equalToConstant: size.width) | |
| // we lower the constraint so that it doesn't conflict with the UIStackView's constraints | |
| constraint.priority = max(1, priority - 1) | |
| NSLayoutConstraint.activate([ constraint ]) | |
| } | |
| if axis == .vertical && size.height != maxLength { | |
| let constraint = view.heightAnchor.constraint(equalToConstant: size.height) | |
| // we lower the constraint so that it doesn't conflict with the UIStackView's constraints | |
| constraint.priority = max(1, priority - 1) | |
| NSLayoutConstraint.activate([ constraint ]) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment