Popup window in OS X App - swift

I have a little problem with app and looking for the best solution. I want to create an popup window that have separate logic in other NSViewController.
The case is - I click on the button in my window and then other window appears with some NSTextField's and custom design. I'm considering what component is the best for my issue.
something like this:
my view
Thank you in advance for any help !

If have done it with the following code:
let addMyPopover = NSPopover()
if let popOverController = aViewController(xyz: data) {
popOverController.closeDelegate = self
addMyPopover.behavior = .Transient
addMyPopover.contentViewController = popOverController
addMyPopover.contentSize = CGSizeMake(450, 380)
addMyPopover.showRelativeToRect(addButton.bounds, ofView: addPhaseButton, preferredEdge: NSRectEdge.MinY)
}
extension MyController: closeViewDelegate {
func closePopover(sender: AnyObject?) {
addMyPopover.performClose(sender)
}
}
protocol closeViewDelegate: class {
func closePopover(sender: AnyObject?)
}
aViewController is from type NSViewController

#IBAction func showPopover(_ sender: NSButton) {
let vcPopover = NSStoryboard(name: "Main", bundle: nil).instantiateController(withIdentifier: "viewControllerPop") as! NSViewController
self.presentViewController(vcPopover, asPopoverRelativeTo : sender.bounds, of : sender, preferredEdge: .maxY, behavior: .transient)
}

Related

How to check if the window is open or not?

I am opening a window say "Window2" from main menu item(Command 2 under Window) . So, if the menu item is clicked again it opens the "Window2" again instead of making the existing window the key window and disabling corresponding menu item. Below is my effort on implementing that in my AppDelegate class, as variable "lmWindowController" always returning nil, instances of window2 is getting opened multiple times, what am I missing here?
class AppDelegate: NSObject, NSApplicationDelegate {
var lmWindowController: LocalisationMappingWindowController?
func applicationDidFinishLaunching(_ notification: Notification) {
}
#IBAction func showLocalisationMappingWindowController(_ sender: Any) {
if (!(lmWindowController != nil)) {
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "LocalisationMapping"), bundle: Bundle.main)
if let windowController: NSWindowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "LocalisationMappingWindowController")) as? NSWindowController{
windowController.showWindow(windowController.window)
windowController.window?.makeKeyAndOrderFront(nil)
}
} else {
lmWindowController?.window?.makeKeyAndOrderFront(nil)
}
}
override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
var enable = self.responds(to: menuItem.action)
if menuItem.action == #selector(showLocalisationMappingWindowController(_:)) {
for window in NSApplication.shared.windows {
if window.title == "Localization Mapping" {
enable = false
}
}
}
return enable
}
}
In showLocalisationMappingWindowController a new windowController is loaded but lmWindowController isn't changed. Add lmWindowController = windowController.
#IBAction func showLocalisationMappingWindowController(_ sender: Any) {
if (!(lmWindowController != nil)) {
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "LocalisationMapping"), bundle: Bundle.main)
if let windowController: NSWindowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "LocalisationMappingWindowController")) as? NSWindowController{
windowController.showWindow(windowController.window)
windowController.window?.makeKeyAndOrderFront(nil)
lmWindowController = windowController
}
} else {
lmWindowController?.window?.makeKeyAndOrderFront(nil)
}
}

Can not remove child coordinator because transitionCoordinator is nil in navigationController delegate method

