WatchKit setBackground Color Crash - swift

After importing UIKit and attempting to toggle around with where to initialize the buttons colors programmatically, I can't seem to figure out why a crash occurs in the class conforming to WKInterfaceController
import UIKit
import WatchKit
class InterfaceController: WKInterfaceController {
#IBOutlet fileprivate var myButton : WKInterfaceButton!
override func willActivate() { // About to be visible to user
super.willActivate()
self.myButton.setBackgroundColor(UIColor.red) // CRASH
}
}

The reason was in unused breakpoints.
Anyway:
Use awake(withContext:) for changing UI:
When creating an interface controller, WatchKit instantiates the class
and calls its init() method followed shortly by its
awake(withContext:) method. Use those methods to initialize
variables, load data, and configure the items in your storyboard
scene. If WatchKit passes a valid object to the awake(withContext:)
method, use the information in that object to customize the
initialization process.
You can't use willActivate() for changing background color etc.:
The willActivate() method lets you know when your interface is
active. Use the willActivate() method to perform any last minute
tasks, such as checking for updates to your content. (Do not use it
for your primarily initialization.)
Also always use weak outlets:
#IBOutlet fileprivate weak var myButton : WKInterfaceButton!
And check that your outlet was connected.

Related

How to share a builder class across multiple instances in swift

I am trying to become a better developer and just started to study and use software patterns in small test projects.
In my current project I focused on the builder pattern.
Basically all I have is an App with two Views (Storyboards), A single ViewController-Class (used by all of the views) and one Builder-Class that creates an object with two properties.
The Builder-Class is implemented in the ViewController.
class ViewController: UIViewController {
#IBOutlet var startTimePicker: UIDatePicker!
#IBOutlet var workingHoursPicker: UIDatePicker!
#IBAction func setStartTimeButtonPressed(_ sender: Any) {
setStartTime()
}
#IBAction func setWorkingHoursButtonPressed(_ sender: Any) {
setWorkingHours()
}
var workdayBuilder: WorkdayBuilder = ConcreteWorkdayBuilder()
....
My current problem is that every time I navigate to the next View (using a Segue) the ViewController will be instantiated again and loses the value I set in the previous view.
I know that I could technically just pass the value to the next ViewController and then set it there or that I could set the value and then pass the instance of my builder to the next view. But that would kind of destroy the purpose of the builder in this project IMO.
I then thought that I could wrap my Builder into a Singleton and use it like a shared instance across the views:
final class WorkdayBuilderSingleton {
static let sharedInstance: WorkdayBuilder = ConcreteWorkdayBuilder()
init() {}
}
But this lead to another error in my VC when I tried to set a property. "Cannot assign to property: 'sharedInstance' is a 'let' constant".
So I am left with some questions and hope you can help me.
Is it a good or OK practice in general to make a builder a singleton or did I make a bad mistake here?
If it is OK, can I change mit static let into a static var or would that kill the singleton?

Referring to viewcontroller outlets from viewcontroller extension

I have two view controllers SymbolsVC and ItemsVC. Both have a UIActivityIndicatorView which has been outlet by the name of spinner in each view controller like this:
class SymbolsVC: UIViewController {
#IBOutlet weak var spinner: UIActivityIndicatorView!
}
class ItemsVC: UIViewController {
#IBOutlet weak var spinner: UIActivityIndicatorView!
}
Now I need to write a lot of shared code between these view controllers, for example, a function which would start the spinner in its respective viewcontroller. So I have created an extension for UIViewController like this:
extension UIViewController {
func startSpinner() {
spinner.startAnimating()
}
}
However, this gives Use of unresolved identifier 'spinner' error.
What am I missing?
EDIT FOR FURTHER CLARIFICATION: My extension is actually an IAP. This IAP can be called from any number of ViewControllers. When one of the ViewControllers, call the IAP function which is in the extension, it is the flow of the program in that IAP function which determines when to start/stop the spinner. Hence it needs to be done within the extension.
When the compiler sees this:
extension UIViewController {
func startSpinner() {
spinner.startAnimating()
}
}
it thinks every instance of UIViewController in your module contains this spinner property, which of coarse is wrong.
Since as a rule, you can't place regular {set get} properties inside extension, my solution would be placing the spinner inside a base view controller SpinnerBaseVC, which inherits UIViewController.
Then, inherit SpinnerBaseVC with whatever class you want to utilize the spinner property.
class SpinnerBaseVC: UIViewController {
#IBOutlet weak var spinner: UIActivityIndicatorView!
func startSpinner() {
spinner.startAnimating()
}
}
// MARK: Classes which can naturally access `spinner` and `startSpinner`
class SymbolsVC: SpinnerBaseVC {
// Here you can access spinner
}
class ItemsVC: SpinnerBaseVC {
// Here you can access spinner
}

