Presenting ViewController just shows black screen - swift

I am trying to present a ViewController from a Button action. But this seems to not work as i want it to. I do not get any error but after clicking the button the screen turns black and nothing happens. The only things i could manage to find are for objective-c and i am just trying to learn Swift. I am using storyboard to design this and added the Viewcontroller in the appropriate place in the designer
Code:
#IBAction func doShow(_ sender: Any)
{
var newWindowViewController = NewWindowViewController()
newWindowViewController.baseItem = self.baseItem
present(newWindowViewController, animated: false)
}

You need to instantiate the ViewController first from your storyboard. Else it won´t contain any View and turn up black.
#IBAction func doShow(_ sender: Any)
{
// Instantiate your ViewController here
var newWindowViewController = storyboard?.instantiateViewController(withIdentifier: "putyourIdentifierhere")) as! NewWindowViewController
newWindowViewController.baseItem = self.baseItem
present(newWindowViewController, animated: false)
}

Related

Xcode 11 Segues

Whenever I am using segues in the Xcode 11 beta (here just changing between two VCs using a swipe gesture), the second VC pops up as a card:
How can I prevent this?
Id take a look at this article. It explains well why its happening and gives an example of how to revert it back to the standard style.
View Controller Presentation Changes
If anyone is doing the segue programmatically, the code needs to be something similar to this:
#objc func buttonClicked(_ sender: UIButton) {
let vc = ViewControllerB() //Destination controller
vc.modalPresentationStyle = .fullScreen // This line is needed now
vc.modalTransitionStyle = .flipHorizontal
self.present( vc, animated: true, completion: nil )
}

perform segue after view controller is dimissed

I am using UINavigationController. I want to show an intermediate screen eg. White and then from there I want to dimiss and segue to green.
The reason I don't create a segue from white to green is because in the case the user goes back they should go back to blue because blue is my main screen.
Here's the code:
class BlueViewController: UIViewController {
#IBAction func tapBlue(_ sender: Any) {
self.performSegue(withIdentifier: "whiteSegue", sender: self)
}
}
class WhiteViewController: UIViewController {
#IBAction func tapGreen(_ sender: Any) {
navigationController?.popViewController(animated: true)
weak var pvc = self.presentingViewController
dismiss(animated: true){
pvc?.performSegue(withIdentifier: "greenSegue", sender: self)
}
}
}
Here's the codebase
https://github.com/omenking/DismissAndSegue
No error occurs but when white is dismissed it doesn't go to green.
I know this has been asked before on StackOverflow but the other examples did not work or were out of date with latest iOS.
The main issue is that since you are using a navigation view controller and pushing view controllers on and off the stack, the self.presentingViewController variable will be nil. That is used for modal presentations, not navigation view controllers.
Try this:
class WhiteViewController: UIViewController {
#IBAction func tapGreen(_ sender: Any) {
// Get navigation stack, remove last item (White VC)
var viewControllers = navigationController?.viewControllers
viewControllers.removeLast()
// Instantiate new Green VC from storyboard
let storyboard = UIStoryboard(name: "Main", bundle: nil) //Change bundle name
let greenViewController = storyboard.instantiateViewController(withIdentifier: "GreenViewController") //Change storyboard ID
viewControllers.append(greenViewController)
// Perform the transition to Green VC with animation
navigationController?.setViewControllers(viewControllers, animated: true)
}
}
A slightly different solution is to link your view controllers blue->white->green, and then in the green view controller, just remove the white view controller from the navigation stack.
Your green view controller then becomes as simple as this.
class GreenViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let count = self.navigationController?.viewControllers.count {
self.navigationController?.viewControllers.remove(at: count - 2)
}
}
}
No special handling needed in the other view controllers.
I do this, in white controller
performSegue(withIdentifier: "showSchedule", sender: date)
if let count = self.navigationController?.viewControllers.count {
self.navigationController?.viewControllers.remove(at: count - 2)
}

What is the correct way to close a popover?

