Swift: Switch between NSViewController inside Container View / NSView
Just note that in order for this to work you have to add storyboard identifiers to your view controllers, which can by going to your storyboard then selecting the Identity Inspector in the right hand pane and then entering the Storyboard ID
in the Identity
subcategory.
Then this implementation of ViewController
would achieve what you are looking for.
import Cocoa
class ViewController: NSViewController {
// link to the NSView Container
@IBOutlet weak var container : NSView!
var vc1 : ViewController1!
var vc2 : ViewController2!
var vc1Active : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Make sure to set your storyboard identiefiers on ViewController1 and ViewController2
vc1 = NSStoryboard(name: "name", bundle: nil).instantiateController(withIdentifier: "ViewController1") as! ViewController1
vc2 = NSStoryboard(name: "name", bundle: nil).instantiateController(withIdentifier: "ViewController2") as! ViewController2
self.addChild(vc1)
self.addChild(vc2)
vc1.view.frame = self.container.bounds
self.container.addSubview(vc1.view)
vc1Active = true
}
// You can link this action to both buttons
@IBAction func switchViews(sender: NSButton) {
for sView in self.container.subviews {
sView.removeFromSuperview()
}
if vc1Active == true {
vc1Active = false
vc2.view.frame = self.container.bounds
self.container.addSubview(vc2.view)
} else {
vc1Active = true
vc1.view.frame = self.container.bounds
self.container.addSubview(vc1.view)
}
}
}
NSViewController displayed inside a containerView doesn't align itself to the bounds of the Container in Cocoa App Swift [Mac OS ]
The problem is that when you are using constraints, they resize with changes in layout. But in vc2, you have given it a constant size at the start. Which is why even if you resize the window, it does not get resized. Try programmatically assigning constraints to vc2.view to attach it to its superview (cv). This will make sure that vc2.view will resize with cv.
Make sure to assign constraints after cv.addSubview(vc2.view)
.
Check out this library which will remove a lot of boiler plate code while creating constraints.
Container View in Cocoa, Swap Contents at Runtime and Preserve Size
It turns out all I needed to do is add this line before setting up my constraints:
newView.translatesAutoresizingMaskIntoConstraints = false
It isn't clear to me why I should need this, given that the child view I want to add belongs to a view controller created from a storyboard (and those are supposed to already not translate autoresizing mask into constraints...?); this code should only be needed for views created programmatically...
macOS - Swift 4.0 Container View does not show NSView
I found sample code SourceView but it is rather complicated and in Objective-C. Here's my translation of the relevant part to Swift (I'm not very familiar with Swift).
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
var tableViewArray = ["Thema 1", "Thema 2", "Thema 3", "Thema 4", "Thema 5"]
var detailViewController: NSViewController?
@IBOutlet weak var container: NSView!
@IBAction func tableViewClickAction(_ tableView: NSTableView) {
if tableView.clickedRow >= 0 {
print("\(tableViewArray[tableView.clickedRow]), clicked")
// remove the detail view if there is one
if let oldViewController = detailViewController {
oldViewController.view.removeFromSuperview()
oldViewController.removeFromParentViewController()
detailViewController = nil
}
// determine the identifier of the new detail view, this is the Storyboard ID of the detail scene
var sceneIdentifier: NSStoryboard.SceneIdentifier?
switch tableView.clickedRow {
case 0:
sceneIdentifier = NSStoryboard.SceneIdentifier(rawValue: "Thema1")
case 1:
sceneIdentifier = NSStoryboard.SceneIdentifier(rawValue: "Thema2")
case 2:
sceneIdentifier = NSStoryboard.SceneIdentifier(rawValue: "Thema3")
case 3:
sceneIdentifier = NSStoryboard.SceneIdentifier(rawValue: "Thema4")
case 4:
sceneIdentifier = NSStoryboard.SceneIdentifier(rawValue: "Thema5")
default:
sceneIdentifier = nil
}
if sceneIdentifier != nil {
// load the new detail controller and view from the storyboard
detailViewController = self.storyboard?.instantiateController(withIdentifier:sceneIdentifier!) as? NSViewController
if let newViewController = detailViewController {
newViewController.view.translatesAutoresizingMaskIntoConstraints = false
// add controller and view
self.addChildViewController(newViewController)
container.addSubview(newViewController.view)
// add constraints
let views = ["targetView": newViewController.view]
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[targetView]|",
options: [], metrics: nil, views: views)
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[targetView]|",
options: [], metrics: nil, views: views)
NSLayoutConstraint.activate(horizontalConstraints)
NSLayoutConstraint.activate(verticalConstraints)
}
}
}
else {
print("Clicked outside the cells")
}
}
}
How to pass value from NSViewController to custom NSView of NSPopover?
Ok, there's some architecture mistakes. You don't need delegate method and protocol at all. All you just need is well defined setter method:
I. Place your PlasmidMapView
into NSViewController
-subclass. This view controller must be set as contentViewController
-property of your NSPopover
-control. Don't forget to set it the way you need in viewDidLoad
-method or another.
class PlasmidMapController : NSViewController {
weak var mapView: PlacmidMapView!
}
II. In your PlacmidMapView
don't forget to call needsDisplay
-method on dna
did set:
class PlasmidMapView: NSView {
//...
var dnaForMap = String() {
didSet {
needsDisplay()
}
//...
}
III. Set dna
-string whenever you need from your MainController
-class.
@IBAction func actionPopoverPlasmidMap(sender: AnyObject) {
popoverPlasmidMap.showRelativeToRect(sender.bounds,
ofView: sender as! NSView, preferredEdge: NSRectEdge.MinY)
let dna = inputDnaFromUser.string
if let controller = popoverPlasmidMap.contentViewController as? PlasmidMapController {
controller.mapView.dna = dna
} else {
fatalError("Invalid popover content view controller")
}
}
NSViewController reference cycle with storyboards
Another answer.
It seems, only the segues defined on Storyboard can perform view controller deallocation.
So, Here is a very ugly but working workaround.
class DismissSegue: NSStoryboardSegue {
var nextViewControllerIdentifier:String?
override func perform() {
let src = self.sourceController as NSViewController
let windowController = src.view.window!.windowController() as TopLevelWindowController
src.view.removeFromSuperview()
src.removeFromParentViewController()
if let identifier = nextViewControllerIdentifier {
windowController.setNewViewController(identifier)
}
}
}
class TopLevelWindowController: NSWindowController {
var containerView: NSView!
var containerViewController: ContainerViewController! {
didSet {
setNewViewController("FirstView")
}
}
func setNewViewController(identifier: String) {
// Create and set up the new view controller and view.
let viewController = storyboard!.instantiateControllerWithIdentifier(identifier) as NSViewController
let view = viewController.view
view.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(viewController.view)
containerViewController.addChildViewController(viewController)
containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: nil, metrics: nil, views: ["view": view]))
containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: nil, metrics: nil, views: ["view": view]))
}
}
class ContainerViewController: NSViewController {
@IBOutlet var containerView: NSView!
override func viewDidAppear() {
super.viewDidAppear()
if let window = view.window {
if let topLevelWindowController = window.windowController() as? TopLevelWindowController {
topLevelWindowController.containerView = containerView
topLevelWindowController.containerViewController = self
}
}
}
}
class FirstViewController: NSViewController {
required init?(coder: NSCoder) {
super.init(coder: coder)
let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
NSLog("First VC init at \(pointerAddress)")
}
deinit {
let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
NSLog("First VC de-init at \(pointerAddress)")
}
override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject?) {
if let segue = segue as? DismissSegue {
segue.nextViewControllerIdentifier = "SecondView"
}
}
}
class SecondViewController: NSViewController {
required init?(coder: NSCoder) {
super.init(coder: coder)
let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
NSLog("Second VC init at \(pointerAddress)")
}
deinit {
let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
NSLog("Second VC de-init at \(pointerAddress)")
}
override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject?) {
if let segue = segue as? DismissSegue {
segue.nextViewControllerIdentifier = "FirstView"
}
}
}
Procedure for modifying your Storyboard:
- Disconnect
@IBAction
from buttons. - Create "DUMMY" view controller scene with normal
NSViewController
. - Connect custom segues from buttons to "DUMMY".
- Configure these segue as shown.
Please let me know if this solution doesn't meet your demand.
Does an NSFilePromiseProviderDelegate need to be also an NSView or NSViewController?
Does an NSFilePromiseProviderDelegate need to be also an NSView or NSViewController?
No.
delegate
is a local variable and is released at the end of pasteboardWriter(forImageCanvas:)
. Without a delegate the file promise provider doesn't work. Solution: keep a strong reference to the delegate.
class ImageCanvasController: NSViewController, ImageCanvasDelegate, NSToolbarDelegate {
let delegate = CustomFilePromiseProviderDelegate()
...
func pasteboardWriter(forImageCanvas imageCanvas: ImageCanvas) -> NSPasteboardWriting {
let provider = NSFilePromiseProvider(fileType: kUTTypeJPEG as String, delegate: delegate)
provider.userInfo = imageCanvas.snapshotItem
return provider
}
}
Related Topics
How to Create a Packed Data Structure in Swift
Conflicting Definition of Swift Struct and Array
Swift .Uppercasestring or .Lowercasestring Property Replacement
Swift: Function with Default Parameter Before Non-Default Parameter
Why Does a Public Class/Struct in Swift Require an Explicit Public Initializer
Coredata Crash Error Xcode 11 Beta, iOS 13 Beta
How to Write an 'If Case' Statement as an Expression
How to Check If a String Contains Letters in Swift
Swift: Uicollectionview Selecting Cell Indexpath Issues
How to Add "%" to Data in iOS-Chart
Swift: How to Fully Strip Internal/Inline Symbols
Terminate Subprocesses of MACos Command Line Tool in Swift
Uitableviewcell Asynchronously Loading Images Issue - Swift
Sending Whatsapp Message to a Specific Contact Number (Swift Project)
Centering the Camera on a Node in Swift Spritekit
Swift Start Tableview from Bottom (Reverse Tableview)
Swift3:How to Handle Precedencegroup Now Operator Should Be Declare with a Body