Swift: How to link Touch Bar controls to main window controls

I'm new to Swift/macOS dev, plenty of dev experience otherwise though. Just trying to make something rudimentary.
Here's my app storyboard:
I'm trying to get:
the Touch Bar slider to change when the slider on the main window changes
vice versa
update the Touch Bar Label button with the Int value of the slider.
Q) How do I achieve this?
Note: The main window slider control is wired up and working when I manipulate it e.g.
#IBOutlet weak var mySlider: NSSlider!
#IBAction func mySlider_Changed(_ sender: NSSlider) {
//... stuff happens here.
}
You'll want your view controller to have some explicit model/state of what the value of these sliders have. e.g.
class ViewController : NSViewController {
var value: Double
}
Then you can connect the sliders and textfield to update or display this value.
Approach 1: Target/Action/SetValue
This follows the use of explicit IBActions that you had started. In response to that action, we'll pull the doubleValue from the slider and update the ViewController's model from that:
#IBAction func sliderValueChanged(_ sender: NSSlider) {
value = sender.doubleValue
}
The second piece is updating everything to reflect that new value. With Swift, we can just use the didSet observer on the ViewController's value property to know when it changes and update all of the controls, e.g:
#IBOutlet weak var touchBarSlider: NSSlider!
#IBOutlet weak var windowSlider: NSSlider!
#IBOutlet weak var windowTextField: NSTextField!
var value: Double {
didSet {
touchBarSlider.doubleValue = value
windowSlider.doubleValue = value
windowTextField.doubleValue = value
}
}
And that's it. You can add a number formatter to the textfield so it nicely displays the value, which you can do in Interface Builder or programmatically. And any other time you change the value, all of the controls will still get updated since they are updated in the didSet observer instead of just the slider action methods.
Approach 2: Bindings
Bindings can eliminate a lot of this boiler plate code when it comes to connecting model data to your views.
With bindings you can get rid of the outlets and the action methods, and have the only thing left in the view controller be:
class ViewController: NSViewController {
#objc dynamic var value: Double
}
The #objc dynamic makes the property be KVO compliant, which is required when using bindings.
The other piece is establishing bindings from the controls to our ViewController's value property. For all of the controls this is done by through the bindings inspector pane, binding the 'Value' of the control to the View Controller's value key path:
And that's it. Again, you could add a number formatter to the textfield, and any other changes to the value property will still update your controls since it will trigger the bindings to it. (you can also still use the didSet observer for value to make other changes that you can't do with bindings)

Alternate way to organize functions in a NSViewController subclass?

