A method is launched more than once in swift - swift

I have a class:
class ViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
...
NotificationCenter.default.addObserver(
self,
selector: #selector(self.onDealLaunched),
name: Notification.Name("newDealLaunched"),
object: nil)
}
#objc func onDealLaunched(notification: NSNotification) {
let deal = notification.object as! SimpleSaveGame.deal
let i = projectCollection.count
let indexPath = IndexPath(row: i, section: 0)
let projectDeal: project = project(...)
projectCollection.append(projectDeal)
activeDeals.append(deal)
projectCollectionView!.numberOfItems(inSection: 0)
projectCollectionView.insertItems(at: [indexPath])
projectCollectionView.reloadData()
}
#IBAction func corpoButtonPressed(_ sender: Any) {
let vcCorpo = UIStoryboard(name: "Corpo", bundle: nil).instantiateViewController(withIdentifier: "CorpoViewController") as! CorpoViewController
vcCorpo.currentRound = second
vcCorpo.yearlyTaxIncome = gameVariables[14].value
NotificationCenter.default.addObserver(
self,
selector: #selector(self.onReturnCorpo),
name: Notification.Name("corpoBackButtonPressed"),
object: nil)
self.present(vcCorpo, animated: true, completion: nil)
}
}
Then I have CorpoViewController which present custom UITableView and if a user clicks button it shows popup
class CorpoViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
...
#objc func onLaunchButtonPressed(notification: NSNotification) { // it is launched when player presses negotiate button
dealId = notification.object as! Int
NotificationCenter.default.removeObserver(self, name: Notification.Name("dealLaunchButtonPressed"), object:dealId)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.onPopupClosed),
name: Notification.Name("negotiationPopupClosed"),
object: nil)
self.negotiatedDeal = self.deals[self.dealId]
popup = NegotiationPopup(dealId: self.dealId, deal: self.negotiatedDeal, corpo: self.corpos[self.deals[self.dealId].corpo])
self.view.addSubview(popup)
}
#objc func onPopupClosed(notification: NSNotification) {
NotificationCenter.default.removeObserver(self, name: Notification.Name("negotiationPopupClosed"), object:nil)
negotiatedDeal = notification.object as! SimpleSaveGame.deal
if (negotiatedDeal.status == 2) {
addActiveDeal(deal: negotiatedDeal)
negotiatedDeal.status = 4
deals.remove(at: dealId)
}
}
override func viewDidLoad() {
super.viewDidLoad()
corpoTableView.delegate = self
corpoTableView.dataSource = self
corpoTableView.register(CorpoCell.self, forCellReuseIdentifier: "cellId")
NotificationCenter.default.addObserver(
self,
selector: #selector(self.onLaunchButtonPressed),
name: Notification.Name("dealLaunchButtonPressed"),
object: nil)
planTimer()
}
}
A class implementing:
class CorpoCell: UITableViewCell {
...
#IBAction func launchButtonPressed(_ sender: UIButton) {
NotificationCenter.default.post(name: Notification.Name("dealLaunchButtonPressed"), object: launchButton.tag)
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupView()
}
}
And a class representing the popup:
class NegotiationPopup: UIView {
...
#objc fileprivate func animateOut() {
self.removeFromSuperview()
NotificationCenter.default.post(name: Notification.Name("negotiationPopupClosed"), object: self.negotiatedDeal)
}
}
#objc fileprivate func cancelButtonPressed() {
...
animateOut()
}
}
My problem is that when close popup method animateOut() is launched once, but method #objc func onPopupClosed is launched many times. Number of times depends on how many times I close CorpoViewController and then open it again.
Therefore I guess that I have multiple instances of CorpoViewController in memory, but I do not know how to confirm it and how to fix it.

I replaced observer which indicated that popup was closed with delegate and it fixed the problem somehow.

Related

Function called for all NSWindow