In my NSDocument subclass I instantiate an NSPopover, with .semitransient behaviour, and show it:
popover.show(relativeTo: rect, of: sender, preferredEdge: .maxX)
popover is declared locally. A button method in the popover controller calls:
view.window?.close()
The popover closes, but I have become aware that it remains in memory, deinit() is never called and the NSApp.windows count increases, whereas if I dismiss it by pressing escape or clicking outside it, deinit is called and the windows count doesn't increase.
If I set the window's .isReleasedWhenClosed to true, the windows count doesn't increase, but deinit is still not called.
(Swift 3, Xcode 8)
You have to call performClose (or close) on the popover, not the window.
Thanks -DrummerB for your interest. It has taken me some time to get around to making a simple test application I might send you, and of course it wasn't a document-based one as mine was, and that seemed to be clouding the issue. My way of opening the popover was based on an example I'd recently read, but can't now find or I'd warn people. It went like this:
let popover = NSPopover
let controller = MyPopover(...)! // my convenience init for NSViewController descendant
popover.controller = controller
popover.behaviour = .semitransient // and setting other properties
popover.show(relativeTo: rect, of: sender, preferredEdge: .maxX)
Here's the improved way I've come across:
let controller = MyPopover(...)! // descendant of NSViewController
controller.presentViewController(controller,
asPopoverRelativeTo: rect, of: sender, preferredEdge: .maxX,
behavior: .semitransient) // sender was a NSTable
In the view controller, the 'Done' button's action simply does:
dismissViewController(self)
which never worked before. And now I find the app's windows list doesn't grow, and the controller's deinit happens reliably.
I would suggest doing the following:
Define a protocol like this
protocol PopoverManager {
func dismissPopover(_ sender: Any)
}
In your popoverViewController (in this example we are displaying a filter view controller as a popover) add a variable for the popoverManager like this
/// Filter shown as a NSPopover()
class FilterViewController: NSViewController {
// Delegate
var popoverManager: PopoverManager?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
// Bind this to the close button or action on your popover view controller
#IBAction func closeAction(_ sender: Any) {
self.popoverManager?.dismissPopover(sender)
}
...
}
Now in your viewController that you show the popover from add an extension like this
extension MainViewController: NSPopoverDelegate, PopoverManager {
#IBAction func setFilter(_ sender: AnyObject) {
self.showFilterPopover(sender)
}
func showFilterPopover(_ sender: AnyObject) {
let storyboard = NSStoryboard(name: "Filter", bundle: nil)
guard let controller = storyboard.instantiateController(withIdentifier: "FilterViewController") as? FilterViewController else {
return
}
// Set the delegate to self so we can dismiss the popover from the popover view controller itself.
controller.popoverManager = self
self.popover = NSPopover()
self.popover.delegate = self
self.popover.contentViewController = controller
self.popover.contentSize = controller.view.frame.size
self.popover.behavior = .applicationDefined
self.popover.animates = true
self.popover.show(relativeTo: sender.bounds, of: sender as! NSView, preferredEdge: NSRectEdge.maxY)
}
func dismissPopover(_ sender: Any) {
self.popover?.performClose(sender)
// If you don't want to reuse it
self.popover = nil
}
}

Swift: Switch between NSViewController inside Container View / NSView

I want to achieve a really simple task—changing the ViewController of a Container View by pressing a button:
In my example the ViewController1 is embedded into the Container View using Interface Builder. By pressing the Button ViewController2 I want to change the view to the second ViewController.
I’m confused because the Container View itself seems to be a NSView if I create an Outlet and as far as I know a NSView can’t contain a VC. Really appreciate your help!
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)
}
}
}
maybe this is a late answer but I will post my solution anyways. Hope it helps someone.
I embedded NSTabViewController in ContainerView. Then, in order not to see tabs on the top I did this:
go to NSTabViewController in storyboard
in Attributes inspector change style to be Unspecified
then click on TabView in Tab Bar View Controller, and set style to be "tabless":
After this you need to:
store tabViewController reference to mainViewController in order to select tabs from code
add a button to mainViewController (where your container is) with which you will change tabs in tabViewController.
You do this by storing the reference to tabViewController when overriding prepare for segue function. Here is my code:
first add property to the mainViewController
private weak var tabViewController: NSTabViewController?
then override this function and keep the reference to tabViewController:
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
guard let tabViewController = segue.destinationController
as? NSTabViewController else { return }
**self.tabViewController = tabViewController as? NSTabViewController**
}
After this you will have reference to tabViewController all set up.
Next (last) thing you have to do is make an action for button in order to move to first (or second) view controller, like this:
#IBAction func changeToSecondTab(_ sender: Any) {
self.tabViewController?.selectedTabViewItemIndex = 0 // or 1 for second VC
}
All the best!

Dismiss Popover after touch

