Using a Singleton to pass data in Watchkit - swift

I've used a Singleton in IOS with success before, but the same approach isn't working for me in watchkit...any idea why my second interface controller displays 0 (initialized value) instead of the numbers entered in the first interface controller?
class Singleton {
static let instance = Singleton()
var salesPriceAsInt: Int = 0
}
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
var salesPrice = ""
#IBOutlet weak var salesPriceLabel: WKInterfaceLabel!
func addCharacter(char: String) {
salesPrice += char
}
#IBAction func zeroButton() {
addCharacter("0")
salesPriceLabel.setText(salesPrice)
}
#IBAction func oneButton() {
addCharacter("1")
salesPriceLabel.setText(salesPrice)
}
#IBAction func twoButton() {
addCharacter("2")
salesPriceLabel.setText(salesPrice)
}
#IBAction func threeButton() {
addCharacter("3")
salesPriceLabel.setText(salesPrice)
}
#IBAction func fourButton() {
addCharacter("4")
salesPriceLabel.setText(salesPrice)
}
#IBAction func fiveButton() {
addCharacter("5")
salesPriceLabel.setText(salesPrice)
}
#IBAction func sixButton() {
addCharacter("6")
salesPriceLabel.setText(salesPrice)
}
#IBAction func sevenButton() {
addCharacter("7")
salesPriceLabel.setText(salesPrice)
}
#IBAction func eightButton() {
addCharacter("8")
salesPriceLabel.setText(salesPrice)
}
#IBAction func nineButton() {
addCharacter("9")
salesPriceLabel.setText(salesPrice)
}
#IBAction func segueButton() {
Singleton.instance.salesPriceAsInt = salesPrice.toInt()!
salesPriceLabel.setText("\(Singleton.instance.salesPriceAsInt)")
}
import WatchKit
import Foundation
class SecondPageInterfaceController: WKInterfaceController {
#IBOutlet weak var totalSaleCommissionLabel: WKInterfaceLabel!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
#IBAction func secondPageButton() {
totalSaleCommissionLabel.setText("\(Singleton.instance.salesPriceAsInt)")
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}

Turns out the Singleton works, what doesn't is enabling a button to both (1) store to the Singleton and (2) segue to my 2nd interface controller. To get around this I moved
Singleton.instance.salesPriceAsInt = salesPrice.toInt()!
to the didActivate method

Related

How to set NSSlider value in a NSToolbar - Swift Cocoa + Storyboard