(Swift, OSX)
I currently have a view controller (named ViewController) that manages the view of my application with storyboards, consisting of a couple of text boxes for user input. If the user enters something incorrect, it calls a function that alerts the user to their error (with a NSAlert modal), and offers them an option: reset their input, reset their input and previous output, or just dismiss the window.
Problem is, that alert code alone is about 60 lines of code (with functions to display appropriate messages), and I want to move it into another class -- a subclass of ViewController, entitled ErrorResponse, so it can still access the user interface to appropriately reset their input (i.e., clear an input string field), and do the other tasks mentioned above while avoiding a massive wall of functions in the ViewController
I've tried creating a subclass of ViewController so the user interface properties are inherited and I can simply access them, however Xcode wants the subclass to implement an init method with NSCoder. (To my knowledge, this is how Xcode sets up a storyboard or .xib to communicate with the controller, as part of the NSViewController class, but this is unrelated to my implmenetation). When I attempt to pass nil or a default initialized NSCoder() object, I get obscure crashes.
So, is there any way to organize these functions? Or should I just keep them all in the ViewController?
Edit: I also have an existing class called ErrorCheck that performs the check to see if there are any errors. Within the ViewContoller, when the input is entered, there is a guard statement with a method from ErrorCheck, and if the input is invalid in some way, the corresponding methods in ErrorResponse are called.
Code snippet:
class ViewController: NSViewController {
// Input and output text fields
#IBOutlet weak var inputStr: NSTextField!
#IBOutlet weak var outputStr: NSTextField!
// Input and output conversion selection
#IBOutlet weak var inputSegments: NSSegmentedControl!
#IBOutlet weak var outputSegments: NSSegmentedControl!
#IBAction func outputIsSelected(sender: NSSegmentedControl) {
// Passing all UI elements in (not shown)
// Not ideal, want to be global obj (see below code)
guard ErrorCheck().stringIsNotEmpty(inputStr.stringValue) else {
ErrorResponse().invalidEmptyInput()
return
}
// 0 = DNA, 1 = mRNA, 2 = English
if (inputSegments.selectedSegment == 0) {
checkPossibleConversionAndConvertDNA()
} else if (inputSegments.selectedSegment == 1) {
checkPossibleConversionAndConvertmRNA()
} else if (inputSegments.selectedSegment == 2) {
checkPossibleConversionAndConvertEnglish()
}
}
}
If I don't subclass ViewController, there is another issue: I can't globally define an object for ErrorResponse, as I can't pass the UI values within the same scope as they are being initialized, so currently I have initialized an ErrorCheck object in each corresponding function... and its messy.
I ended up just moving the methods into ViewController. Not the prettiest, but it's functional.

Why would the action not be able to connect to target class NSViewController?

I'm trying to learn Swift, but I seem to have gotten stuck at this (admittedly, probably very simple) problem - the error as follows:
Could not connect action, target class NSViewController does not respond to -(encbutton/decbutton)
Here is my code. I'm designing my interface in the Storyboard and connecting it to the code through #IB(Outlet/Action).
// ViewController.swift
import Cocoa
import Foundation
class TabViewController: NSTabViewController {
// This has been changed from NSViewController to NSTabViewController as I have replaced the initial single-view with a two-tab-view.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
public class EncViewController: NSViewController {
// This is for the first tab, encrypt
#IBOutlet var encdirfield: NSTextField!
#IBOutlet var encpassfield: NSSecureTextField!
#IBOutlet var enclogfield: NSTextField!
#IBOutlet var encbutton: NSButton!
#IBAction func startenc(sender: NSButton) { // here's the problem. the function isn't triggered when the button is pressed
// get values
encdir = encdirfield.stringValue
encpass = encpassfield.stringValue
tarcrypt.enc();
// this is an function that leads to an NSTask that runs a binary I wrote (not related).
// definitely not the cause of the problem because running it independently works fine
}
}
public class DecViewController: NSViewController {
// and this is for the second tab, decrypt
#IBOutlet var decdirfield: NSTextField!
#IBOutlet var decpassfield: NSSecureTextField!
#IBOutlet var declogfield: NSTextField!
#IBOutlet var decbutton: NSButton!
#IBAction func startdec(sender: NSButton) { // here's the same problem, again. the function isn't triggered when the button is pressed
// get values
encdir = encdirfield.stringValue
encpass = encpassfield.stringValue
tarcrypt.dec();
// this is an function that leads to an NSTask that runs a binary I wrote (not related).
// definitely not the cause of the problem because running it independently works fine
}
}
For some reason, upon drawing the scene along with the NSButton, the error message as seen above is generated. What is causing the error, and how do I fix it?
I've figured it out! For anyone else who runs into this problem, here's how to fix it:
It turns out there is a little dropdown under "Custom Class" titled "Module", which is, by default, set to none. Set it to tarcrypt (or whichever available option suits you) and that should fix the errors.
Thanks for all the help!
It sounds as if you connected your UI element to the File's Owner object, which is an instance of NSApplication.
If you haven't done so already, you want to drag a NSObject out of the Object Library palette in Xcode 4 to the margin to the left of your layout. Once you've done that, and have selected it, choose the identity inspector and, in the Class field, enter "WindowController"
Swift 5 and Xcode 13.3
Recently I had the same bug. I solved it by reassigning the viewcontroller class name to the class in my nameclass.swift file and activating the MODULE entry with my project name (following the dropdown menu).