Present View Controller when the view is not in the window hierarchy - xcode

import AVFoundation
import UIKit
import AVKit
class Class1: UIViewController {
var playerViewController = AVPlayerViewController()
var playerView = AVPlayer()
var playerTimer = NSTimer()
override func viewDidAppear(animated:Bool) {
let fileURL = NSURL(fileURLWithPath: "file.mp4")
playerView = AVPlayer(URL: fileURL)
playerViewController.player = playerView
self.presentViewController(playerViewController, animated: true){
self.playerViewController.player?.play()
self.playerTimer = NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector:Selector("stopAfter4seconds:"), userInfo: nil, repeats: false)
}
}
func stopAfter4seconds(timer: NSTimer){
self.playerViewController.player?.pause()
self.playerViewController.player = nil
self.presentingViewController?.dismissViewControllerAnimated(false, completion: nil)
self.presentViewController(GameViewController1(), animated: false, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
My Problem is that when I present the line:
self.presentViewController(GameViewController1(), animated: false, completion: nil)
I Get the error "attempt to present GameViewController1... whose view is not in the window hierarchy!"
I made sure to play it in viewDidAppear as to add it to the view hierarchy but it doesn't seem to do so
[Update] Okay i realize now that I'm presenting the avplayerviewcontroller and then trying to play the next view controller after. How exactly do I "Dismiss" the AVplayerviewcontroller as well have my next view in the right window hierarchy???

ANSWER: I was presenting my next view with 'self.' instead of with the view controller that was present, which happened to be the playerViewController so this line of code worked for me.
playerViewController.presentViewController(GameViewController1(),
animated: false, completion: nil)

Related

open new view controller from UIViewCell

// In UITableViewCell
// when I tab on screen it says "open image
2017-11-22 13:15:31.023129+0530 chat[1376:265960] Warning: Attempt to present on whose view is not in the window hierarchy!"
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
let tappedImage = tapGestureRecognizer.view as! UIImageView
print("open image")
let sb = UIStoryboard.init(name: "Main", bundle: nil)
let vc : openimageinfullscreen = sb.instantiateViewController(withIdentifier: "openimageinfullscreen") as! openimageinfullscreen
self.window?.rootViewController?.present(vc, animated: true, completion: nil)
}
You need to get the top presented view controller and then present.
Try this.
func topViewController() -> UIViewController? {
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
}
return nil
}
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
let tappedImage = tapGestureRecognizer.view as! UIImageView
print("open image")
let sb = UIStoryboard.init(name: "Main", bundle: nil)
let vc : openimageinfullscreen = sb.instantiateViewController(withIdentifier: "openimageinfullscreen") as! openimageinfullscreen
self.topViewController()?.present(vc, animated: true, completion: nil)
}
On a separate note, You shouldn't use class name starts with small letter. openimageinfullscreen class should be OpenImageInFullscreen

Change of scene class causes error, Xcode / Swift