When I call function, it's called for all window opened and not just for the selected window.
If the function is called by #IBAction It's applied for the selected window. Otherwhise, it's applied for all windows.
How can i call the function just for the current selected window ?
Here is an preview:
This is the minimal reproductible code:
// AppDelegate.swift
import Cocoa
#main
class AppDelegate: NSObject, NSApplicationDelegate {
#objc func openMyWindow()
{
let storyboard:NSStoryboard = NSStoryboard(name: "Main", bundle: nil)
guard let controller:NSWindowController = storyboard.instantiateController(withIdentifier: "WindowMain") as? NSWindowController else { return }
controller.showWindow(self)
}
#objc func test()
{
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "TEST"), object: nil, userInfo: nil)
}
func applicationDockMenu(_ sender: NSApplication) -> NSMenu? {
let dockMenu = NSMenu()
dockMenu.addItem(withTitle: "New window", action: #selector(openMyWindow), keyEquivalent: "")
dockMenu.addItem(withTitle: "test", action: #selector(test), keyEquivalent: "")
return dockMenu
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
// ViewController.swift
import Cocoa
class ViewController: NSViewController {
#objc func Test(){
TextView.string = "It's applied for ALL views -> it's NOT ok"
}
#IBAction func button(_ sender: Any) {
TextView.string = "It's applied just for this view -> it's ok"
}
#IBOutlet var TextView: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(Test), name: NSNotification.Name(rawValue: "TEST"), object: nil)
}
override var representedObject: Any? {
didSet {
}
}
}
Notification with object nil (no object) are hard to distinguish when it is not even evaluated which of the windows invoked the post.
In other words, make use of the object: parameter when you post the Notification.
Otherwise all registered observers in multiple windows will act on one and the same Notification.
So what object could be used to know who was sending?
The window object itself of course.
Your WindowController has a window it belongs to as well, just compare its address to the posted Notifications object and act when they are the same.
Or compare against the front most windows address, which usually is the window the user expects to act on commands given.
If the target of the menu item isn't set then the action message is sent to the first responder. In your view the text view is the first responder but it doesn't handle the test message and sends it to the next responder. The view controller is in the responder chain and will receive the test message.
Set the selector of the menu item to the action of the view controller and the view controller of the front window will receive it. No notifications required.
// AppDelegate.swift
import Cocoa
#main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#objc func openMyWindow() {
let storyboard:NSStoryboard = NSStoryboard(name: "Main", bundle: nil)
guard let controller:NSWindowController = storyboard.instantiateController(withIdentifier: "WindowMain") as? NSWindowController else { return }
controller.showWindow(self)
}
func applicationDockMenu(_ sender: NSApplication) -> NSMenu? {
let dockMenu = NSMenu()
dockMenu.addItem(withTitle: "New window", action: #selector(openMyWindow), keyEquivalent: "")
dockMenu.addItem(withTitle: "test", action: #selector(ViewController.test), keyEquivalent: "")
return dockMenu
}
}
// ViewController.swift
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#objc func test() {
TextView.string = "It's applied for this view -> it's now ok"
}
#IBAction func button(_ sender: Any) {
TextView.string = "It's applied just for this view -> it's ok"
}
#IBOutlet var TextView: NSTextView!
}

I am creating Keyboard Extension (swift) But unable to get height of keyboard

import UIKit
class KeyboardViewController: UIInputViewController {
var heightKeyboard : CGFloat?
#IBOutlet var nextKeyboardButton: UIButton!
override func updateViewConstraints() {
super.updateViewConstraints()
// Add custom view sizing constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
// Perform custom UI setup here
self.nextKeyboardButton = UIButton(type: .system)
self.nextKeyboardButton.setTitle(NSLocalizedString("Next Keyboard", comment: "Title for 'Next Keyboard' button"), for: [])
self.nextKeyboardButton.sizeToFit()
self.nextKeyboardButton.translatesAutoresizingMaskIntoConstraints = false
self.nextKeyboardButton.addTarget(self, action: #selector(handleInputModeList(from:with:)), for: .allTouchEvents)
self.view.addSubview(self.nextKeyboardButton)
self.nextKeyboardButton.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.nextKeyboardButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillDisappear), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillAppear(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillAppear(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(true)
NotificationCenter.default.removeObserver(self)
}
#objc func keyboardWillAppear(notification: NSNotification) {
//Do something here
print("keyboardWillAppear()")
print("keyboardShown")
if let infoKey = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey],let rawFrame = (infoKey as AnyObject).cgRectValue {
let keyboardFrame = view.convert(rawFrame, from: nil)
self.heightKeyboard = keyboardFrame.size.height
print(self.heightKeyboard)
}
}
#objc func keyboardWillDisappear() {
print("keyboardWillDisappear()")
}
override func viewWillLayoutSubviews() {
self.nextKeyboardButton.isHidden = !self.needsInputModeSwitchKey
super.viewWillLayoutSubviews()
}
override func textWillChange(_ textInput: UITextInput?) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(_ textInput: UITextInput?) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
let proxy = self.textDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.dark {
textColor = UIColor.white
} else {
textColor = UIColor.black
}
self.nextKeyboardButton.setTitleColor(textColor, for: [])
}
}
I am creating Keyboard Extension (swift) But unable to get height of keyboard . I am using storyboard for keyboard creation. func keyboardWillAppear(),func keyboardWillDisappear() never getting called.
So unable to get keyboard size based on different sizes and orientation

View above keyboard don't show up

