In Xcode, How Can I Add A Tutorial Window to My Mac App, Which Opens Only On First Launch - swift

I'm designing a MacOS app, for which I'd like to have a second window appear on the first time the app is launched (similar to what Apple does on the first time you open iMovie or Pages). I've tried a couple things to no avail. So far, I've created a second group as a target of the original app. The new group contains the tutorial storyboard I designed and a custom class called OnboardingWindowController (a subclass of NSWindowController). I gave the window controller the Storyboard ID of OnboardingWindowController, and added the following method:
class func loadFromNib() -> OnboardingWindowController {
let vc = NSStoryboard(name: "Onboarding", bundle: nil).instantiateController(withIdentifier: "OnboardingWindowController") as! OnboardingWindowController
return vc
}
I'm not sure, however, how to get this to launch without prompting the first time the app is launched and then never again. Thanks in advance!

Add a key to userdefaults check it's value, if it's false then show the tutorial else don't.
if UserDefaults.standard.bool(forKey: "didShowTutorial") {
// Code to proceed without showing tutorial
} else {
UserDefaults.standard.set(true, forKey: "didShowTutorial")
// Code to show the tutorial
}

Related

How to override Copy and Paste NSMenuItems for one View Controller Swift macOS

I am writing a macOS application with multiple view controllers, using Storyboards.
In my main View Controller I would like to be able to copy and paste data to the NSPasteboard. The data is related to buttons displayed to the user, and the exact data to be copied varies depending on which button has most recently been pressed/selected.
I would like to be able to override the standard behaviour of the Copy and Paste NSMenuItems when my main View Controller is the front most (key) window, but revert to back to standard behaviour when other windows are in the foreground, as they all contain NSTextFields which can be copied/pasted into.
I have done a lot of googling, and overriding this behaviour is not very well documented. I can achieve it globally by adding an IBAction into the App Delegate, which I could use to call a function in whichever View Controller is key, but this doesn't feel like a very elegant solution.
Currently my IBAction in the App Delegate looks like this:
#IBAction func copy(_ sender: Any) {
if let window = NSApplication.shared.keyWindow {
if let splitView = window.contentViewController as? SplitViewController {
if let controlVC = splitView.controlItem.viewController as? ControlViewController {
controlVC.copyAction(self)
}
}
}
}
Am I missing a neater solution?
Thanks,
Dan

How to set initial view controller after button tapped

I have an app that has a begin journey activity and a choose category activity. I want to make it that after the user presses the start journey button he won't be able to return there or be able with a certain button.
Set the next viewController as the root viewController as described here: How to set the rootViewController with Swift, iOS 7
It seems like you’re looking for the root view of the app to be changed to the beginning of the journey, rather than the initial home screen. This can be done by setting the rootViewController property of the app’s main window, UIApplication.shared.keyWindow.
This can be done using the following code block:
let viewController = JourneyViewController()
guard
let window = UIApplication.shared.keyWindow
else {
return
}
window.rootViewController = viewController
Please note, I work in Xcode 11 beta with iOS 13. My apologies if this doesn’t work with older versions of iOS/OS X.

How to programmatically show a window/view controller in Swift 4 for macOS application

I'm trying to programmatically show a window in my macOS application. The reason I want to do it programmatically is because the user clicks a Login button and the resulting function depends on the success of the login. If it was successful, the window is shown; otherwise, it is not.
Here is a screenshot of my Xcode window:
Here's what I'm trying in the code (Alert is a class I created to make showing NSAlert dialogs easier):
#IBAction func btnLogin_Click(_ sender: Any) {
let email = txtEmail.stringValue
if(email.isEmpty) {
Alert.show(parent: view.window!, message: "Email is required.")
return
}
let password = txtPassword.stringValue
if(password.isEmpty) {
Alert.show(parent: view.window!, message: "Password is required.")
return
}
// this does not work, even though I gave the window and view controllers
// both identifiers in the storyboard
self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("wcStores"))
}
Apple's documentation for NSStoryboard.SceneIdentifier is pretty much nonexistent, and other methods I've seen seem to pertain to earlier versions of Swift.
What am I doing wrong?
Alright, so this is not what I originally was trying to do, but this way is much better, and what I was really looking for.
let vcStores = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("vcStores"))
as! NSViewController
self.view.window?.contentViewController = vcStores
This will just replace the window's contents with the contents in the view vcStores as defined in Interface Builder.
This video also helped, albeit for iOS and Swift 3. I was trying to create a new NSWindow at first because I'm so used to doing desktop development in Java Swing or C# Windows Forms. But I love how easy it is to just switch out the window contents like this.