I am quite new to Swift programming and I am trying to set a slider min, max and value in a NSToolbar. As an hypothetical exemple, I have a list of client and I want to use the slider in the toolbar to select a client data page. I will firt to load the client database in the NSViewController and count the number of client. Than I would like to set the slider in the toolbar minvalue to 1 and maxvalue to the number of client. I understand how to send slider values from the Windowcontroller to the ViewController but I did not found how to do the inverse , how to send data from the Viewcontroller to the Window controller in order to set the slider values.
I have attach an simple code based on this exemple https://github.com/gbdavid2/DavidCodes_macOS/tree/master/NSToolbar%20with%20Storyboards/NSToolbar%20with%20Storyboards
In this exemple, the Toolbar shows a Previous and an Next button that , when clicked, they change a counter value (count). I would like to send back that value from the ViewCoOntroller to the WindowController in order to display it in label and eventually, the slider value in the toolbar. Thanks for your help.
// WindowController.swift
import Cocoa
class WindowController: NSWindowController {
#IBOutlet weak var myBoutton: NSToolbarItem!
var viewController: ViewController {
get {
return self.window!.contentViewController! as! ViewController
}
}
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
//viewController.myLabel.stringValue = "boo"
}
#IBAction func previous(_ sender: Any) {
viewController.updateMyLabelText(newText: "Prev Button clicked! ")
}
#IBAction func next(_ sender: Any) {
viewController.updateMyLabelText(newText: "Next Button clicked! ")
}
}
import Cocoa
class ViewController: NSViewController {
var count : Int = 0
#IBOutlet weak var myLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func updateMyLabelText(newText: String){
if newText.contains("Prev") {count -= 1}
else if newText.contains("Next") {count += 1}
myLabel.stringValue = newText + String(count)
}
}
Another way to to achieve this is with Cocoa Bindings. Example:
In the toolbar are a Previous button, a Next button and a slider. The actions of the buttons are connected to the First Responder. The action methods are implemented in ViewController. The count property of ViewController has attributes #objc dynamic so it can be used with Cocoa Bindings.
class ViewController: NSViewController {
#objc dynamic var count: Int = 0
#IBAction func previous(_ sender: Any) {
count -= 1
}
#IBAction func next(_ sender: Any) {
count += 1
}
}
The slider in the toolbar is bound to the Window Controller, key path window.contentViewController.count.
In the view is a label with a number formatter. The value of the label is bound to the View Controller, key path count.
The window controller isn't subclassed.
There are multiple ways to achieve this.
One of the way is by creating a class [e.g: SliderManager] which keep tracks of current value and handles increment/decrement. You can get the current value of Slider with the help of Singleton in any Controller.
Here is an example implementation:
protocol SliderCountDelegate: NSObject {
func counterDidUpdate()
}
final class SliderCountManager {
static let shared = SliderCountManager()
var value: UInt8 = 0 // <-- Unsigned Integers: Only Positive numbers
weak var delegate: SliderCountDelegate?
public func increaseCounter() {
value += 1
delegate?.counterDidUpdate()
}
public func decreaseCounter() {
value -= 1
delegate?.counterDidUpdate()
}
}
Here is how you should use this in your code:
// WindowController.swift
import Cocoa
class WindowController: NSWindowController {
#IBOutlet weak var myBoutton: NSToolbarItem!
override func windowDidLoad() {
super.windowDidLoad()
}
#IBAction func previous(_ sender: Any) {
SliderCountManager.shared.increaseCounter()
print(SliderCountManager.shared.value) // <- Accessing Current value here
}
#IBAction func next(_ sender: Any) {
SliderCountManager.shared.decreaseCounter()
print(SliderCountManager.shared.value)
}
}
import Cocoa
class ViewController: NSViewController, SliderCountDelegate {
#IBOutlet weak var myLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
SliderCountManager.shared.delegate = self // Set Delegate to `self`
}
override var representedObject: Any? {
didSet {
}
}
// Protocol conformance
func counterDidUpdate() {
myLabel.stringValue = String(SliderCountManager.shared.value)
}
}
Thanks for the proposed solutions. It certainly put me in the wrigth direction.
Here is what I did. In the WindowController , I set a toolbar with 1) button «previous», 2) button «next» and 3) a slider «slider».
Those are linked to the proper IBOutler and IBaction in the WindowController.
The viewController have a textLabel «myLabel»
The 2 buttons and the slider change the slider_ptr value in the ViewControler and is sent to myLabel. Also, the slider.label change according to the slider_pointer and the slider_max values. Here is the code for the windowController:
import Cocoa
class WindowController: NSWindowController {
#IBOutlet weak var slider: NSSlider!
#IBOutlet weak var sliderTB: NSToolbarItem!
var viewController: ViewController {
get {
return self.window!.contentViewController! as! ViewController
}
}
override func windowDidLoad() {
super.windowDidLoad()
setSlider() // set initial value based on ViewController
}
#IBAction func previous(_ sender: Any) {
viewController.previous (WindowController())
setSlider()
}
#IBAction func next(_ sender: Any) {
//viewController.updateMyLabelText(newText: "Prev Button clicked! ")
viewController.next (WindowController()) //send to VC function previous
// let pt = viewController.slider_ptr + 1
//let sMax = viewController.slider_max
setSlider()
//sliderTB.label = String(pt) + " de " + String(sMax)
}
#IBAction func sliderDidChange(_ sender: Any) {
viewController.sliderDidSlide (WindowController(), pointer: Int(slider.doubleValue))
setSlider()
// viewController.sliderDidSlide(PosiWC(), sValue: myslider.doubleValue)
}
func setSlider() {
/* myslider.minValue = 1
myslider.maxValue = Double(max)
myslider.integerValue = pointer*/
//print ("WCP58:" , myslider.integerValue )
let pt = viewController.slider_ptr
let sMax = viewController.slider_max
//slider (max : pt, pointer: sMax)
sliderTB.label = String(pt) + " de " + String(sMax)
slider.minValue = 1
slider.maxValue = Double(sMax)
slider.integerValue = pt
}
}
and for the Viewcontroller :
class ViewController: NSViewController {
var slider_ptr = 1 // slider position
var slider_max: Int = 0 //
#IBOutlet weak var myLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
slider_max = 250
myLabel.stringValue = String(slider_ptr)
}
override var representedObject: Any? {
didSet {
}
}
func previous(_ sender: Any) {
if slider_ptr > 1 {
slider_ptr -= 1
}
else { NSSound.beep()}
myLabel.stringValue = String(slider_ptr)
}
func next(_ sender: Any) {
if slider_ptr < slider_max {
slider_ptr += 1
}
else { NSSound.beep()}
myLabel.stringValue = String(slider_ptr)
}
func sliderDidSlide(_ sender: Any, pointer : Int) {
print (pointer)
slider_ptr = pointer
myLabel.stringValue = String(slider_ptr)
}
}