I want to have a View with a textField an a send button above my keyboard, when the keyboard is shown. But this doesn't work.
I already implemented this in my code:
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_ :)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.isHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
tabBarController?.tabBar.isHidden = false
}
// MARK: - Keyboard stuff
#objc func keyboardWillShow(_ notification: NSNotification) {
let keyboardFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue
print("hier ist \(keyboardFrame!)")
UIView.animate(withDuration: 0.1) {
self.bottomConstraint.constant = keyboardFrame!.height
self.view.layoutIfNeeded()
}
}
#objc func keyboardWillHide(_ notification: NSNotification) {
UIView.animate(withDuration: 0.1) {
self.bottomConstraint.constant = 0
self.view.layoutIfNeeded()
}
}
Check out textField inputAccessoryView. There's a tutorial here that explains what you're trying to do.

Unrecognized selectors sent to instance, Notification center getting the Keyboard size

If I try to tap into my textfields I get an error, related to these few lines of code that try to get the size of the keyboard on a mobile ios device. The Notification Center lines of code are inside the overriding ViewDidAppear.
NotificationCenter.default.addObserver(self, selector: Selector(("keyboardWillShow:")), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: Selector(("keyboardWillHide:")), name: UIResponder.keyboardDidHideNotification, object: nil)
func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
if let keyboardSize = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
self.bottomConstraint.constant = keyboardSize.height
}
}
}
func keyboardWillHide(notification: NSNotification) {
self.bottomConstraint.constant = 0
}
Use the type safe syntax
#selector(keyboardWillShow)
and
#objc func keyboardWillShow(_ notification: Notification) { ...
However I highly recommend to use the modern closure based syntax
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { [weak self] notification in
if let userInfo = notification.userInfo,
let keyboardSize = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
self?.bottomConstraint.constant = keyboardSize.height
}
}
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { [weak self] _ in
self?.bottomConstraint.constant = 0
}
Try Following code :
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWillShow(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
print("notification: Keyboard will show")
}
}
#objc func keyboardWillHide(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
}
}
You can try this:
This code in viewDidLoad():
// Do any additional setup after loading the view.
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
and then add this in ViewController
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
print("Keyboard opened \(keyboardSize)")
}
}
#objc func keyboardWillHide(notification: NSNotification) {
print("Keyboard hide")
}
Hope this will help.
You should deregister any notification you register in a view.
func registerForKeyboardNotifications()
{
//Adding notifies on keyboard appearing
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
func deRegisterFromKeyboardNotifications()
{
//Removing notifies on keyboard appearing
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWasShown(_ notification: NSNotification)
{
//todo
}
#objc func keyboardWillBeHidden(_ notification: NSNotification)
{
//todo
}
override func viewDidLoad() {
super.viewDidLoad()
registerForKeyboardNotifications()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
deRegisterFromKeyboardNotifications()
}
You've got this error because of notification parameter. With current signature you should use:
#selector(keyboardWillShow(notification:))
#selector(keyboardWillHide(notification:))
Or rewrite your methods in that way:
#objc func keyboardWillShow(_ notification: Notification) {
// Code
}
#objc func keyboardWillHide(_ notification: Notification) {
// Code
}
And use the next syntax:
#selector(keyboardWillShow(_:))
#selector(keyboardWillHide(_:))
Edited:
You can also use simplified syntax:
#selector(keyboardWillShow)
#selector(keyboardWillHide)
Use like below code
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow(notification:)),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillHide(notification:)),
name: UIResponder.keyboardWillHideNotification,
object: nil)
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
// Your code
}
}
#objc func keyboardWillHide(notification: NSNotification) {
// Your code
}
Hope this works, If any doubt plz comment.

swift screen move when keyboard appears

I have a textview and a text field on my screen and I want move screen when edit textview.
This is my code
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: .UIKeyboardWillShow, object: nil)
// NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: .UIKeyboardWillHide, object: nil)
print("addd observer")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
// NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)
}
#objc func keyboardWillShow(_ sender: Notification) {
let keyboardSize = (sender.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
print("keyboardwillshow")
ksize = keyboardSize?.height
}
#objc func textViewDidBeginEditing(_ textView: UITextView) {
print("textviewedit")
boxview.frame.origin.y -= ksize
print("textview")
}
#objc func textViewDidEndEditing(_ textView: UITextView) {
print("textvieweditend")
boxview.frame.origin.y = 0
}
It works when I just edit textview. However, if I edit the textview right after focus on the textfield(without exit keyboard) it doens't work.
I already checked that textviewDidBeginEditing() is called but the screen doesn't move.. How can I solve this? All advises are welcome!
I add self.view.Layoutifneeded() to keyboardwillchange() and it works!! thanks everybody.