I am trying to make a small iPad application which features an 'About' page. I have run into an issue when I try to change the class of the about page scene which is displayed modally, as the application crashes when the user tries to access this page. I wish to change its class to "ViewController", which is the same class as my main window (map view), to create actions from buttons on the 'About' page.
Currently I am unable to ctrl+drag to the .Swift file from the About page unless I change its class. Not sure why this is happening :(
The error printed to the console is as follows:
fatal error: unexpectedly found nil while unwrapping an Optional value
0 specialised_fatalErrorMessage(StaticString, StaticString, StaticString, Uint) -> () –
Here is my storyboard with scenes:
Here is the full ViewController.Swift code:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UISearchBarDelegate, UIPopoverPresentationControllerDelegate {
var location: CLLocation!
let locationManager = CLLocationManager()
// Map variables
var searchController:UISearchController!
var annotation:MKAnnotation!
var localSearchRequest:MKLocalSearchRequest!
var localSearch:MKLocalSearch!
var localSearchResponse:MKLocalSearchResponse!
var error:NSError!
var pointAnnotation:MKPointAnnotation!
var pinAnnotationView:MKPinAnnotationView!
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var showSearchBar: UIBarButtonItem!
#IBOutlet weak var addButton: UIBarButtonItem!
#IBOutlet weak var moreStuff: UIBarButtonItem!
#IBAction func addButton(sender: AnyObject) {
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: self.mapView.userLocation.coordinate.latitude, longitude: self.mapView.userLocation.coordinate.longitude)
self.mapView.addAnnotation(annotation)
self.locationManager.startUpdatingLocation()
}
#IBAction func showSearchBar(sender: UIBarButtonItem!) {
searchController = UISearchController(searchResultsController: nil)
searchController.hidesNavigationBarDuringPresentation = false
self.searchController.searchBar.delegate = self
presentViewController(searchController, animated: true, completion: nil)
}
#IBAction func moreStuff(sender: AnyObject) {
self.performSegueWithIdentifier("showMoreStuff", sender:self)
}
#IBAction func refresh(sender: AnyObject) {
self.locationManager.startUpdatingLocation()
}
#IBAction func segmentedControl(sender: UISegmentedControl!) {
if sender.selectedSegmentIndex == 0{
mapView.mapType = MKMapType.Standard
}
else if sender.selectedSegmentIndex == 1{
mapView.mapType = MKMapType.Satellite
}
else if sender.selectedSegmentIndex == 2{
mapView.mapType = MKMapType.Hybrid
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// location delegate methods
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
self.mapView.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
{
print("Error code: " + error.localizedDescription)
}
func searchBarSearchButtonClicked(searchBar: UISearchBar){
searchBar.resignFirstResponder()
dismissViewControllerAnimated(true, completion: nil)
if self.mapView.annotations.count != 0{
annotation = self.mapView.annotations[0]
self.mapView.removeAnnotation(annotation)
}
localSearchRequest = MKLocalSearchRequest()
localSearchRequest.naturalLanguageQuery = searchBar.text
localSearch = MKLocalSearch(request: localSearchRequest)
localSearch.startWithCompletionHandler { (localSearchResponse, error) -> Void in
if localSearchResponse == nil{
let alertController = UIAlertController(title: nil, message: "No Such Place", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
return
}
self.pointAnnotation = MKPointAnnotation()
self.pointAnnotation.title = searchBar.text
self.pointAnnotation.coordinate = CLLocationCoordinate2D(latitude: localSearchResponse!.boundingRegion.center.latitude, longitude: localSearchResponse!.boundingRegion.center.longitude)
self.pinAnnotationView = MKPinAnnotationView(annotation: self.pointAnnotation, reuseIdentifier: nil)
self.mapView.centerCoordinate = self.pointAnnotation.coordinate
}
}
}
I am new to Swift so any help is really appreciated. Thanks!
Edit:
This is the error screen presented to me once the app crashes:
I haven't used mapKit but I still try.
Looking at your error your getting a nil crash, which indicates one of your properties is nil yet you try to use it.
Having a quick read through your code it seams you haven't initialised
var localSearchResponse....
Again i don't know the MK API very well so I am not 100 sure what I'm saying is relevant.
Have you checked that all your IBOutlets are properly connected?
On which line do you get the crash when this happens? Do you get the crash before or after changing to the abouts page?

Passing data between view controllers with a UIAlertAction in Swift

I have a tableView, and when the user taps a cell, it opens an alert box with the options "Close" and "Search". The handler for "Search" is shown below as func searchSongAction.
This presents a new viewcontroller (embedded in a Nav Bar) that searches the song online. In this new viewcontroller, Search, there are 2 functions: one searches the song currently playing and the other searches the song that the user asked to search from the tableView alert.
I am trying to pass the data from the cell into the Search class, but I keep coming up short. I feel like what I have is correct, but that is obviously not he case.
Any ideas?
Ask me if you need any more information.
History.swift
func searchSongAction(alert: UIAlertAction!) {
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("Search") as! Search
let navigationController = UINavigationController(rootViewController: vc)
self.presentViewController(navigationController, animated: true, completion: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationNavigationController = segue.destinationViewController as! UINavigationController
let targetController = destinationNavigationController.topViewController as! Search
targetController.searchType = "Previous"
targetController.songNowText = self.songToSearch
targetController.artistNowText = self.artistToSearch
}
Search.swift
class Search: UIViewController {
var songNowText = ""
var artistNowText = ""
var searchType = ""
override func viewDidLoad() {
super.viewDidLoad()
if searchType == "Previous" {
searchSongPrevious()
} else {
searchSongNow()
}
}
}
You're not actually performing a Segue from your searchSongAction: function, so I'm guessing that prepareForSegue:sender: isn't actually being called and therefore the setup is not running.
Try assigning searchType, songNowText and artistNowText on vc inside of searchSongAction::
func searchSongAction(alert: UIAlertAction!) {
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("Search") as! Search
//Setup the properties
vc.searchType = "Previous"
vc.songNowText = self.songToSearch
vc.artistNowText = self.artistToSearch
let navigationController = UINavigationController(rootViewController: vc)
self.presentViewController(navigationController, animated: true, completion: nil)
}
As a side note, typically you'll want to call super's implementation of a function when overriding it (you have neglected to do this in prepareForSegue:sender:)

NSViewController reference cycle with storyboards

NSViewController, when instantiated from storyboards under Swift, seems to have a reference cycle somewhere.
Calling the following code multiple times will instantiate and set a new view controller, but the old view controller is never dealloced. In the code, containerViewController is an NSViewController which should contain a single NSViewController, containerView is a subview within containerViewController, and identifier is the storyboard identifier to instantiate.
// Remove any sub viewcontrollers and their views
for viewController in containerViewController.childViewControllers as [NSViewController] {
viewController.view.removeFromSuperview()
viewController.removeFromParentViewController()
}
// 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]))
(Sample project no longer available)
I used an Apple TSI and they agree it's a bug, which I have filed, but I expected someone else to have come up against this by now seeing as NSViewControllers and storyboards are now the de facto on OSX. How have you worked around this problem? Or does it not affect anyone else and I am doing something wrong?
Pre-bounty edit: Each view controller must be able to link to any other view controller from code as the destination is determined on the fly. This seems to remove segues as an option.
Bug fixed
As of Xcode 6.3 this is no longer a bug.
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.
This might not solve your problem, but the only workaround I found is:
Embed initial view controller using "Container View"
No #IBAction
Use custom NSStoryboardSegue to Switch between view controllers.
Something like this:
import Cocoa
class TopLevelWindowController: NSWindowController {
}
class ContainerViewController: NSViewController {
}
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)")
}
}
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)")
}
}
class MySegue: NSStoryboardSegue {
override func perform() {
let source = self.sourceController as NSViewController
let destination = self.destinationController as NSViewController
if let containerViewController = source.parentViewController {
source.view.removeFromSuperview()
source.removeFromParentViewController()
let view = destination.view
let containerView = containerViewController.view
view.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(view)
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]))
containerViewController.addChildViewController(destination)
}
}
}