Two windows are presented when I start my xib only project

I am using Xcode 10.1 two create a simple single window application. The issue is that I see two windows instantiated when I run it instead of the single one I expect. Here is my code
//AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var mainWindowController: WindowController!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
print("loading")
let mainWindowController = WindowController()
mainWindowController.showWindow(self)
self.mainWindowController = mainWindowController
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
print("closed")
}
}
//WindowController.swift
import Cocoa
class WindowController: NSWindowController, NSSpeechSynthesizerDelegate {
#IBOutlet weak var textField: NSTextField!
#IBOutlet weak var speakButton: NSButton!
#IBOutlet weak var stopButton: NSButton!
override var windowNibName: String {
return "Window"
}
let speechSynth = NSSpeechSynthesizer()
var isStarted: Bool = false {
didSet {
updateButtons()
}
}
override func windowDidLoad() {
super.windowDidLoad()
viewDidLoad()
}
func viewDidLoad() {
// super.viewDidLoad()
updateButtons()
speechSynth.delegate = self
print("view load")
// Do any additional setup after loading the view.
}
#IBAction func speakIt(sender: NSButton) {
let string = textField.stringValue
if string.isEmpty {
print("String is empty")
}
else {
print(string)
speechSynth.startSpeaking(string)
isStarted = true
}
}
#IBAction func stopIt(sender: NSButton) {
speechSynth.stopSpeaking()
isStarted = false
}
func updateButtons() {
if isStarted {
speakButton.isEnabled = false
stopButton.isEnabled = true
} else {
stopButton.isEnabled = false
speakButton.isEnabled = true
}
}
func speechSynthesizer(_ sender: NSSpeechSynthesizer, didFinishSpeaking finishedSpeaking: Bool) {
isStarted = false
}
}
My Xib has the option checked "Visible at launch". If I uncheck it, I see no window.
Basically, I am attempting a program from the book "Cocoa Programming for OSX". However, the book is obsolete. But I am trying to find my way through new Xcode and Swift. Any help?
Also a point worth noting is that only one window is functional, the second window IBOutlets and IBAction are not connected. So nothing happens in the second window.
I can't figure out why is this happening?

I have a problem with a variable in AppDelegate

