When I searched on here I now know that almost everything in Swift is value based, not referenced base. I also have read that a class holds references. I tried my best, but below code will not print anything. If anyone could help me out printing out that line with the help of an array, that would be great :) (if it is possible ofcourse...).
Edit: I want 5 booleans in the array, in which they all have a didSet method. When I access them, that specific didSet will trigger. Is that possible?
class PowerUpBooleans{
var boolean: Bool
init(boolean: Bool){
self.boolean = boolean
}
}
var iWantToChangeThis = false{
didSet{
print("it worked")
}
}
var powerUpBooleans = [PowerUpBooleans]()
override func viewDidLoad() {
super.viewDidLoad()
powerUpBooleans.append(PowerUpBooleans(boolean: iWantToChangeThis))
powerUpBooleans[0].boolean = true
}
I guess you want set some booleans that have their own trigger.
As I known, making value type wrapped by class can only make it be reference type.
So try this.
class PowerUpBooleans{
var boolean: Bool {
didSet {
trigger()
}
}
var trigger: () -> ()
init(boolean: Bool, trigger: #escaping () -> ()){
self.boolean = boolean
self.trigger = trigger
}
}
let trigger1 = {
print("one worked.")
}
let trigger2 = {
print("two worked.")
}
var powerUpBooleans = [PowerUpBooleans]()
powerUpBooleans.append(PowerUpBooleans(boolean: false, trigger: trigger1))
powerUpBooleans.append(PowerUpBooleans(boolean: false, trigger: trigger2))
powerUpBooleans[0].boolean = true // print one worked
powerUpBooleans[1].boolean = false // print two worked
Related
I have created a class
class NewTabContainerModal {
var newContainerDate = Date()
var newContainerSelectedFilter : NewAllErrorFIlter = .Yearly
func resetModal () {
newContainerSelectedFilter = .Yearly
newContainerDate = Date()
}
}
I have created an enum to get the values from it
enum NewAllErrorFIlter : String {
case Monthly = "2"
case Yearly = "1"
case Daily = "3"
}
Now in my ViewController class I have created a variable
var newContainerModal: NewTabContainerModal?
And in viewWillAppear I am trying to print the value of the enum like this
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let newContainerModelData = newContainerModal?.newContainerSelectedFilter
print(newContainerModelData)
}
but it is giving me nil instead of yearly value. I don't understand what I am doing wrong. Please help?
Its because newContainerModal in your Viewcontroller is nil so newContainerModal?.newContainerSelectedFilter also gonna be a nil change it with
var newContainerModal: NewTabContainerModal = NewTabContainerModal()
In addition
let newContainerModelData = newContainerModal.newContainerSelectedFilter
print(newContainerModelData)
will print Yearly. to get that value use newContainerModelData.rawValue
I'm a newbie in Swift and this is my very first question I want to ask the community.
I have a viewcontroller with a textField, custom button and a label and also I have a separate checker to check whether the entered in textField word is correct or not. So when I push the button the checker is checking and changing the color of the label (green/ red / transparent if textfield.text is empty. How can I implement this using callback closure? Thank you so much in advance!
Here is my ViewController:
final class FeedViewController: UIViewController {
private let checkTextField: UITextField = {
let textField = MyCustomTextField(
font: UIFont.systemFont(ofSize: 16),
textColor: .black,
backgroundColor: .white,
placeholder: "Enter the word")
return textField
}()
private lazy var checkButton: UIButton = {
let button = MyCustomButton(
title: "Check the word",
titleColor: .white,
backgroundColor: .systemGray,
backgroundImage: nil) {
}
button.layer.cornerRadius = 6
button.clipsToBounds = true
return button
}()
private let colorLabel: UILabel = {
let label = UILabel()
label.alpha = 0
label.toAutoLayout()
return label
}()
private let checker: CheckTextField
init(checker: CheckTextField) {
self.checker = checker
super .init(nibName: nil, bundle: nil)
}
Here is my Checker:
class CheckTextField {
private let correctWord = "correct word"
func check( word: String) {
}
}
I have managed to complete this task with notifications, but closures for me are too much complicated :(
you can define your clousure in data sending class as follow
class DataSenderClass {
var dataPassClousure:((String)->Void)?
func sendHere(){
// i am invoking closure from here
dataPassClousure?("hi i am data")
}
}
here is dataPassClousure accepting string as argument(you can adjust according to your choice or can use object as well or can also pass multiple arguments to your closure) to be sent/passed to receiver class.
Just listen to fresh data in your data receiver class as follow
class DataReceiverClass {
let senderObject = DataSenderClass()
func receiveHere() {
//each time data change from sendHere function this closure invoke . perform your action
senderObject.dataPassClousure = {arug1 in
print("received data is \(arug1)")
}
}
}
The closure is actually a parameter of the method that receives and calls it, so if you want check(word:) to call some closure when it finishes checking, you'll need to add that as a parameter:
func check(word: String, closure: ()->Void)
If you want to pass a parameter to the closure, you'll need to specify that too:
func check(word: String, closure: (status: CheckResult)->Void)
I just made up CheckResult here; I just needed some type that you can use to convey the result of the check. It could be a an enumeration, for example:
enum CheckResult {
case ok;
case bad;
case empty;
}
Now, inside your check function, you can call the closure with the appropriate value, like: closure(.ok). To be more concrete, let's fill in your empty function:
class CheckTextField {
private let correctWord = "apple"
func check(word: String, closure: (_ status: CheckResult)->Void) {
if word.count == 0 {
closure(.empty)
} else if word == correctWord {
closure(.ok)
} else {
closure(.bad)
}
}
}
Then we can use it this way:
let checker = CheckTextField()
checker.check(word:"apple") { print($0) } // ok
checker.check(word:"pear") { print($0) } // bad
checker.check(word:"jackfruit") { print($0) } // bad
checker.check(word:"") { print($0) } // empty
You can replace the print($0) with code to set the label color or whatever else you might want to do. That's nice, because it keeps details about your view out of the CheckTextField class.
I would like to know the best possible way to handle the following situation, I have tried an approach as it will be described but I have encountered an issue of events calling each other repeatedly in a circular way hence it causes stackoverflow 😂
I have 4 observables as follows: -
let agreeToPrivacyPolicyObservable = BehaviorRelay<Bool>(value: false)
let agreeToTermsObservable = BehaviorRelay<Bool>(value: false)
let agreeToMarketingEmailObservable = BehaviorRelay<Bool>(value: false)
let agreeToAllOptionsObservable = BehaviorRelay<Bool>(value: false)
Goal:
Sync agree to all button with individual options. ie if agree to all is true/checked then force other options to be checked as well and vice-versa. Additionally if the previous state of all items were checked and either of them emit unchecked then remove a checkmark on Agree to all button.
The image below visualizes my goal above.
What I have tried:
Observable.combineLatest(
agreeToPrivacyPolicyObservable,
agreeToTermsObservable,
agreeToMarketingEmailObservable,
agreeToAllOptionsObservable
, resultSelector:{(termsChecked,privacyChecked,marketingChecked,agreeToAllChecked) in
switch (termsChecked,privacyChecked,marketingChecked,agreeToAllChecked) {
case (true, true, true,true):
//All boxes are checked nothing to change.
break
case (false,false,false,false):
//All boxes are unchecked nothing to change.
break
case (true,true,true,false):
// I omitted the `triggeredByAgreeToAll` flag implementation details for clarity
if triggeredByAgreeToAll {
updateIndividualObservables(checked: false)
}else {
agreeToAllOptionsObservable.accept(true)
}
case (false,false,false,true):
if triggeredByAgreeToAll {
updateIndividualObservables(checked: true)
}else {
agreeToAllOptionsObservable.accept(false)
}
default:
if triggeredByAgreeToAll && agreeToAllChecked {
updateIndividualObservables(checked: true)
}else if triggeredByAgreeToAll && agreeToAllChecked == false {
updateIndividualObservables(checked: false)
} else if (termsChecked == false || privacyChecked == false || marketingChecked == false ) {
agreeToAllOptionsObservable.accept(false)
}
}
}
})
.observeOn(MainScheduler.instance)
.subscribe()
.disposed(by: rx.disposeBag)
// Helper function
func updateIndividualObservables(checked: Bool) {
agreeToPrivacyPolicyObservable.accept(checked)
agreeToTermsObservable.accept(checked)
agreeToMarketingEmailObservable.accept(checked)
}
Explanation:
My attempt gives me Reentracy anomaly was detected error , which according to my observations is caused by events being triggered repeatedly. This seems to occurs in the default switch case (on my solution above). I think this solution is not good as I have to check which event triggered the function execution.
Is there any better approach or is it possible to refactor this solution into something easily manageable? Btw Feel free to ignore my implementation and suggest a different better approach if any. Thanks!
UPDATES (WORKING SOLUTION)
I successfully implemented a working solution by using #Rugmangathan idea (Found on the accepted answer). So I leave my solution here to help anyone in the future facing the same issue.
Here is the working solution: -
import Foundation
import RxSwift
import RxRelay
/// This does all the magic of selecting checkboxes.
/// It is shared across any view which uses the license Agreement component.
class LicenseAgreemmentState {
static let shared = LicenseAgreemmentState()
let terms = BehaviorRelay<Bool>(value: false)
let privacy = BehaviorRelay<Bool>(value: false)
let marketing = BehaviorRelay<Bool>(value: false)
let acceptAll = BehaviorRelay<Bool>(value: false)
private let disposeBag = DisposeBag()
func update(termsChecked: Bool? = nil, privacyChecked: Bool? = nil, marketingChecked: Bool? = nil, acceptAllChecked: Bool? = nil) {
if let acceptAllChecked = acceptAllChecked {
// User toggled acceptAll button so change everything to it's value.
acceptAll.accept(acceptAllChecked)
updateIndividualObservables(termsChecked: acceptAllChecked, privacyChecked: acceptAllChecked, marketingChecked: acceptAllChecked)
} else {
// If either of the individual item is missing change acceptAll to false
if termsChecked == nil || privacyChecked == nil || marketingChecked == nil {
acceptAll.accept(false)
}
updateIndividualObservables(termsChecked: termsChecked, privacyChecked: privacyChecked, marketingChecked: marketingChecked)
}
// Deal with the case user triggered select All from individual items and vice-versa.
Observable.combineLatest(terms, privacy, marketing,resultSelector: {(termsChecked,privacyChecked, marketingChecked) in
switch (termsChecked,privacyChecked, marketingChecked) {
case (true, true, true):
self.acceptAll.accept(true)
case (false,false,false):
self.acceptAll.accept(false)
default:
break
}
})
.observeOn(MainScheduler.instance)
.subscribe()
.disposed(by: disposeBag)
}
// MARK: - Helpers
private func updateIndividualObservables(termsChecked: Bool?,privacyChecked: Bool?, marketingChecked:Bool?) {
if let termsChecked = termsChecked {
terms.accept(termsChecked)
}
if let privacyChecked = privacyChecked {
privacy.accept(privacyChecked)
}
if let marketingChecked = marketingChecked {
marketing.accept(marketingChecked)
}
}
}
Your helper function updateIndividualObservables(:) triggers an event every time you update which in turn triggers the combineLatest you implemented above.
I would suggest you to keep a State object instead
struct TermsAndConditionState {
var terms: Bool
var privacy: Bool
var marketing: Bool
var acceptAll: Bool
}
In updateIndividualObservables method change this state and implement this state change with your respective checkboxes
func render(state: TermsAndConditionState) {
if state.acceptAll {
// TODO: update all checkboxes
} else {
// TODO: update individual checkboxes
}
}
This is a simple state machine. State machines are implemented in Rx using the scan(_:accumulator:) or scan(into:accumulator:) operator like so:
struct Input {
let agreeToPrivacyPolicy: Observable<Void>
let agreeToTerms: Observable<Void>
let agreeToMarketingEmail: Observable<Void>
let agreeToAllOptions: Observable<Void>
}
struct Output {
let agreeToPrivacyPolicy: Observable<Bool>
let agreeToTerms: Observable<Bool>
let agreeToMarketingEmail: Observable<Bool>
let agreeToAllOptions: Observable<Bool>
}
func viewModel(input: Input) -> Output {
enum Action {
case togglePrivacyPolicy
case toggleTerms
case toggleMarketingEmail
case toggleAllOptions
}
let action = Observable.merge(
input.agreeToPrivacyPolicy.map { Action.togglePrivacyPolicy },
input.agreeToTerms.map { Action.toggleTerms },
input.agreeToMarketingEmail.map { Action.toggleMarketingEmail },
input.agreeToAllOptions.map { Action.toggleAllOptions }
)
let state = action.scan(into: State()) { (current, action) in
switch action {
case .togglePrivacyPolicy:
current.privacyPolicy = !current.privacyPolicy
case .toggleTerms:
current.terms = !current.terms
case .toggleMarketingEmail:
current.marketingEmail = !current.marketingEmail
case .toggleAllOptions:
if !current.allOptions {
current.privacyPolicy = true
current.terms = true
current.marketingEmail = true
}
}
current.allOptions = current.privacyPolicy && current.terms && current.marketingEmail
}
return Output(
agreeToPrivacyPolicy: state.map { $0.privacyPolicy },
agreeToTerms: state.map { $0.terms },
agreeToMarketingEmail: state.map { $0.marketingEmail },
agreeToAllOptions: state.map { $0.allOptions }
)
}
struct State {
var privacyPolicy: Bool = false
var terms: Bool = false
var marketingEmail: Bool = false
var allOptions: Bool = false
}
I am using iOS Swift, and I am trying to understand how to execute a method once the value of two variables have been set up (non-null value) once the requests have finished.
After reading some documentation, I have found out some concepts which are interesting. The first one would be didSet, which works as an observer.
I could call the method using this method by simply using didSet if I would require just one variable
didSet
var myVar: String 0 {
didSet {
print("Hello World.")
}
}
Nevertheless, I also need to wait for the second one myVar2, so it would not work.
I have also found DispatchQueue, which I could use to wait a second before calling the method (the requests that I am using are pretty fast)
DispatchQueue
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
print("Hello world")
})
but I consider that this solution is not efficient.
Is there anyway to combine these two variables or requests in order to call a method once they have finishing setting the value?
Update
I have tried to replicate David s answer, which I believe is correct but I get the following error on each \.
Type of expression is ambiguous without more context
I copy here my current code
var propertiesSet: [KeyPath<SearchViewController, Car>:Bool] = [\SearchViewController.firstCar:false, \SearchViewController.secondCar:false] {
didSet {
if propertiesSet.allSatisfy({ $0.value }) {
// Conditions passed, execute your custom logic
print("All Set")
} else {
print("Not yet")
}
}
}
var firstCar: Car? {
didSet {
propertiesSet[\SearchViewController.firstCar] = true
}
}
var secondCar: Car? {
didSet {
propertiesSet[\SearchViewController.secondCar] = true
}
}
The variables are set individually, each one on its own request.
You could make your properties optional and check they both have values set before calling your function.
var varA: String? = nil {
didSet {
if varA != nil && varB != nil {
myFunc()
}
}
}
var varB: String? = nil {
didSet {
if varA != nil && varB != nil {
myFunc()
}
}
}
Or you can call your function on each didSet and use a guard condition at the start of your function to check that both of your properties have values, or bail out:
var varA: String? = nil {
didSet {
myFunc()
}
}
var varB: String? = nil {
didSet {
myFunc()
}
}
func myFunc() {
guard varA != nil && varB != nil else { return }
// your code
}
First, you should think very carefully about what your semantics are here. When you say "set," do you mean "assigned a value" or do you mean "assigned a non-nil value?" (I assume you mean the latter in this case.) You should ask yourself, what should happen if your method has already fired, and then another value is set? What if one of the properties has a value is set, then nil is set, then another value set? Should that fire the method 1, 2, or 3 times?
Whenever possible you should work to make these kinds of issues impossible by requiring that the values be set together, in an init rather than mutable properties, for example.
But obviously there are cases where this is necessary (UI is the most common).
If you're targeting iOS 13+, you should explore Combine for these kinds of problems. As one approach:
class Model: ObservableObject {
#Published var first: String?
#Published var second: String?
#Published var ready = false
private var observers: Set<AnyCancellable> = []
init() {
$first.combineLatest($second)
.map { $0 != nil && $1 != nil }
.assign(to: \.ready, on: self)
.store(in: &observers)
}
}
let model = Model()
var observers: Set<AnyCancellable> = []
model.$ready
.sink { if $0 { print("GO!") } }
.store(in: &observers)
model.first = "ready"
model.second = "set"
// prints "GO!"
Another approach is to separate the incidental state that includes optionals, from the actual object you're constructing, which does not.
// Possible parameters for Thing
struct Parameters {
var first: String?
var second: String?
}
// The thing you're actually constructing that requires all the parameters
struct Thing {
let first: String
let second: String
init?(parameters: Parameters) {
guard let first = parameters.first,
let second = parameters.second
else { return nil }
self.first = first
self.second = second
}
}
class TheUIElement {
// Any time the parameters change, try to make a Thing
var parameters: Parameters = Parameters() {
didSet {
thing = Thing(parameters: parameters)
}
}
// If you can make a Thing, then Go!
var thing: Thing? {
didSet {
if thing != nil { print("GO!") }
}
}
}
let element = TheUIElement()
element.parameters.first = "x"
element.parameters.second = "y"
// Prints "GO!"
You need to add a didSet to all variables that need to be set for your condition to pass. Also create a Dictionary containing KeyPaths to your variables that need to be set and a Bool representing whether they have been set already.
Then you can create a didSet on your Dictionary containing the "set-state" of your required variables and when all of their values are true meaning that all of them have been set, execute your code.
This solution scales well to any number of properties due to the use of a Dictionary rather than manually writing conditions like if aSet && bSet && cSet, which can get out of hand very easily.
class AllSet {
var propertiesSet: [KeyPath<AllSet, String>:Bool] = [\.myVar:false, \.myVar2:false] {
didSet {
if propertiesSet.allSatisfy({ $0.value }) {
// Conditions passed, execute your custom logic
print("All Set")
} else {
print("Not yet")
}
}
}
var myVar: String {
didSet {
propertiesSet[\.myVar] = true
}
}
var myVar2: String {
didSet {
propertiesSet[\.myVar2] = true
}
}
init(myVar: String, myVar2: String) {
self.myVar = myVar
self.myVar2 = myVar2
}
}
let all = AllSet(myVar: "1", myVar2: "2")
all.myVar = "2" // prints "Not yet"
all.myVar2 = "1" // prints "All set"
Is it possible to observe values being added to a Set data-structure?
What i'm trying to achieve:
var storedStrings = Set<String>() {
didSet (value) {
// where value is a string that has been added to the Set
}
}
Example:
storedStrings.insert("hello")
didSet called, as a new value has been added.
storedString.insert("world")
didSet called again.
storedString.insert("hello")
didSet not called, as the set already contains the string "hello"
This can be a bit expensive, but you still can do something like:
var storedStrings = Set<String>() {
didSet {
if storedStrings != oldValue {
print("storedStrings has changed")
let added = storedStrings.subtracting(oldValue)
print("added values: \(added)")
let removed = oldValue.subtracting(storedStrings)
print("removed values: \(removed)")
}
}
}
The insert function returns a tuple with the definition: (inserted: Bool, memberAfterInsert: Element).
Therefore the check for a new unique element can be made on insertion instead of using didSet.
var storedStrings = Set<String>()
var insertionResult = storedStrings.insert("Hello")
if insertionResult.inserted {
print("Value inserted") // this is called
}
insertionResult = storedStrings.insert("Hello")
if insertionResult.inserted {
print("Value inserted") // this isn't called
}
You could implement your own inserter for your set, which could emulate the use of a property observer, making use of the fact that the insert method of Set returns a tuple whose first member is false in case the element to be inserted is already present.
func insert(Element)
Inserts the given element in the set if it is not already present.
From the language reference.
E.g.:
struct Foo {
private var storedStrings = Set<String>()
mutating func insertNewStoredString(_ newString: String) {
if storedStrings.insert(newString).0 {
print("Inserted '\(newString)' into storedStrings")
}
}
}
var foo = Foo()
foo.insertNewStoredString("hello") // Inserted 'hello' into storedStrings
foo.insertNewStoredString("hello")
foo.insertNewStoredString("world") // Inserted 'world' into storedStrings