How to call a method once two variables have been set - ios

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"

Related

Role of underscore "_" in conditional statement (if else) in Swift

Can somebody please explain the role of "_" in Swift code below.
var guessWasMade: Bool {
if let _ = game.guesses[currentQuestion] {
return true
} else {
return false
}
}
I understand how it is usually used as unnamed parameter in for-loops and functions.
But could not find any explanation for if else statement.
Tutorial explains it as guessWasMade checks game.guesses for a value. If a value is found we know the user has made a guess at the question.
game.guesses is an array of integers.
This is part of class declaration. Full code looks like this
class GameViewModel: ObservableObject {
// MARK: - Published properties
// 2
#Published private var game = Game()
// MARK: - Internal properties
// 3
var currentQuestion: Question {
game.currentQuestion
}
// 4
var questionProgressText: String {
"\(game.currentQuestionIndex + 1) / \(game.numberOfQuestions)"
}
// 1
var guessWasMade: Bool {
if let _ = game.guesses[currentQuestion] {
return true
} else {
return false
}
}
// MARK: - Internal Methods
// 2
func makeGuess(atIndex index: Int) {
game.makeGuessForCurrentQuestion(atIndex: index)
}
// 3
func displayNextScreen() {
game.updateGameStatus()
}
}
If let statements check to see if the value is nil. If it is, then the statement is false. If it isn't, then the value is assigned to a variable.
var optionalValue: Int? = 42
if let nonOptionalValue = optionalValue {
print(nonOptionalValue) // -> 42
} else {
print("optionalValue is nil")
}
So, when you do if let _ = game.guesses[...], you are checking to see if game.guesses[...] is nil. If it isn't, then you are ignoring the value with a wildcard pattern (_), which will match anything.
Because you are ignoring the value, it is the same as saying
if game.guesses[...] != nil {
return true
} else {
return false
}
Then, because you are simply returning the value of the condition, you can just return the condition itself:
var guessWasMade: Bool {
game.guesses[currentQuestion] != nil
}

Handling circular style events on observable sequence RxSwift

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
}

Swift: modifying data format through a button click

My goal is to use a button (that contains multiple messages) to trigger a text (making a marker such as first click will be method 1, second click will be method 2) correspondingly added at the end of the my data (after joined(separator: "~")) so that it could help me to analyze which button was clicked when I look back at the data.
Currently, I have a struct that will output the data:
struct CaptureData {
var vertices: [SIMD3<Float>] //A vector of three scalar values. It will return a list of [SIMD3<Float>(x,y,z)]
var mode: Mode = .one
mutating func nextCase() { // the data method will be changed
mode = mode.next()
}
var verticesFormatted : String { //I formatted in such a way so that it can be read more clearly without SIMD3
let v = "<" + vertices.map{ "\($0.x):\($0.y):\($0.z)" }.joined(separator: "~") + "trial: \(mode.next().rawValue)"
return "\(v)"
}
}
Based on #Joshua suggestion
enum Mode: String, CaseIterable {
case one, two, three
}
extension CaseIterable where Self: Equatable {
var allCases: AllCases { Self.allCases }
var nextCase: Self {
let index = allCases.index(after: allCases.firstIndex(of: self)!)
guard index != allCases.endIndex else { return allCases.first! }
return allCases[index]
}
#discardableResult
func next() -> Self {
return self.nextCase
}
}
And the button is alternating the messages after each click,
var x = 0
var instance = CaptureData(vertices: [SIMD3<Float>])
// Next button for changing methods
#IBAction func ChangingTapped(_ btn: UIButton) {
if(x==0){
Textfield.text = "changing to driving"
}
else if(x==1){
Textfield.text = "changing to walking"
instance.nextCase()
}
else{
Textfield.text = "changing to cycling"
instance.nextCase()
}
x += 1
}
Updates: I am able to print one of the methods , .two (method two), after separator: "~". However, at the moment I am still not be able to click button to switch the case in the data.
The main problem is the initialization of variables. I am not able to define var instance = CaptureData(vertices: [SIMD3<Float>]) because it comes with error: Cannot convert value of type '[SIMD3<Float>].Type' to expected argument type '[SIMD3<Float>]'
I am sorry if my explanation is a bit messy here. I am trying to describe the problem I have here. Let me know if there is anything missing! Thank you so much in advance.
Enums is a data type that is more like a constant but much more readable.
An example will be passing in a status to a function.
enum Status {
case success
case failure
}
func updateStatus(_ status: Status) {
statusProperty = status
}
// somewhere in your code
instance.updateStatus(.success)
versus using an Int as a value.
func updateStatus(_ status: Int) {
statusProperty = status
}
// somewhere in your code
instance.updateStatus(1) // eventually you'll forget what this and you'll declare more of a global variable acting as constant, which technically what enums are for.
Enums in swift are a bit different though, much more powerful. More info about enums here
Back to the topic.
enum Mode: String, CaseIterable {
case one, two, three
}
extension CaseIterable where Self: Equatable {
var allCases: AllCases { Self.allCases }
var nextCase: Self {
let index = allCases.index(after: allCases.firstIndex(of: self)!)
guard index != allCases.endIndex else { return allCases.first! }
return allCases[index]
}
#discardableResult
func next() -> Self { // you don't need to update self here, remember self here is one of the items in the enum, i.e. one, so assigning one = two just doesn't work.
return self.nextCase
}
}
// The data struct
struct CaptureData {
var mode: Mode = .one
// we add a mutation function here so we can update the mode
mutating func nextCase() { // the data your concern about, that can actually mutate is the mode property inside CaptureData struct.
mode = mode.next()
}
}
So lets say somewhere in the app you can use it like this you initialised an instance of CaptureData:
var instance = CaptureData() // Don't forget it should be var and not let, as we are updating its property.
instance.nextCase() // get the next case, initially it was .one
print(instance.mode) // prints two
instance.nextCase() // from .two, changes to .three
print(instance.mode) // prints three
Hope this helps.

Swift - Assign newValue to an Object

I know that probably is a very simple question, however I spent a lot of time trying to figure out why is not working, and still doesn't make sense for me, so I need help.
I am learning iOS, I came from Android where I am used to work with objects.
I want to do something similar that I have been doing with Android(maybe this is the problem).
I have created an object with the methods get and set.
private var _token: String!
var error: String {
get {
guard self._error != nil else { return "" }
return _error
}
set {
self._token = newValue
}
}
When I want to manipulate this object, is when I am having the problem.
let objectCreated = ObjectCreated()
guard let errorReceived = (xml["Whatever"]["Whatever"].element?.text) else { return }
print(errorReceived)
objectCreated.error = errorReceived
print(objectCreated.error)
The first print is printing the correct String, but the second is printing "". So the set method is not doing his job.
Isn't it supposed to be
...
guard self._token != nil else { return "" }
return _token
...
This can be simplified with the nil-coalescing operator
var error: String {
get {
return self._token ?? ""
}
set {
self._token = newValue
}
}
Note: Variable names with leading underscores are unusual in Swift.
private var _error: String!
var error: String {
get {
guard self._error != nil else { return "" }
return _error
}
set {
self._error = newValue
}
}
self._token = newValue >>>> self._error = newValue

Observe values added to Set - Swift

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

Resources