Display a view for few seconds (custom push from NavController) - xcode

I started with Master-Detail Application. Now I try to add a custom View (default UIViewController with simple Label) which will be displayed for some seconds (like a loading view) before detail/master views will be accessible.
My Problem now is that if I add my ViewController to AppDelegate and push to it from NavigationController i get an empty view.
See here:
I didn't change anything but adding just one line of code to appdelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let splitViewController = self.window!.rootViewController as UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
splitViewController.delegate = self
let masterNavigationController = splitViewController.viewControllers[0] as UINavigationController
let controller = masterNavigationController.topViewController as MasterViewController
controller.managedObjectContext = self.managedObjectContext
// +++ Custom ViewController is to be displayed from here +++
navigationController.pushViewController(ViewController(), animated: true)
return true
}
I do not get where the problem is...

With this line in your code
navigationController.pushViewController(ViewController(), animated: true)
you are instantiating a new, empty view controller, whis is probably not what you want.
Get a reference to your storyboard (i.e from your masterviewcontroller) to create the view controller and push this one on the navigation stack. Example:
if let myVC = controller.storyboard!.instantiateViewControllerWithIdentifier(...) {
navigationController.pushViewController(myVC, animated: true)
}

Related

Xcode 11 beta: AppDelegate file doesn't have window global variable

I am facing an issue in Xcode 11 beta.
The problem is that I am not getting default window variable declared in AppDelegate file.
Is anybody facing this same issue?
In Xcode 11 this is now done in SceneDelegate
EDIT: if you’re still supporting iOS 12 or prior (or want to support 13 plus prior)
Add the UIWindowSceneDelegate to ApplicationDelegate
Add : var window:UIWindow?
Then proceed as you would to setting up in didFinishLaunching
The default var window: UIWindow? Is now moved in SceneDelegate.swift. To set a rootViewController in Xcode 11 you can work within SceneDelegate.swift file, In the scene delegate, you must create the window instance and the root view controller like below:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// set or create your viewController here
let yourViewController = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(identifier: "yourViewController") as! YourViewController
// set the rootViewController here using window instance
self.window?.rootViewController = yourViewController
}
Also this answer is helpful : Why is manually setup root view controller showing black screen?
Hope it will help you!
My approach:
first, in AppDelegate create a static property with the class of the view controller you want to access, like this
class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: Home
static var homeViewController: HomeViewController?
...
}
then, in the view controller
// MARK: - Managing the view
override func viewDidLoad() {
super.viewDidLoad()
// Singleton
AppDelegate.homeViewController = self
...
}
how to use:
extension UIViewController {
var homeViewController: HomeViewController? {
if let controller = self as? HomeViewController {
return controller
}
return AppDelegate.homeViewController
}
}
In my case, that's all it takes.
var window: UIWindow? // add this by yourself
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: Device.bounds)
let root = DDTabBarController()
window?.rootViewController = root
window?.makeKeyAndVisible()
return true
}

Cannot add viewcontroller´s frame to a scrollview in another viewcontroller