How to open a new window with its own ViewController from AppDelegate in Swift

I have made a statusBar application with a drop down. I would like to open a settingsWindow from that dropdown. I have made the settings window with its own ViewController.
The issue is that i can't figure out how to instantiate and show the settingsWindow that i have made. I have tried to follow every thread on the internet without any success.
My Viewcontroller:
class SettingsViewController: NSViewController {
#IBOutlet var ipAddress: NSTextField!
#IBOutlet var port: NSTextField!
#IBAction func connect(sender: AnyObject) {}
override func viewDidLoad() {
super.viewDidLoad()
}
}
My AppDelegate:
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var statusMenu: NSMenu!
var statusItem: NSStatusItem?
var tcpService: TcpService = TcpService()
func applicationDidFinishLaunching(aNotification: NSNotification?) {
let bar = NSStatusBar.systemStatusBar()
statusItem = bar.statusItemWithLength(20)
statusItem!.menu = statusMenu
statusItem!.image = NSImage(byReferencingFile: NSBundle.mainBundle().pathForResource("16*16", ofType: "png"))
statusItem!.highlightMode = true
tcpService.initOutputStream("192.168.1.1", Port: 8888)
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
#IBAction func openSettings(sender: AnyObject) {
// open settings for ip and port optional port
}
}
in swift 3:
var myWindow: NSWindow? = nil
let storyboard = NSStoryboard(name: "Main",bundle: nil)
let controller: EditorViewController = storyboard.instantiateController(withIdentifier: "editorViewController") as! ViewController
myWindow = NSWindow(contentViewController: controller)
myWindow?.makeKeyAndOrderFront(self)
let vc = NSWindowController(window: myWindow)
vc.showWindow(self)
For 2022
in your normal Main storyboard, tap to add a new window controller.
tap precisely on the red "X", then the blue circle, and then enter "ExampleID" at the green entry.
in your app's ordinary main view controller, add this
variable:
var otherWindow: NSWindowController?
function:
private func otherWindow() {
let sb = NSStoryboard(name: "Main", bundle: nil)
otherWindow = sb.instantiateController(
withIdentifier: "ExampleID") as! NSWindowController
otherWindow?.showWindow(self)
}
That's it.
Call otherWindow when you want to.
Problem:
Inevitably you will want to set up the otherWindow in a certain way, example, transparent, whatever. Unfortunately this is a whole topic in itself, but you do it like this:
private func otherWindow() {
... as above ...
otherWindow?.window?.ExampleSetup()
}
and then
extension NSWindow {
func ExampleSetup() {
self.styleMask = .borderless
self.collectionBehavior = [.fullScreenPrimary]
self.level = .floating
self.isMovable = false
self.titleVisibility = .hidden
// etc etc etc ..
guard let screen = self.screen ?? NSScreen.main else {
print("what the???")
return
}
self.setFrame(screen.frame, display: true)
// consider also .visibleFrame
}
}
enum Storyboards: String {
case main = "Main"
func instantiateVC<T>(_ identifier: T.Type) -> T? {
let storyboard = NSStoryboard(name: rawValue, bundle: nil)
guard let viewcontroller = storyboard.instantiateController(withIdentifier: String(describing: identifier)) as? T else { return nil}
return viewcontroller
}
}
var ssoLoginController: IDSSOLoginViewController?
var myWindow: NSWindow? = nil
ssoLoginController = Storyboards.main.instantiateVC(IDSSOLoginViewController.self)
myWindow = NSWindow(contentViewController: ssoLoginController!)
myWindow?.makeKeyAndOrderFront(self)
let vc = NSWindowController(window: myWindow)
vc.showWindow(self)
I am not 100% that I fully understand your problem, but assuming that you are using a storyboard (you should if you are starting fresh), adding few lines to your applicationDidFinishLaunching method will help:
var myWindow: NSWindow? = nil
let storyboard = NSStoryboard(name: "Main",bundle: nil)
let controller: SettingsViewController = storyboard?.instantiateControllerWithIdentifier("SettingsViewController") as SettingsViewController
myWindow = controller.window
myWindow?.makeKeyAndOrderFront(self)
Do not forget to set the Storyboard ID in IB (in the example above to SettingsViewController)!

Resources