I am creating a wizard using UICollectionView with an array of CollectionViewCells:
var viewCells:[BaseCVCell] = [createEventSubjectSearch(), createEventEventForm()]
This array is dynamically added to based on a series of UISwitch's that the user controls. I can add to the array fine using the code below, however I can't seem to remove an item when a user turns the switch off.
func switchToggled(sender : UISwitch) {
if sender == createDiarySwitch {
if sender.isOn {
parentClass?.viewCells.append(createEventDeferEvent())
} else {
if let i = parentClass?.viewCells.index(where: { $0 == createEventDeferEvent() }) {
parentClass?.viewCells.remove(at: i)
}
}
}
if sender == createDeferredSwitch {
if sender.isOn {
parentClass?.viewCells.append(createEventDiariseEvent())
} else {
if let i = parentClass?.viewCells.index(where: { $0 == createEventDiariseEvent() }) {
parentClass?.viewCells.remove(at: i)
}
}
}
parentClass?.wizardCollectionView.reloadData()
}
I have tried the above code, as well as:
if let index = parentClass?.viewCells.index(of: createEventDiariseEvent()) {
parentClass?.viewCells.remove(at: index)
}
Neither approach works (no errors, the code just never returns a value). I'd like to try and avoid naming elements where possible. Is there a way to do this?
Thanks for your answers, DonMag
I've achieved the desired functionality by instanciating the two dynamic cells in the main class:
let diariseCell : createEventDiariseEvent()
and then in the loop calling as thus:
if sender == createDiarySwitch {
if sender.isOn {
parentClass?.viewCells.append((parentClass?.diariseCell)!)
} else {
if let i = parentClass?.viewCells.index(where: { $0 == parentClass?.diariseCell }) {
print("Found cell reference at index \(i)")
parentClass?.viewCells.remove(at: i)
}
}
}
Works a charm now. Amazing what another pair of eyes can pick out!
Related
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 trying to filter my homeCollectionView with SegmentControl. On taping on the segment, I am filtering the content based on tag available in the dictionary. When I am performing ReloadData and switching between the segments, in the first go (when I am taping on the segments the first time), the filter is working and all data is coming, but when I tap back on the segments, part of the content in the cell, especially the LabelViews text are not showing up afterwards. Also, it's happening for random indexPath.
This is my code:
#objc func toggleHomeContent(_ notification: NSNotification) {
toggleValContType = notification.object as? String ?? "all"
if (toggleValContType == "all") {
mainArrayData = primaryArrayData
}
else if (toggleValContType == "collections") {
mainArrayData = primaryArrayData { $0["filterType"] == "Col" || $0["filterType"] == "CTA" }
}
else if (toggleValContType == "books") {
mainArrayData = primaryArrayData { $0["filterType"] == "Book" || $0["filterType"] == "CTA" }
}
homeCollectionView?.reloadData()
homeCollectionView?.layoutIfNeeded()
homeCollectionView?.collectionViewLayout.invalidateLayout()
//DispatchQueue.main.async(execute: homeCollectionView.reloadData)
}
And by arrays are declared like this:
var mainArrayData : [[String:String]] = HomeArray().mainArray
var primaryArrayData: [[String:String]] = HomeArray().mainArray
Heres the snapshot of what the issue is:
Snapshot of the issue
Thanks in advance!
Add this code in main thread :
homeCollectionView?.reloadData()
homeCollectionView?.layoutIfNeeded()
DispatchQueue.main.async {
homeCollectionView?.reloadData()
homeCollectionView?.layoutIfNeeded()}
I am trying to create adding to favorite button, I'am able to add object to an array but for some reason I cannot delete it from the array. How to remove it from the array? .Here is my code. thanks
func didClickFavoriteButton(item: Item) {
// removing from favorite (not working)
if user.favoritCar.contains(item.id!) {
let index = user.favoritCar.firstIndex(of:item.id!)
user.favoritCar.remove(at: index!)
FirebaseReference(.User).document(kFAVORIT).updateData([kFAVORIT :
FieldValue.arrayRemove(user.favoritCar)])
} else {
// Adding to favorite
user.favoritCar.append(item.id!)
FirebaseReference(.User).document(Auth.auth().currentUser!.uid).updateData([kFAVORIT : FieldValue.arrayUnion(user.favoritCar)])
}
Can you try this code:
if let temp = user.favoritCar.first(where: {$0.id == item.id}) {
if let index = user.favoritCar.firstindex(of: temp) {
user.favoritCar.remove(at: index)
}
}
Instead of this code:
if user.favoritCar.contains(item.id!) {
let index = user.favoritCar.firstIndex(of:item.id!)
user.favoritCar.remove(at: index!)
I have an array of dictionary with custom object in swift.
Now I am comparing the object for add & update.
The logic is as simple to add the data if not exist and update if any change in dictionary.
User is custom object type:
#objc public class User: NSObject , Mappable
from the getUserID i can able to get userID
The below code is execute in for loop from where i am passing User object.
var peopleList = [User]()
if self.peopleList.count > 0 {
if self.peopleList.contains(where: {$0.getUserID() == users.getUserID()})
{
// check for any update in dist
if let index = self.peopleList.index(of: users)
{
if users.isEqual(self.peopleList[index])
{
print("equal no updates")
}
else
{
print("need to updates objects..")
}
}
//already exist room
}
else
{
self.peopleList.append(users)
}
}
I know it may be related to equatable
so I am using below fuction
func isEqual<T: Equatable>(type: T.Type, a: Any, b: Any) -> Bool? {
guard let a = a as? T, let b = b as? T else { return nil }
return a == b
}
But I am getting index = nil.
Is there any idea or suggestion to solve it.
If any other way to do it efficiently them most welcome.
I think this simplified version should work
if self.peopleList.isEmpty, let user = self.peopleList.first(where: { $0.getUserID() == users.getUserID() }) {
if user == users {
// is equal
} else {
// do update
}
} else {
self.peopleList.append(users)
}
I would like to find the first EKSource of type EKSourceType.Local with a "single"-line expression in Swift. Here is what I currently have:
let eventSourceForLocal =
eventStore.sources[eventStore.sources.map({ $0.sourceType })
.indexOf(EKSourceType.Local)!]
Is there a better way of doing this (such as without mapping and/or with a generic version of find)?
Alternatively in Swift3 you could use:
let local = eventStore.sources.first(where: {$0.sourceType == .Local})
There's a version of indexOf that takes a predicate closure - use it to find the index of the first local source (if it exists), and then use that index on eventStore.sources:
if let index = eventStore.sources.indexOf({ $0.sourceType == .Local }) {
let eventSourceForLocal = eventStore.sources[index]
}
Alternately, you could add a generic find method via an extension on SequenceType:
extension SequenceType {
func find(#noescape predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Self.Generator.Element? {
for element in self {
if try predicate(element) {
return element
}
}
return nil
}
}
let eventSourceForLocal = eventStore.sources.find({ $0.sourceType == .Local })
(Why isn't this there already?)
I don't understand why you're using map at all. Why not use filter? You will then end up with all the local sources, but in actual fact there will probably be only one, or none, and you can readily find out by asking for the first one (it will be nil if there isn't one):
let local = eventStore.sources.filter{$0.sourceType == .Local}.first
Swift 4 solution that also handles the situation when there are no elements in your array that match your condition:
if let firstMatch = yourArray.first{$0.id == lookupId} {
print("found it: \(firstMatch)")
} else {
print("nothing found :(")
}
Swift 5 If you want to find out from Array of Model then speciyfy $0.keyTofound otherwise use $0
if let index = listArray.firstIndex(where: { $0.id == lookupId }) {
print("Found at \(index)")
} else {
print("Not found")
}
Let's try something more functional:
let arr = [0,1,2,3]
let result = arr.lazy.map { print("💥"); return $0 }.first(where: { $0 == 2 })
print(result) // 3x 💥 then 2
Whats cool about this?
You get access to element or i while you search. And it's functional.
For Swift 3 you'll need to make a few small changes to Nate's answer above. Here's the Swift 3 version:
public extension Sequence {
func find(predicate: (Iterator.Element) throws -> Bool) rethrows -> Iterator.Element? {
for element in self {
if try predicate(element) {
return element
}
}
return nil
}
}
Changes: SequenceType > Sequence, Self.Generator.Element > Iterator.Element