I want to add a swipe function to my app and swipe between viewcontrollers as in this tutorial: https://www.youtube.com/watch?v=3jAlg5BnYUU
The source code for the tutorial is here: https://www.veasoftware.com/posts/swipe-navigation-in-swift-xcode-7-ios-9-tutorial
In that guide they are using nib/xib files that they create together with the corresponding viewcontrollers that they then use to call for in their ViewController´s constructors.
I have done exact the same and it is wokring as in the tutorial. But I also want use a ViewController that not has a xib file because it is on my storyboard. That viewcontroller is called ViewController2 in my code below and it is the only that does not appear in the scrollview when I swipe. The other two do because I call for the xibName in the constructor call but ViewController2 does not have a xib file since it is on my storyboard.
So how can I add it to the scrollview so it appears when I swipe?
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let vc0 = ViewController0(nibName: "ViewController0", bundle: nil)
self.addChildViewController(vc0)
self.scrollView.addSubview(vc0.view)
vc0.didMoveToParentViewController(self)
let vc1 = ViewController1(nibName: "ViewController1", bundle: nil)
var frame1 = vc1.view.frame
frame1.origin.x = self.view.frame.size.width
vc1.view.frame = frame1
self.addChildViewController(vc1)
self.scrollView.addSubview(vc1.view)
vc1.didMoveToParentViewController(self)
let vc2 = ViewController2()
var frame2 = vc2.view.frame
frame2.origin.x = self.view.frame.size.width
vc2.view.frame = frame2
self.addChildViewController(vc2)
self.scrollView.addSubview(vc2.view)
vc2.didMoveToParentViewController(self)
self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width * 3, self.view.frame.size.height - 66);
}
Try doing the following:
1) Getting the storyboard object programmatically.
2) Giving the View Controller 2 in your storyboard file a storyboard ID. You can do this by going to your Main.storyboard file and selecting the view controller you wish to give an identifier and then looking in the Identity Inspector (It should be in the under "Identity" section.
3) Instantiating an instance of View Controller 2 with the storyboard method instantiateViewControllerWithIdentifier. This method uses a storyboard ID as it's argument and adding that as the child of your scroll view.
Like so:
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
//1
let mainStoryboard = UIStoryboard.init(name: "Main", bundle: NSBundle.mainBundle())
//3
let vc2 = mainStoryboard.instantiateViewControllerWithIdentifier("<InsertStoryBoardID>") as! ViewController2
self.addChildViewController(vc2)
self.scrollView.addSubview(vc2.view)
vc2.didMoveToParentViewController(self)
self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width * 3, self.view.frame.size.height - 66);
}
Hope this helps! Feel free to ask questions!

how to presentViewControllerAsSheet on OSX Mavericks?

