I'm currently developing an application with an user interface much like Twitter for Mac (Pushing in/out of views like on iOS).
Has anyone implemented a UIViewController for desktop Cocoa? This would save me many hours of work.
I'm currently developing an application with an user interface much like Twitter for Mac (Pushing in/out of views like on iOS).
Has anyone implemented a UIViewController for desktop Cocoa? This would save me many hours of work.
There isn't one in standard AppKit at this time. You'll have to write your own.
This may help if you decide to go down that path: http://parsekit.com/umekit/
UMEKit is a little framework for Cocoa that implements some equivalents to UIKit classes and UI components.
I spent ages looking for this and started to write my own, then found https://github.com/bfolder/BFNavigationController
For some reason Google doesn't know about it.
Here how we did NavigationController
, without navigation bar. Implementing navigation bar with transitions between navigation items similar to how transition between views made in example below.
import AppKit
public class NavigationController: NSViewController {
public private (set) var viewControllers: [NSViewController] = []
open override func loadView() {
view = NSView()
view.wantsLayer = true
}
public init(rootViewController: NSViewController) {
super.init(nibName: nil, bundle: nil)
pushViewController(rootViewController, animated: false)
}
public required init?(coder: NSCoder) {
fatalError()
}
}
extension NavigationController {
public var topViewController: NSViewController? {
return viewControllers.last
}
public func pushViewControllerAnimated(_ viewController: NSViewController) {
pushViewController(viewController, animated: true)
}
public func pushViewController(_ viewController: NSViewController, animated: Bool) {
viewController.navigationController = self
viewController.view.wantsLayer = true
if animated, let oldVC = topViewController {
embedChildViewController(viewController)
let endFrame = oldVC.view.frame
let startFrame = endFrame.offsetBy(dx: endFrame.width, dy: 0)
viewController.view.frame = startFrame
viewController.view.alphaValue = 0.85
viewControllers.append(viewController)
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.2
context.allowsImplicitAnimation = true
context.timingFunction = .easeOut
viewController.view.animator().frame = endFrame
viewController.view.animator().alphaValue = 1
oldVC.view.animator().alphaValue = 0.25
}) {
oldVC.view.alphaValue = 1
oldVC.view.removeFromSuperview()
}
} else {
embedChildViewController(viewController)
viewControllers.append(viewController)
}
}
@discardableResult
public func popViewControllerAnimated() -> NSViewController? {
return popViewController(animated: true)
}
@discardableResult
public func popViewController(animated: Bool) -> NSViewController? {
guard let oldVC = viewControllers.popLast() else {
return nil
}
if animated, let newVC = topViewController {
let endFrame = oldVC.view.frame.offsetBy(dx: oldVC.view.frame.width, dy: 0)
view.addSubview(newVC.view, positioned: .below, relativeTo: oldVC.view)
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.23
context.allowsImplicitAnimation = true
context.timingFunction = .easeIn
oldVC.view.animator().frame = endFrame
oldVC.view.animator().alphaValue = 0.85
}) {
self.unembedChildViewController(oldVC)
}
} else {
unembedChildViewController(oldVC)
}
return oldVC
}
}
Reusable extensions:
extension NSViewController {
private struct OBJCAssociationKey {
static var navigationController = "com.mc.navigationController"
}
public var navigationController: NavigationController? {
get {
return ObjCAssociation.value(from: self, forKey: &OBJCAssociationKey.navigationController)
} set {
ObjCAssociation.setAssign(value: newValue, to: self, forKey: &OBJCAssociationKey.navigationController)
}
}
}
extension NSViewController {
public func embedChildViewController(_ vc: NSViewController, container: NSView? = nil) {
addChildViewController(vc)
vc.view.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
vc.view.autoresizingMask = [.height, .width]
(container ?? view).addSubview(vc.view)
}
public func unembedChildViewController(_ vc: NSViewController) {
vc.view.removeFromSuperview()
vc.removeFromParentViewController()
}
}
struct ObjCAssociation {
static func value<T>(from object: AnyObject, forKey key: UnsafeRawPointer) -> T? {
return objc_getAssociatedObject(object, key) as? T
}
static func setAssign<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_ASSIGN)
}
static func setRetainNonAtomic<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
static func setCopyNonAtomic<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
static func setRetain<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN)
}
static func setCopy<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_COPY)
}
}