Brief :
I have implemented Soroush's Coordinators architecture. Everything works fine except the removing part which is needed to remove previous(child) coordinators.
Scenario :
I have two ViewController named HomeViewController and MyGroupsViewController. Each has its own coordinator named HomeCoordinator and MyGroupsCoordinator respectively.
User taps a button on HomeViewController which triggers gotoMyGroupsTapped function and gets the user to MyGroupsViewController, Then the user taps on another button on MyGroupsViewController which get the user back to HomeViewController by triggering gotoHomePage().
Pretty simple! : HomeVC -> MyGroupsVC -> HomeVC
But the Problem is :
navigationController.transitionCoordinator? is nil in func navigationController(..., didShow viewController: UIViewController...) in both coordinators and I can not remove child coordinators in each transition.
Is it correct to set navigationController.delegate = self in start() func of both coordinators?
Should I use navigationController?.popViewController(animated: false ) in my backToHomePage() func? because Paul Hudson has only used pushViewController.
My Codes [Simplified Versions]:
HomeCoordinator.swift
import Foundation
import UIKit
class HomeCoordinator: NSObject,Coordinator,UINavigationControllerDelegate {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
weak var parentCoordinator : Coordinator?
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
// Transition here is nil
print(" Transition : ",navigationController.transitionCoordinator)
guard let fromViewController = navigationController.transitionCoordinator?.viewController(forKey: .from) else {
print("Unknown fromViewController!")
return
}
// Removing a child coordinator
}
func gotoMyGroups (){
let groupsCoordinator = GroupsCoordinator(navigationController: navigationController)
childCoordinators.append(groupsCoordinator)
groupsCoordinator.parentCoordinator = self
groupsCoordinator.start()
}
func start() {
let vc = HomeViewController.instantiate()
vc.coordinator = self
navigationController.delegate = self
navigationController.pushViewController(vc, animated: false)
navigationController.setNavigationBarHidden(true, animated: false)
}
}
MyGroupsCoordinator.swift
import Foundation
import UIKit
class MyGroupsCoordinator: NSObject,Coordinator,UINavigationControllerDelegate {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
weak var parentCoordinator : Coordinator?
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
// Transition here is nil
print(" Transition : ",navigationController.transitionCoordinator)
guard let fromViewController = navigationController.transitionCoordinator?.viewController(forKey: .from) else {
print("Unknown fromViewController!")
return
}
// Removing a child coordinator
}
func start() {
let vc = MyGroupViewController.instantiate()
vc.coordinator = self
navigationController.delegate = self
navigationController.pushViewController(vc, animated: false)
navigationController.setNavigationBarHidden(true, animated: false)
}
}
MyGroupViewController.magik
class MyGroupViewController : UIViewControllerWithCoordinator,UITextFieldDelegate,Storyboarded{
#IBAction func gotoHomePage(_ sender: Any) {
if let coord = coordinator as? GroupsCoordinator {
coord.parentCoordinator?.start()
}
}
}
HomeViewController.swift
class HomeViewController: UIViewControllerWithCoordinator,Storyboarded {
#IBAction func gotoMyGroupsTapped(_ sender: Any) {
guard let acoordinator = coordinator as? HomeCoordinator else {
return
}
acoordinator.gotoMyGroups()
}
It looks to me there is a confusion around Coordinator pattern usage here.
From your expected flow HomeVC -> MyGroupsVC -> HomeVC, if you mean in the sense level1 -> level2 -> level3, then GroupsCoordinator should create a new HomeCoordinator instance with its own new HomeVC.
So instead of your previous code
class MyGroupViewController ... {
#IBAction func gotoHomePage(_ sender: Any) {
if let coord = coordinator as? GroupsCoordinator {
coord.parentCoordinator?.start()
}
}
}
I would change it to
class MyGroupViewController ... {
#IBAction func gotoHomePage(_ sender: Any) {
if let coord = coordinator as? GroupsCoordinator {
coord.goToHome()
}
}
}
class MyGroupsCoordinator ... {
func goToHome() {
let homeCoordinator = HomeCoordinator(navigationController: navigationController)
childCoordinators.append(homeCoordinator)
groupsCoordinator.parentCoordinator = self
groupsCoordinator.start()
}
}
This will allow you to create a brand new page as you describe there HomeVC -> MyGroupsVC -> HomeVC.
However, if you meant in this approach level1 -> level2 -> (back) level1, then you'll need to terminate MyGroupsCoordinator and remove from the parent while navigating back.
As you noticed, to do so, you'll need to use UINavigationControllerDelegate to be able to be notified when the user navigate back (either pop in code, or with classic back button).
One solution I found is to use a Router to handle all this navigation when a UIViewController is removed from it to also notify via closures the right coordinator to be removed. You can read more about it here.
Hope it helps

Show and hide window instead of terminating app on close click in cocoa app