It's a long story, but to cut it short; my first OSX app was written (on Yosemite) in Swift using a storyboard until I found out my (finished) app will not run on Mavericks. I need to run on Mavericks, so I have replaced the storyboard with NIBs.
My problem is with the segues; I was using 'sheet type' segues to show other view controllers in a sheet over the main view controller. A call to the presentViewControllerAsSheet method of NSViewController is a good replacement as it looks the same, but this API was introduced in Yosemite - so I need to work out how to do this for Mavericks.
In the action for a button on the main view, I've tried using beginSheet like this:
secondViewController = SecondViewController(nibName: "SecondViewController", bundle: nil)
self.view.window?.beginSheet(secondViewController!view.window!, completionHandler: nil)
But the second view controller's window is null at runtime. I've tried adding the new view controller as a subview to the application window but this is an unrecognised selector:
NSApplication.sharedApplication().windows[0].addSubView(secondViewController!.view)
I've search high and low for a description of how to show a sheet and all I can find is: Can a view controller own a sheet? but I'm sorry to admit I don't understand the answer. Can anybody help me with some working code? I'm beginning to worry that I'm trying to do something unusual but it looks OK on Yosemite, so how did people do this before Yosemite was released?
EDIT
I still haven't got to the solution, so I have put together a small app which shows the problems I'm having.
In AppDelegate.swift:
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var mainViewController: FirstView!
func applicationDidFinishLaunching(aNotification: NSNotification) {
mainViewController = FirstView(nibName:"FirstView", bundle: nil)
window.contentView = mainViewController.view
mainViewController.view.frame = (window.contentView as! NSView).bounds
}
}
In FirstView.swift (associated NIB has a 'open sheet' button)
class FirstView: NSViewController {
var secondView: SecondView?
var secondWindow: SecondWinCon?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func pressButton(sender: AnyObject) {
secondView = SecondView(nibName: "SecondView", bundle: nil)!
// method 1 - this is the behaviour I want (but it only works on OSX 10.10)
// presentViewControllerAsSheet(secondView!)
// method 2 - this just creates a floating window
// self.view.addSubview(secondView!.view)
// self.view.window?.beginSheet(secondView!.view.window!, completionHandler: nil)
// method 3 - this also creates a floating window
secondWindow = SecondWinCon(windowNibName: "SecondWinCon")
self.view.window?.beginSheet(secondWindow!.window!, completionHandler: nil)
}
}
In SecondView.swift (associated NIB has a 'close' button)
class SecondView: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func dismissPressed(sender: AnyObject) {
if (presentingViewController != nil) {
presentingViewController?.dismissViewController(self)
} else {
self.view.window?.sheetParent?.endSheet(self.view.window!)
}
}
}
In SecondWinCon.swift (Associated NIB is empty)
class SecondWinCon: NSWindowController {
var secondView: SecondView?
override func windowDidLoad() {
super.windowDidLoad()
secondView = SecondView(nibName: "SecondView", bundle: nil)!
self.window?.contentView.addSubview(secondView!.view)
}
}
If method 1 is uncommented, you will see the behaviour I'm trying to emulate (remember it only works on OS X 10.10). Method 2 or 3 displays the second view, but not as a sheet.
I have the same problem, and found maybe is't an issue related to view life cycle.
When I call presentViewControllerAsSheet in viewDidLoad, sheet will not shown, and you will get this in console:
Failed to set (contentViewController) user defined inspected property on (NSWindow): presentViewController:animator:: View '''s view is not in a window/view hierarchy.
If you trigger this in viewWillAppear or viewDidAppear, it's totally no problem.
UPDATE
Okay, let's make it clear.
For this initial storyboard, NSWindowController is connected with a view controller, think this as a root view controller (RootVC).
Create another view controller desired as a sheet in storyboard (SheetVC).
in viewWillAppear or viewDidAppear of RootVC, [self presentViewControllerAsSheet: SheetVC]
The sheet will show, no additional code required.
If you get here looking for a solution, I was nearly there with method 3. The important step I had missed was to turn off "Visible At Launch" in the NSWindowController's NIB (it's an attribute of the NSWindow). In my sample code, this was in SecondWinCon.nib.

Connect to ViewController from AppDelegate (Swift)

I have created a new OS X Cocoa Application using the standard Xcode Swift template (using StoryBoards).
I have implemented an IBAction in AppDelegate.swift to handle when the users selects "Open..." from the "File" menu. If the chosen file is a valid image file, I create an NSImage which I then want to display in the view of ViewController.
#IBAction func openFile(sender: NSMenuItem) {
var openPanel = NSOpenPanel()
openPanel.beginWithCompletionHandler { (result :Int) -> Void in
if result == NSFileHandlingPanelOKButton {
if let imageURL = openPanel.URL {
let image = NSImage(contentsOfURL: imageURL)
// PRESENT image IN THE VIEW CONTROLLER
}
}
}
However, I don't see any way to connect to ViewController from AppDelegate. I have only managed to find suggestions that I should look at self.window! in AppDelegate, but there is no such thing as a window in AppDelegate.
Thanks,
Michael Knudsen
It seems that AppDelegate can connect to objects only within Application Scene in a storyboard. If you want to get a ViewController, instantiate it from a storyboard.
sample:
#IBAction func menuAction(sender: AnyObject) {
if let storyboard = NSStoryboard(name: "Main", bundle: nil) {
let controller = storyboard.instantiateControllerWithIdentifier("VC1") as NSViewController
if let window = NSApplication.sharedApplication().mainWindow {
window.contentViewController = controller // just swap
}
}
}
You can access the mainWinow property and the contentViewController property to create a reference to your custom ViewController class. This is similar to the iOS rootViewController property.
let rootViewController = NSApplication.shared().mainWindow?.windowController?.contentViewController as! ViewController
Now you can use this reference to access IBOutlets on your main storyboard from your AppDelegate.
rootViewController.myTextView.textStorage?.mutableString.setString("Cats and dogs.")
This is good for a simple app with one Window with one ViewController.
I was stuck trying to do this same thing recently and managed to get the event I needed to update my view by creating the #IBAction in my ViewController and control dragging to my Application's First Responder (above the menu in my storyboard view).
Here's the question that got me out of the woods:
Application Menu Items Xcode
And thanks to Bluedome for the suggestion to connect it to First Responder's action.
If you control-drag from the menu to the first responder (red cube above menu) and picked an existing action, then you can "responder chain" to your view controller. In my case I attached Open to openFile and then in my view controller I added the following
override var acceptsFirstResponder: Bool {
return true
}
func openFile(sender: NSMenuItem) {
print("In view controller")
}
and it worked without any changes in AppDelegate. Most of the menus are already hooked up to first responder so just add the matching function name in your view controller.
See this comment and this document on Event Handling Basics for more info.
In Swift 5 and accessing new windows array:
#IBAction func menuAction(sender: AnyObject) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateInitialViewController()
// The windows in the array are ordered from back to front by window level;
// thus, the last window in the array is on top of all other app windows.
// On app launch, UIApplication.shared.windows.count == 1 anyway.
if let window = UIApplication.shared.windows.last {
window.rootViewController = controller
}
}

How to present a modal atop the current view in Swift

(Xcode6, iOS8, Swift, iPad)
I am trying to create a classic Web-like modal view, where the outside of the dialog box is "grayed-out." To accomplish this, I've set the alpha value of the backgroundColor of the view for the modal to 0.5, like so:
self.view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5)
The only problem is that when the modal becomes full-screen, the presenting view is removed. (Ref Transparent Modal View on Navigation Controller).
(A bit irritated at the concept here. Why remove the underlying view? A modal is, by definition, to appear atop other content. Once the underlying view is removed, it's not really a modal anymore. it's somewhere between a modal and a push transition. Wa wa wa... Anyway..)
To prevent this from happening, I've set the modalPresentationStyle to CurrentContext in the viewDidLoad method of the parent controller, and in Storyboard... but no luck.
self.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
self.navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
How do I prevent the presenting view from being removed when the modal becomes full screen?
tyvm.. more info below.
Also in Storyboard, like so (Presentation: Current Context)
Thx for your help... documentation below:
First, remove all explicit setting of modal presentation style in code and do the following:
In the storyboard set the ModalViewController's modalPresentation style to Over Current context
Check the checkboxes in the Root/Presenting ViewController - Provide Context and Define Context.
They seem to be working even unchecked.
You can try this code for Swift:
let popup : PopupVC = self.storyboard?.instantiateViewControllerWithIdentifier("PopupVC") as! PopupVC
let navigationController = UINavigationController(rootViewController: popup)
navigationController.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
self.presentViewController(navigationController, animated: true, completion: nil)
For swift 4 latest syntax using extension:
extension UIViewController {
func presentOnRoot(`with` viewController : UIViewController){
let navigationController = UINavigationController(rootViewController: viewController)
navigationController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.present(navigationController, animated: false, completion: nil)
}
}
How to use:
let popup : PopupVC = self.storyboard?.instantiateViewControllerWithIdentifier("PopupVC") as! PopupVC
self.presentOnRoot(with: popup)
The only problem I can see in your code is that you are using CurrentContext instead of OverCurrentContext.
So, replace this:
self.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
self.navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
for this:
self.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
self.navigationController.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
This worked for me in Swift 5.0. Set the Storyboard Id in the identity inspector as "destinationVC".
#IBAction func buttonTapped(_ sender: Any) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let destVC = storyboard.instantiateViewController(withIdentifier: "destinationVC") as! MyViewController
destVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
destVC.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(destVC, animated: true, completion: nil)
}
The problem with setting the modalPresentationStyle from code was that you should have set it in the init() method of the presented view controller, not the parent view controller.
From UIKit docs: "Defines the transition style that will be used for this view controller when it is presented modally. Set
this property on the view controller to be presented, not the presenter. Defaults to
UIModalTransitionStyleCoverVertical."
The viewDidLoad method will only be called after you already presented the view controller.
The second problem was that you should use UIModalPresentationStyle.overCurrentContext.
The only way I able to get this to work was by doing this on the presenting view controller:
func didTapButton() {
self.definesPresentationContext = true
self.modalTransitionStyle = .crossDissolve
let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "YourViewController") as! YourViewController
let navController = UINavigationController(rootViewController: yourVC)
navController.modalPresentationStyle = .overCurrentContext
navController.modalTransitionStyle = .crossDissolve
self.present(navController, animated: true, completion: nil)
}
I am updating a simple solution. First add an id to your segue which presents modal. Than in properties change it's presentation style to "Over Current Context". Than add this code in presenting view controller (The controller which is presenting modal).
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let Device = UIDevice.currentDevice()
let iosVersion = NSString(string: Device.systemVersion).doubleValue
let iOS8 = iosVersion >= 8
let iOS7 = iosVersion >= 7 && iosVersion < 8
if((segue.identifier == "chatTable")){
if (iOS8){
}
else {
self.navigationController?.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
}
}
}
Make sure you change segue.identifier to your own id ;)

Resources