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)
}
}
Related
I have some problems displaying a viewcontroller in my IOS app.
Sometimes it works and the view is displayed, but sometimes and I guess when the context is a bit different it will not work. No errors or warnings in the debugger and it can find the ViewController from the Main storyboard (at least it is not nil)
It use to work with self.present but that seems not to work anymore.
#IBAction func showHistoryButton(_ sender: MDCButton) {
let exercisesHistoryVC = ExercisesHistoryViewController.instantiate(from: .Main)
exercisesHistoryVC.modalPresentationStyle = .fullScreen
let appDeligate = UIApplication.shared.delegate as! AppDelegate
appDeligate.window?.rootViewController!.present(exercisesHistoryVC,animated: true,completion: nil)
// parent?.present(exercisesHistoryVC, animated: true, completion: nil)
}
Use the code like below,while Present New View Controller
#IBAction func showHistoryButton(_ sender: MDCButton) {
let exercisesHistoryVC = ExercisesHistoryViewController.instantiate(from: .Main)
exercisesHistoryVC.modalPresentationStyle = .fullScreen
UIApplication.topViewController()?.present(exercisesHistoryVC, animated: false, completion: nil)
}
extension UIApplication {
static func topViewController(base: UIViewController? = UIApplication.shared.delegate?.window??.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
return topViewController(base: selected)
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
I am making an app that will display a random quote from a stoic philosopher. Right now, I am stuck on trying to make the correct picture pop up. (User clicks on a Button with the philosopher's name on it, and then a new view pops up with an image of the philosopher and a random quote by him).
class ViewController: UIViewController {
var allQuotes = [String]()
var pictures = [String]()
#IBOutlet var Epictetus: UIButton!
#IBOutlet var Seneca: UIButton!
#IBOutlet var MarcusAurelius: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Create a constant fm and assign it the value returned by FileManager.default (built in system type)
let fm = FileManager.default
// Declares a new constant called path that sets the resource path of ours apps buddle.
// A bundle is a directory containing our compiled program and all our assets
let path = Bundle.main.resourcePath!
// items array will be a constant collection of the names of all the files found in the directory of our app
let items = try! fm.contentsOfDirectory(atPath: path)
// create a loop to go through all of our items...
for item in items {
if item.hasSuffix("jpg"){
pictures.append(item)
}
}
print(pictures)
title = "Stoicism"
if let stoicQuotesURL = Bundle.main.url(forResource: "quotes", withExtension: "txt"){
if let stoicQuotes = try? String(contentsOf: stoicQuotesURL) {
allQuotes = stoicQuotes.components(separatedBy: "\n\n")
}
}
}
#IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 0 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[0]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 1 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[1]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 2 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[2]
navigationController?.pushViewController(vc, animated: true)
}
}
}
}
That's the code for my main viewController.
import UIKit
class PictureViewController: UIViewController {
#IBOutlet var picture: UIImageView!
#IBOutlet var imageView: UIImageView!
var selectedImage: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let imageToLoad = selectedImage {
imageView.image = UIImage(named: imageToLoad)
}
}
override func viewWillAppear(_ animated: Bool) {
// doing it for the parent class
super.viewWillAppear(animated)
// if its a nav Cont then it will hide bars on tap...
}
// now make sure it turns off when you go back to the main screen
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
That's the code for the viewController that has the imageView. Right now, the image that's popping up is always the preset (Marcus Aurelius), even though my code looks correct to me. Obviously it isn't (also, I've already debugged and ensured through print statements that the jpg files add to the pictures array correctly).
Any help would be appreciated.
First of all, this code is really silly:
#IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 0 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[0]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 1 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[1]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 2 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[2]
navigationController?.pushViewController(vc, animated: true)
}
}
}
Do you see that everything in those lines is identical except for the numbers? So make the number a variable:
#IBAction func buttonTapped(_ sender: UIButton) {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
print(sender.tag)
vc.selectedImage = pictures[sender.tag]
navigationController?.pushViewController(vc, animated: true)
}
}
See how much shorter and clearer that is? Okay, I've also added a print statement. This will print the tag to the console. You need to make sure that your buttons do have the right tags. If they do, your code should work.
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
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)
}
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)
}