I have been trying to show hide window on close(red button) click on the window. I would like to do is hide the window and when a user clicks on my app again it will show again.
Thanks in advance to all the developers who provide an answer. I am new to Cocoa apps. I am iOS developers so I have not much knowledge about cocoa apps.
I tried to hide(:) method and orderOut(:) method too. but not working.
class ViewController : NSViewController, NSWindowDelegates {
override func viewDidAppear() {
self.view.window?.delegate = self
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
//NSApplication.shared.terminate(self)
//NSApp.hide(self)
//self.view.window?.orderOut(sender)
return false
}
}
I want to create a timer app which runs in the background if user click on close it will hide instead of terminating. and when he clicks again from dock menu it will reopen the window.
I'm not much into Mac OS development, but I think you should inherit from NSWindowController like this:
class MyWindowController: NSWindowController, NSWindowDelegate {
func windowShouldClose(_ sender: NSWindow) -> Bool {
NSApp.hide(nil)
return false
}
}
Then you need just open your Main (or whatever name you have) storyboard, choose Window Controller and set your MyWindowController to it:
I tried and it worked for me.
I have found solution. Thanks to #Silvester suggestion to present NSViewController.
On button click event :
#IBAction func onButtonClick(_ sender: VSButton) {
let animator = ReplacePresentationAnimator()
let vc = self.storyboard?.instantiateController(withIdentifier: "identifier") as! yourVC
present(vc, animator: animator)
}
Custom animator class :
class ReplacePresentationAnimator: NSObject, NSViewControllerPresentationAnimator {
func animatePresentation(of viewController: NSViewController, from fromViewController: NSViewController) {
if let window = fromViewController.view.window {
NSAnimationContext.runAnimationGroup({ (context) -> Void in
fromViewController.view.animator().alphaValue = 0
}, completionHandler: { () -> Void in
viewController.view.alphaValue = 0
window.contentViewController = viewController
viewController.view.animator().alphaValue = 1.0
})
}
}
func animateDismissal(of viewController: NSViewController, from fromViewController: NSViewController) {
if let window = viewController.view.window {
NSAnimationContext.runAnimationGroup({ (context) -> Void in
viewController.view.animator().alphaValue = 0
}, completionHandler: { () -> Void in
fromViewController.view.alphaValue = 0
window.contentViewController = fromViewController
fromViewController.view.animator().alphaValue = 1.0
})
}
}
}
This will work perfectly with Silvester MyWindowController. Thank you.

Cocoa Xcode: Open Window Controller from AppDelegate Programmatically using Swift 4 for Mac?

I am making a simple menu bar app which has 2 items - Preferences & Quit
I want to open a new Window when I click on Preferences
Currently I have
func applicationDidFinishLaunching(_ aNotification: Notification) {
constructMenu()
}
func constructMenu() {
let menu = NSMenu()
menu.addItem(NSMenuItem(
title: "Preferences...",
action: #selector(AppDelegate.preferencesWindow(_:)),
keyEquivalent: "P"))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(
title: "Quit",
action: #selector(NSApplication.terminate(_:)),
keyEquivalent: "q"))
statusItem.menu = menu
}
#objc func preferencesWindow(_ sender: Any) {
print("open preference window here")
if let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) {
let controller = storyboard.instantiateControllerWithIdentifier("preferencesWindow")
as NSViewController
if let window = NSApplication.shared.mainWindow {
window.contentViewController = controller // just swap
}
}
}
But it throws error on this line
if let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) {
stating
Initializer for conditional binding must have Optional type, not 'NSStoryboard'
When I click Preferences the print statement gets logged but I don't know how to open Window programatically.
I have just dragged Window Controller from Object Library & given StoryBoard ID a value of preferencesWindow
I also tried the following because of the above error
#objc func preferencesWindow(_ sender: Any) {
print("open preference window here")
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
let controller = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "preferencesWindow")) as! NSViewController
if let window = NSApplication.shared.mainWindow {
window.contentViewController = controller // just swap
}
}
but it only logs & never opens a window. What should I do?
I find answers fast when I post a question on SO. Here's what worked for me
#objc func preferencesWindow(_ sender: Any) {
var myWindow: NSWindow? = nil
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"),bundle: nil)
let controller = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "preferencesWindow")) as! NSViewController
myWindow = NSWindow(contentViewController: controller)
NSApp.activate(ignoringOtherApps: true)
myWindow?.makeKeyAndOrderFront(self)
let vc = NSWindowController(window: myWindow)
vc.showWindow(self)
}