Display windows present in different storyboards

I'm using Xcode 8 and storyboards for mac development.
I have 2 storyboards with NSWindowController in each of them.
1). How can I display both windows when the app is launched?
2). How can I display window on pressing button on other window?
Edit:
Code:
func applicationDidFinishLaunching(_ aNotification: Notification)
{
let st = NSStoryboard(name: "Logs", bundle: nil) // Logs is my storyboard name
var logWindow: NSWindowController? = nil
logWindow = st.instantiateInitialController() as! NSWindowController?
if logWindow?.window?.isVisible == false
{
logWindow?.window?.setIsVisible(true)
}
}
Everything is correct but the problem is with logWindow.
I assume, you know that the local variable is dead after the execution of method.
As you can see logWindow is a local variable. When applicationDidFinishLaunching(_ aNotification: Notification) is executing, the variable is alive, a new window is created and also displayed.
When applicationDidFinishLaunching(_ aNotification: Notification) is finished executing, your variable logWindow is not alive. So, the allocated memory is de-allocated automatically (by ARC) and the window is destroyed/de-allocated.
All this is happening so fast and it is the reason you are not seeing the window inspite of isVisible() returning true(it returns true because the method is still in execution and logWindow is still alive).
So just make logWindow a variable of class(in what ever class you are trying to display the window) and you are good to go.
Both questions boil down to pretty much the same thing: How can I display a window from a storyboard? Whether you want to do it when the app is launched or when the user clicks a button shouldn't really affect what you need to do to display the window.
Take a look at the NSStoryboard class and you'll find methods for instantiating a window controller from a storyboard. So, create an instance of NSStoryboard if you don't already have one, and then use that to instantiate the window controller in question. For example, if your window controller is called "My Window Controller" and is located in the main storyboard...
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateController(withIdentifier: "My Window Controller") as! NSWindowController
Then do whatever you like with windowController. If you want to display two windows when the app is launched, then maybe your app delegate has two properties for keeping track of the corresponding window controllers. In your app delegate's applicationDidFinishLaunching(notification:) method, you could create two windows using code like I've shown and assign the window controllers to those windows. If you want to open a window when the user clicks a button, put code like the code above in the button's action, and store a reference to the resulting window controller in some appropriate object, possibly the window controller where the action appears.

Change key to true then show view in swift

OK, so I've added a view onto my Application that asks the user to accept or decline the Terms of Service. I have it so when they click accept, it changes the key "TermsAccepted" to true. If they close the app, and re-open it, it gives them access. However I'd like to be able to give them access without re-opening the app first.
So in my ViewController (Main Screen), in my viewDidLoad I have the following:
if NSUserDefaults.standardUserDefaults().boolForKey("TermsAccepted") {
// They've been accepted, do nothing.
} else {
let termsView = self.storyboard?.instantiateViewControllerWithIdentifier("FirstLaunchTerms") as! FirstLaunchTermsView
self.presentViewController(termsView, animated: true, completion: null
}
In the 'LaunchTermsView' I have the following code for when they accept the terms.
#IBAction func acceptTerms(sender : AnyObject)
{
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "TermsAccepted")
}
And thi works fine, but they have to re-open the application.
I tried to just have it so the button opens the Main View at the same time as those terms are accepted (After the key is updated) but it gives me the following error.
fatal error: unexpectedly found nil while unwrapping an Optional value (lldb)
I assumed this meant that it was wanting to re-open the launch terms view again, so I tried to move all the code from viewDidLoad to viewWillAppear so it checks each time, but it just gives the same error. (it was a long shot try before I posted on here).
I had a look at some posts on here, but a lot of them were in ObjC or just didn't give me a solution or any form of help to trying to find one myself.
As you're presenting your terms view controller you should simply be able to dismiss it when you're done with it. You don't show any other code so I'm assuming that your 'home' view controller is waiting to be revealed underneath (you don't need to try to show it again).