I have an error in my code when trying to return a variable that is in the App delegate to a viewController, it returns a null value and therefore does not assign the delegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var client: SINClient!
.
.
.
}
class ViewController: UIViewController, SINCallClientDelegate {
#IBOutlet weak var destination: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func client() -> SINClient? {
return (UIApplication.shared.delegate as! AppDelegate).client
}
override func awakeFromNib() {
super.awakeFromNib()
self.client()?.call().delegate = self
}
#IBAction func actionCall(_ sender: UIButton) {
if destination.text != "" && self.client()!.isStarted() {
weak var call = client()!.call().callUserVideo(withId: destination.text)
performSegue(withIdentifier: "callView", sender: call)
}
}
func client(_ client: SINCallClient?, didReceiveIncomingCall call: SINCall?) {
performSegue(withIdentifier: "callView", sender: call)
}
}
Fatal error: Unexpectedly found nil while unwrapping an Optional value

How to set NSButton.isEnabled from subclassed NSTextField

I'm very new to Swift MacOS programming and have been learning by writing small test applications.
The aim of this application is to enable the pushbutton when the 2nd textfield has the focus, and disable it when it is not focused.
I have found that by subclassing the NSTextField I can override becomeFirstResponder() however don't know how to set the button to be disabled from the subclass.
ViewController:
class ViewController: NSViewController {
#IBOutlet public weak var pushButton: NSButton!
#IBOutlet weak var textField3: NSTextField!
#IBOutlet weak var textField2: GSTextField!
#IBOutlet weak var textField1: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
textField2.delegate = self
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func chgButton(onoff: Bool){
pushButton.isEnabled = onoff
}
}
// When the field completes editing make the pushbutton disabled.
extension ViewController: NSTextFieldDelegate {
override func controlTextDidEndEditing(_ obj: Notification) {
print("did end")
chgButton(onoff: false)
}
}
GSTextField.Swift
class GSTextField: NSTextField {
override func becomeFirstResponder() -> Bool {
print("GSTextField Firstresponder")
////*** I need to set the button to be enabled here
return super.becomeFirstResponder()
}
}
Your NSTextField subclass needs to be able to communicate with the pushButton. The easiest way to do this is to pass a reference to the pushButton to your text field and then update the push button from there.
Update your ViewController like this:
override func viewDidLoad() {
super.viewDidLoad()
textField2.delegate = self
textField2.pushButton = pushButton
// Do any additional setup after loading the view.
}
And your GSTextField like this:
class GSTextField: NSTextField {
weak var pushButton: NSButton?
override func becomeFirstResponder() -> Bool {
print("GSTextField Firstresponder")
pushButton?.isEnabled = true
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
pushButton?.isEnabled = false
return super.resignFirstResponder()
}
}
It should be noted that while this works fine in this toy example, this is a sub-optimal solution to this problem because it tightly couples the pushButton and the GSTextField. A better solution would be to use delegation to communicate the focus changes to the ViewController, and let the ViewController handle the updates.
Your GSTextField would look like this:
protocol FocusObservable: class {
func didGainFocus(sender: Any)
func didLoseFocus(sender: Any)
}
class GSTextField: NSTextField {
weak var focusDelegate: FocusObservable?
override func becomeFirstResponder() -> Bool {
print("GSTextField Firstresponder")
focusDelegate?.didGainFocus(sender: self)
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
focusDelegate?.didLoseFocus(sender: self)
return super.resignFirstResponder()
}
}
And then you would add protocol conformance to the ViewController:
extension ViewController: FocusObservable {
func didGainFocus(sender: Any) {
pushButton.isEnabled = true
}
func didLoseFocus(sender: Any) {
pushButton.isEnabled = false
}
}
and set the focusDelegate of the text field:
override func viewDidLoad() {
super.viewDidLoad()
textField2.delegate = self
textField2.focusDelegate = self
// Do any additional setup after loading the view.
}

How do I call a function from a different view controller?