How do I pass data from a View controller into my pop up view controller (swift/ios)

I'm quite new with Swift and I'm making this mini game type app that counts the score and updates the label in the view controller. I want to pass that score from a view controller into another external pop up view controller I created.
#IBAction func Button7Tapped(_ sender: AnyObject)
{
if Index == 13 {
game.score += 1
} else {
let scorepopVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "finalScorePop") as! finalScoreViewController
self.addChildViewController(scorepopVC)
scorepopVC.view.frame = self.view.frame
self.view.addSubview(scorepopVC.view)
scorepopVC.didMove(toParentViewController: self)
}
updateGame()
}
Above is my code for the external pop up view controller I created, which also has a separated .swift file. How would I go about taking my game.score and passing that into my Popup view controller?
In your finalScoreViewController swift file add a new property.
final class FinalScoreViewController: UIViewController {
var score: Int?
}
And then just assign it when you're instantiating it.
#IBAction func Button7Tapped(_ sender: AnyObject) {
if Index == 13 {
game.score += 1
} else {
let scorepopVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "finalScorePop") as! finalScoreViewController
scorepopVC.score = game.score //THIS LINE
self.addChildViewController(scorepopVC)
scorepopVC.view.frame = self.view.frame
self.view.addSubview(scorepopVC.view)
scorepopVC.didMove(toParentViewController: self)
}
updateGame()
}
It is better to use storyboard to open the ViewController. In storyboard, right click and drag from you button to the second view controller (the one that you wish to open).
Choose the segue type that you wish to use. In your case, I think Present Modally will work fine.
You will see a line between the two UIViewControllers in storyboard. That is the segue. Tap on it. In the Attributes inspector give the segue an identifier. For instance "myFirstSegue".
Then in the code of the UIViewController that contains your button override prepare(for:sender:). This method is called when preparing for the segue to happen. I.o.w when you tap on the button. You have access to the destination UIViewController and can therefor access and set the properties on it.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "myFirstSegue" {
if let vc = segue.destination as? MyViewController {
//here you set your data on the destination view controller
vc.myString = "Hello World"
}
}
}
Note that we check the identifier, because all segues that go from this ViewController to other ViewControllers will call prepare(for:sender:)
It's quite simple, Just add a property in your finalScoreViewController (if you are not already done this) and -for example- call it score:
class finalScoreViewController: UIViewController {
var score: String?
// ...
Add this line to the Button7Tapped action (where you set a value for finalScoreViewController's score):
let scorepopVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "finalScorePop") as! finalScoreViewController
// add this line:
scorepopVC.score = "My score"
self.addChildViewController(scorepopVC)
scorepopVC.view.frame = self.view.frame
self.view.addSubview(scorepopVC.view)
scorepopVC.didMove(toParentViewController: self)
Finally, in finalScoreViewController:
override func viewDidLoad() {
super.viewDidLoad()
if let scr = score {
print(scr)
}
}
Hope that helped.
You do not actually have to pass the variable to the next view controller. All you have to do is create a variable outside of the View Controller class, and voila, you can access your variable from anywhere, in any swift file. For example:
var score = 0
class ViewController: UIViewController {
override func viewDidLoad(){
super.viewDidLoad()
}
#IBAction func Button7Tapped(_ sender: AnyObject){
score += 1
}
}
And then in the other View Controller, you would have something like this:
#IBOutlet weak var scoreLabel: UILabel!
class ViewController: UIViewController {
override func viewDidLoad(){
super.viewDidLoad()
var timer1 = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateScore), userInfo: nil, repeats: true)
}
#objc func updateScore() {
scoreLabel.text = "You have \(score) points!"
}