How to share a builder class across multiple instances in swift - 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?

Related

How do you get a text field input in you view controller code?

I’m trying to make Xcode print "Nice!" when you type in "Hi". I've used a IBOutlet, but I don’t know how to use the user input in my code. Also BTW I'm using Storyboard and not SwiftUI. It also gives me an error when I try to compare the datatype UIViewController and a String. Here is my view controller code(with the default App Delegate and Scene Delegate code):
import UIKit
class ViewController: UIViewController {
#IBOutlet var yeet: [UITextField]!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func fuel(_ yeet:UIViewController) -> Int {
if yeet == ("hi") {
print("Nice!")
}
}
}
your textfield show be setup as
#IBOutlet weak var textFeildName: UITextField!
you will need to change a couple things inside of your file to prevent a crash. I'd delete the textfield and drag it into the assistant view and give it a new name.
but before you press "connect" press the "outlet" tab and change it to "Action" and then a new selector should come up select "Editing Did End" and go to the top and press "Did End On Exit"
after that is done would want to reference the variable of the text field:
example:
#IBAction func TextFieldName(_ sender: Any) {
if(self.TextFeildName.text!.contains("hi")){
print("Nice!")
}
}
On top of all this, you do not compare strings with == that's only if you compare 2 separate strings for example stringOne == stringTwo if you are comparing or asking if a string contains anything you'd want to use the developing language specific string container IE: .contains
Also, please do not include "Xcode" as a tag with your question, as that should be reserved for Xcode related problems. not Swift or objective-c coding issues.

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.

Changing the value of a variable in another a class using a UIButton Swift 2

I have a View Controller (called BackgroundViewController) which has a few buttons, each of them set the color of the background of a different view, my main view. (just called ViewController, yes I started this project about a month ago, before I knew that I should name it something better). For that I set a class, SoundboardBrain, which I intend to use to hold a lot of the app's logic. Here's the class so far:
var backgroundName = String()
init(){
backgroundName = "Image"}
func changeBackgroundName(background: String){
backgroundName = background}
Now, BackgroundViewController is kind of like a settings pane, where the user could select one of the options and a bullet point appears by the one that he checked. Here's one of the of the buttons:
#IBAction func whiteButton(sender: AnyObject){
whiteBullet.hidden = false
imageBullet.hidden = true
}
//Here I call the changeBackground function I defined in SoundboardBrain
SoundboardBrain.changeBackgroundName("White")
//I then print the result of that and I still get "Image" NO MATTER WHAT!
So all I want to know is how to change a variable initialized in a class with a UIButton or another object of a ViewController.
You should keep the instance of SoundBrain somewhere in variable, or use a singleton. You could be initializing a new SoundBrain instance later.
Singleton is better for main app logic. Example:
class SoundboardBrain {
static let shared = SoundboardBrain()
var backgroundName = "Image"
func changeBackgroundName(background: String) {
backgroundName = background
}
}
SoundboardBrain.shared.backgroundName
// now the property is "Image"
// in UIButton
SoundboardBrain.shared.changeBackgroundName("something")
SoundboardBrain.shared.backgroundName
// now it's "something"
Example was made in Playground, but it doesn't matter.

Swift - IBOutletCollection equivalent

I'm trying to replicate the Stanford Matchismo game from "Developing ios7 apps for iphone and ipad" in iTunesU in Swift.
On page 77 of the 3rd lecture slides, it shows using an IBOutletCollection which isn't an option on Swift. The Swift doc example shows one example that has an array of IBOutlet, but I can't figure out how to make Interface Builder connect multiple outlets to the same IBOutlet/IBOutlet Array.
Has anyone figured out how to do this yet?
I know that I can create 12 outlets and deal with it that way, but I'd like to make this work as closely as possible to the example in the lecture slides.
Update: This works properly in Xcode now - "Outlet Collection" is one of the connection options in Interface Builder, which creates something that looks like:
#IBOutlet var labelCollection: [UILabel]!
While we're waiting for a fix, you can approximate this using a computed property. Let's say my view has five UILabels that I want in a collection. I still have to declare each one, but then I also declare a computed property that collects them:
class MyViewController {
#IBOutlet var label1 : UILabel
#IBOutlet var label2 : UILabel
#IBOutlet var label3 : UILabel
#IBOutlet var label4 : UILabel
#IBOutlet var label5 : UILabel
var labels: UILabel![] { return [label1, label2, label3, label4, label5] }
Kind of annoying, but from then on we can treat the labels property as if it were an IBOutletCollection, and won't have to change the rest of our code once the bug is fixed:
override func viewDidLoad() {
super.viewDidLoad()
for (index, item) in enumerate(self.labels) {
item.text = "Label #\(index)"
}
}
Use:
#IBOutlet var lineFields: [UITextField]!
Then control-drag from UITextField elements to lineFields in order.
#IBOutlet var buttons : [UIView]!
then drag it from the connections inspector in the interface builder or whatever metod you usually use for that
EDIT
This was fixed in a later Beta release of Swift - there's now in
IBCollection option in the interface builder.
For early Beta releases of Swift:
I came across the same problem: in the release notes of Beta 2 you find the following statement:
Interface Builder does not support declaring outlet collections in Swift classes
I solved this the following way (easy to customize):
class CardGameViewController: UIViewController {
#lazy var cardButtons : UIButton[] = {
var tempBtn: UIButton[] = []
for v:AnyObject in self.view.subviews {
if v is UIButton {
tempBtn.append(v as UIButton)
}
}
return tempBtn
}()
...
Basically, it loops through all the subviews and checks if one is a UIButton. In that case it gets added to a temporary array. This temporary array is then used to lazy instantiate the cardButtons array. For all details, check: Matchismo: Objective-C to Swift
Follow steps to create an array of outlets and connect it with IB Elements:
Create an array of IBOutlets
Add multiple UIElements (Views) in your Storyboard ViewController interface (As shown in below snapshot)
Select ViewController (In storyboard) and open connection inspector
There is option 'Outlet Collections' in connection inspector (You will see an array of outlets there)
Connect if with your interface elements
-
class ViewController2: UIViewController {
#IBOutlet var collection:[UIView]!
override func viewDidLoad() {
super.viewDidLoad()
}
}
I got this working in Xcode seed 3 using this syntax
#IBOutlet strong var views: NSArray?
See my discussion here: https://stackoverflow.com/a/24686602/341994
What #machine said seems to be the current state (XCode 7.1) with iOS 9 bindings.
The key is to drag them all in order.
Use the first item to control+drag into the controller code and then change the Outlet type to collection. After the from the controller code file drag the outlet point onto each of the screen controls one by one in order (as #machine says)