I have what is basically a notes app, and I am trying to call a function that is in one view controller from another. How do I do this, or is there a better way I should do it?
Here's where I am trying to call the function from
class NoteDetailViewController: UIViewController
{
#IBOutlet weak var funcButton: UIButton!
#IBAction func funcButTouched(sender: UIButton)
{
// where i want to call the function
}
}
And where the function is
class ListTableViewController: UITableViewController
{
// the function
func wordCount()
{
var contentArr = Project.sharedInstance.content.componentsSeparatedByString(" ")
for (index, element) in contentArr.enumerate()
{
let location = conciseArr.indexOf(element)
if (location != nil)
{
contentArr[index] = inflatedArr[location!]
afterStr = contentArr.joinWithSeparator(" ")
Project.sharedInstance.after = afterStr
}
}
}
}
I've tried just creating an instance of ListTableViewController and just calling the function that way but I get an error.
You could use delegation.
First, declare a delegate protocol. This should be in your NoteDetailViewController.swift file but outside of the class declaration:
protocol NoteDetailViewControllerDelegate: class {
func noteDetailViewControllerButtonTouched(controller: NoteDetailViewController)
}
Next, add a delegate property to your NoteDetailViewController:
weak var delegate: NoteDetailViewControllerDelegate?
Now, we use the #IBAction to tell the delegate, which will be the ListTableViewController:
#IBAction func funcButTouched(sender: UIButton)
{
delegate?.noteDetailViewControllerButtonTouched(self)
}
Finally, back in ListTableViewController (assuming this controller is the one directly before a NoteDetailViewController is shown), conform to the protocol and use prepareForSegue to set the delegate to itself:
class ListTableViewController: UITableViewController, NoteDetailViewControllerDelegate {
// ... more stuff ...
// Implement the delegate protocol
func noteDetailViewControllerButtonTouched(controller: NoteDetailViewController) {
// Do something! The button was pressed!
wordCount()
}
// Set ourselves as delegate when we are about to show the other view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let yourVC = segue.destinationViewController as? NoteDetailViewController {
yourVC.delegate = self
}
}
}
You should finish with something like this:
NoteDetailViewController.swift:
class NoteDetailViewController: UIViewController
{
#IBOutlet weak var funcButton: UIButton!
weak var delegate: NoteDetailViewControllerDelegate?
#IBAction func funcButTouched(sender: UIButton)
{
delegate?.noteDetailViewControllerButtonTouched(self)
}
}
protocol NoteDetailViewControllerDelegate: class {
func noteDetailViewControllerButtonTouched(controller: NoteDetailViewController)
}
ListTableViewController.swift:
class ListTableViewController: UITableViewController, NoteDetailViewControllerDelegate
{
// the function
func wordCount()
{
var contentArr = Project.sharedInstance.content.componentsSeparatedByString(" ")
for (index, element) in contentArr.enumerate()
{
let location = conciseArr.indexOf(element)
if (location != nil)
{
contentArr[index] = inflatedArr[location!]
afterStr = contentArr.joinWithSeparator(" ")
Project.sharedInstance.after = afterStr
}
}
}
// Implement the delegate protocol
func noteDetailViewControllerButtonTouched(controller: NoteDetailViewController) {
// Do something! The button was pressed!
wordCount()
}
// Set ourselves as delegate when we are about to show the other view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let yourVC = segue.destinationViewController as? NoteDetailViewController {
yourVC.delegate = self
}
}
}
Just follow bellow code its very simple :-
NoteDetailViewController.swift:
class NoteDetailViewController: UIViewController
{
#IBOutlet weak var funcButton: UIButton!
var parentListTableView : ListTableViewController!
#IBAction func funcButTouched(sender: UIButton)
{
parentListTableView.wordCount()
}
}
ListTableViewController.swift:
class ListTableViewController: UITableViewController
{
// the function
func wordCount()
{
var contentArr = Project.sharedInstance.content.componentsSeparatedByString(" ")
for (index, element) in contentArr.enumerate()
{
let location = conciseArr.indexOf(element)
if (location != nil)
{
contentArr[index] = inflatedArr[location!]
afterStr = contentArr.joinWithSeparator(" ")
Project.sharedInstance.after = afterStr
}
}
}
// Set ourselves as delegate when we are about to show the other view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let yourVC = segue.destinationViewController as? NoteDetailViewController {
yourVC.parentListTableView = self
}
}
}