I've created a popover inside my MainViewController when some button its touched using the UIPopoverPresentationController and set like it's delegate like it's showed in the WWDC 2014, in the following way :
MainViewController.swift
class MainViewController : UIViewController, UIPopoverPresentationControllerDelegate {
#IBAction func showPopover(sender: AnyObject) {
var popoverContent = self.storyboard?.instantiateViewControllerWithIdentifier("PopOverViewController") as UIViewController
popoverContent.modalPresentationStyle = UIModalPresentationStyle.Popover
var popover = popoverContent.popoverPresentationController
popoverContent.preferredContentSize = CGSizeMake(250, 419)
popover!.delegate = self
popover!.sourceView = self.view
popover!.sourceRect = CGRectMake(180,85,0,0)
self.presentViewController(popoverContent, animated: true, completion: nil)
}
}
The popover have a View inside it and when the View it's clicked with a Tap Gesture Recognizer I show LastViewController using a modal segue, the modal segue is created through the Interface Builder, not in code using an action to present the another LastViewController
Once the LastViewController is dismissed and I'm back in the MainViewController the popover remains open.
Inside the PopOverController I only have the default code nothing more.
LastViewController.swift
#IBAction func dismissVIew(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
The above code is used to dismiss the LastViewController once the button inside is touched.
Storyboard
How can I dismiss the popover once the another LastViewController it's visible, or before the another LastViewController should be opened?
Thanks in advance
I have already answer same problem over here.
There scenario is different but solution is same
You have to write code for dismiss presented view controller on completion of current view controller.
Write below code on your dismissVIew method of LastViewController.swift
var tmpController :UIViewController! = self.presentingViewController;
self.dismissViewControllerAnimated(false, completion: {()->Void in
println("done");
tmpController.dismissViewControllerAnimated(false, completion: nil);
});
Download link
In your button action on the FinalViewController, have you tried:
#IBAction func dismissMe() {
//this should tell the popover to tell the main view controller to dismiss it.
self.presentingViewController!.presentingViewController!.dismissViewControllerAnimated(false, completion: nil)
}
here is how I would do it.
I usually use lazy initialization for the PopoverViewController and it's ContentViewController
lazy var popoverVC: UIPopoverController = {
let vc = UIPopoverController(contentViewController: self.contentVC)
vc.delegate = self
return vc
}()
lazy var contentVC: UIViewController = {
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("ContentViewController") as UIViewController
vc.modalInPopover = true
return vc
}()
inside my contentViewController I hold a reference to the UIPopoverController.
var popoverVC: UIPopoverController!
then when I show the popover i just assign the popoverController to the contentViewController
#IBAction func showPopover(sender: UIButton) {
contentVC.popoverVC = self.popoverVC
let viewCenterRect = self.view.convertRect(self.view.bounds, toView: self.view)
popoverVC.presentPopoverFromRect(CGRectMake(CGRectGetMidX(viewCenterRect), CGRectGetMidY(viewCenterRect), 1, 1), inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.allZeros, animated: true)
}
finally I dismiss the Popover programmatically inside an #IBAction
#IBAction func dismissPopover(sender: AnyObject) {
popoverVC.dismissPopoverAnimated(true)
}
Inside the viewcontroller you can override viewWillAppear()
Inside of this block dimiss it
override public func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
_viewToDismiss.removeFromSuperView()
}
But the above code assumes you have a reference to the PopOver object, which I don't think is good practice based on how you described the problem.
Rather, why not have the viewcontroller that created the PopOver be responsible for destroying it. Put this in the class that listens for the button touch (which I also assumes creates the PopOver as well)
- (void)viewWillDisappear:(BOOL)animated
{
_popOver.removeFromSuperView()
}
The popover have a View inside it and when the View it's clicked with a Tap Gesture Recognizer I show another ViewController using a modal segue.
As far as I understand from what you say, you should be able to call dismissViewControllerAnimated(_:completion:) from the action associated to your tap gesture recogniser. This will dismiss the popover you presented calling:
self.presentViewController(popoverContent, animated: true, completion: nil)
You can call this method on the popover view controller itself, depending on what is more convenient for you:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
Based on #Jageen answer
Swift 4.2
let tmpController :UIViewController! = self.presentingViewController;
self.dismiss(animated: false, completion: {()->Void in
print("done");
tmpController.dismiss(animated: false, completion: nil);
});