Using #AppStorage - appstorage

is it possible to store a set of boolean variables by using #AppStorage.
Currently I use e.g. this situation:
class UserSettings: ObservableObject {
#Published var localTime : Bool {
didSet {
UserDefaults.standard.set(localTime, forKey:"local")
}
}
...
init() {
self.localTime = UserDefaults.standard.object(forKey: "local") as? Bool ?? false
...
}
}
Thanks for your support.
Peter

Try this:
class UserSettings: ObservableObject {
#AppStorage("local") var localTime = false
}

Related

Observing multiple published variable changes inside ObservableObject

I have an ObservableObject that contains multiple published variables to handle my app state.
Whenever one of those published variables change, I want to call a function inside my ObservableObject. What's the best way to do that?
class AppModelController: ObservableObject {
#Published var a: String = "R"
#Published var b: CGFloat = 0.0
#Published var c: CGFloat = 0.9
// Call this function whenever a, b or c change
func check() -> Bool {
}
}
You can use didSet, like this code:
class AppModelController: ObservableObject {
#Published var a: String = "R" { didSet(oldValue) { if (a != oldValue) { check() } } }
#Published var b: CGFloat = 0.0 { didSet(oldValue) { if (b != oldValue) { check() } } }
#Published var c: CGFloat = 0.9 { didSet(oldValue) { if (c != oldValue) { check() } } }
func check() {
// Some Work!
}
}
The simplest thing you can do is listen to objectWillChange. The catch is that it gets called before the object updates. You can use .receive(on: RunLoop.main) to get the updates on the next loop, which will reflect the changed values:
import Combine
class AppModelController: ObservableObject {
#Published var a: String = "R"
#Published var b: CGFloat = 0.0
#Published var c: CGFloat = 0.9
private var cancellable : AnyCancellable?
init() {
cancellable = self.objectWillChange
.receive(on: RunLoop.main)
.sink { newValue in
let _ = self.check()
}
}
// Call this function whenever a, b or c change
func check() -> Bool {
return true
}
}

How to unit test private members of a class in swift? [duplicate]

This question already has answers here:
Swift - Unit testing private variables and methods
(8 answers)
Closed 1 year ago.
I have a class with few public and private members like below :
public class ReviewManager {
public static let shared = ReviewManager()
private static let startDateKey = "StartDate"
private static let popupLastSeenKey = "LastSeen"
private static let lastVersionPromptedForReview = "lastVersionPromptedForReview"
private(set) var startDate: Date {
get {
Date()
}
set { }
}
private(set) var lastPopupDate: Date? {
get {
}
set {
}
}
private(set) var lastVersionPromptedForReview: String {
get {
}
set {
}
}
private(set) var appCurrentVersion: String {
get {
// Get the current bundle version for the app
guard let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
else {
logError("Expected to find a bundle version in the info dictionary")
return ""
}
return currentVersion
}
set { }
}
private func requestReview() {
SKStoreReviewController.requestReview()
lastPopupDate = Date()
lastVersionPromptedForReview = appCurrentVersion
}
public func requestReviewIfPossible() {
let today = Date()
if lastPopupDate == nil {
if allowedToPresentToday() && allowedToPresentForThisVersion() {
requestReview()
}
} else {
if let lastPopupDate = lastPopupDate {
if allowedToPresentToday() && allowedToPresentForThisVersion() {
requestReview()
}
}
}
}
private func allowedToPresentToday() -> Bool {
let calendar = Calendar(identifier: .gregorian)
let today = Date()
let components = calendar.dateComponents([.weekday], from: today!)
return components.weekday == 5 || components.weekday == 6
}
private func allowedToPresentForThisVersion() -> Bool {
let allowedToShowForThisVersion = (appCurrentVersion != lastVersionPromptedForReview) ? true : false
return allowedToShowForThisVersion
}
}
Now I want to write Unit tests for all the private functions in this class. I can access private properties in this class by making access modifier as
private(set)
But private functions can not be accessed outside the class.
Is there any way to unit test private functions in Swift ?
Completely echo #luk2302, in that you should be testing the public interface. But if you feel you really must, perhaps consider creating a wrapper function around the private function to expose it. You could make this compile conditionally, say in this example only in DEBUG mode, so it doesn't pollute your production code:
class MyClass {
private func myPrivateFunc() -> Void {
print("Hello")
}
}
#if DEBUG
extension MyClass {
func myPublicFunc() -> Void {
myPrivateFunc()
}
}
#endif
let myClass = MyClass()
myClass.myPublicFunc()

Swift - toggle model to readonly momentarily

I have a phone number model which looks like this:
import UIKit
import Foundation
struct PhoneValidation : OptionSet {
let rawValue: Int
static let phoneInValid = PhoneValidation(rawValue: 1 << 0)
static let phoneValid = PhoneValidation(rawValue: 1 << 1)
static let smsValidationAttempted = PhoneValidation(rawValue: 1 << 2)
static let smsValidationFailed = PhoneValidation(rawValue: 1 << 3)
static let smsValidationSuccessful = PhoneValidation(rawValue: 1 << 4) // OTP is successfully validated in backend. The field should be non-editable in this duration
static let smsValidationOTPTriggered = PhoneValidation(rawValue: 1 << 5) // OTP validation triggered. The field should be non-editable in this duration
}
class PhonesViewModel: NSCopying {
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
public var phone: String?
public var code: String?
public var countryCode: String?
public var isValid : PhoneValidation?
func copy(with zone: NSZone? = nil) -> Any {
let copy = PhonesViewModel()
copy.phoneType = phoneType
copy.phone = phone
copy.code = code
copy.countryCode = countryCode
copy.isValid = isValid
return copy
}
}
As you can see above the phone model can transition between different states. The SMS validation is available for few countries and for few it is not applicable. So, I plan on setting smsValidationOTPTriggered state when SMS validation is applicable for a country and while the validation is in progress.
What I need here is, while the states smsValidationOTPTriggered or smsValidationSuccessful are set I would not want any module of the application to modify the values(phoneType, phone, code, countryCode) of the model. In other words, I would like the model to switch to a read-only mode while these 2 states are set in model and would like the module to be informed with an error or exception when a modification is attempted.
Is there a best practice already available for what I am trying to achieve here? I have searched before raising this question but did not find any. How can I achieve this?
Thanks,
Raj Pawan Gumdal
How about something like this, I think its better to use property wrappers for your case! The below is not an exact solution but can modify/change to accommodate your need
import UIKit
enum PhoneNumberType {
case mobile
}
enum PhoneValidation {
case phoneInValid
case phoneValid
case smsValidationAttempted
case smsValidationFailed
case smsValidationSuccessful
case smsValidationOTPTriggered
}
struct PhonesViewModel {
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
public var phone: String?
public var code: String?
public var countryCode: String?
public var phoneValidation : PhoneValidation?
func validate(value: [PhoneValidation]) -> Bool {
//add proper check here
return false
}
}
#propertyWrapper
struct Wrapper {
private(set) var value: PhonesViewModel? = nil
var validators: [PhoneValidation] = []
var wrappedValue: PhonesViewModel? {
get { value }
set {
if let model = newValue, model.validate(value: validators) {
value = newValue
print("Value assigned")
} else {
print("Value not assigned")
}
}
}
}
struct SomeOtherClass {
#Wrapper(validators: [PhoneValidation.phoneInValid])
var model: PhonesViewModel?
}
var a = SomeOtherClass()
a.model = PhonesViewModel()
a.model = PhonesViewModel()
You can use a technique with the name "popsicle immutability". An object is initially mutable, but can be "frozen". Modifications for frozen objects are forbidden. In your case PhonesViewModel become frozen when isValid property have value smsValidationOTPTriggered or smsValidationSuccessful.
Let's add Freezable protocol for requirements to objects that can become immutable and conforming for PhonesViewModel:
protocol Freezable: class {
var isFrozen: Bool { get }
}
extension PhonesViewModel: Freezable {
var isFrozen: Bool {
isValid == .smsValidationOTPTriggered || isValid == .smsValidationSuccessful
}
}
Now we must add validation for isFrozen value when a property is assigned. It can be added in property observers like:
...
public var phone: String? {
didSet {
validate()
}
}
...
private func validate() {
assert(!isFrozen)
}
Or using property wrapper:
#propertyWrapper
struct Guarded<Value> {
private var value: Value
init(wrappedValue: Value) {
value = wrappedValue
}
#available(*, unavailable)
var wrappedValue: Value {
get { fatalError("only works on instance properties of classes that conforms to Freezable protocol") }
set { fatalError("only works on instance properties of classes that conforms to Freezable protocol") }
}
static subscript<EnclosingSelf: Freezable>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
) -> Value {
get {
object[keyPath: storageKeyPath].value
}
set {
precondition(!object.isFrozen, "Object \(object) is frozen! Modifications are forbidden")
object[keyPath: storageKeyPath].value = newValue
}
}
}
So your class will look like:
class PhonesViewModel: NSCopying {
#Guarded
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
#Guarded
public var phone: String?
#Guarded
public var code: String?
#Guarded
public var countryCode: String?
#Guarded
public var isValid : PhoneValidation?
func copy(with zone: NSZone? = nil) -> Any {
let copy = PhonesViewModel()
copy.phoneType = phoneType
copy.phone = phone
copy.code = code
copy.countryCode = countryCode
copy.isValid = isValid
return copy
}
}

What am I doing wrong on passing data through protocol

I'm trying to pass data between viewControllers, but something seems wrong.
The first viewController I want to set the "Bool" to the protocol function to be able to recover in the other screen. What am I doing wrong, I always used protocols but at this time I got in trouble.
That's how I'm doing that:
//
// ComboBoxNode.swift
//
import Foundation
import SWXMLHash
protocol ComboBoxNodeDelegate {
func getCustomOption(data:Bool)
}
class ComboBoxNode: FormControlNode, IFormControlDataSource {
var listType: String?
var dataSource: String?
var dataSourceValue: String?
var dataSourceText: String?
var hasCustomOption:Bool?
var customOptionText: String?
var ctrlDataSourceType: String?
var parameters = [ParameterNode]()
var staticList: FormControlStaticListNode?
var delegate:ComboBoxNodeDelegate?
override init(indexer: XMLIndexer) {
super.init(indexer: indexer)
guard let element = indexer.element else {
preconditionFailure("Error")
}
let isCustomOption = element.bool(by: .hasCustomOption) ?? hasCustomOption
if isCustomOption == true {
self.delegate?.getCustomOption(data: hasCustomOption!)
}
self.readFormControlDataSource(indexer: indexer)
}
override func accept<T, E: IViewVisitor>(visitor: E) -> T where E.T == T {
return visitor.visit(node: self)
}
}
That's how I'm trying to recover on next screen:
// FormPickerViewDelegate.swift
import Foundation
import ViewLib
import RxSwift
class FormPickerViewDelegate: NSObject {
var items = Variable([(value: AnyHashable, text: String)]()) {
didSet {
PickerNodeDelegate = self
self.setDefaultValues()
}
}
private var controlViewModel: FormControlViewModel
private var customText:Bool?
private var PickerNodeDelegate:ComboBoxNodeDelegate?
init(controlViewModel: FormControlViewModel) {
self.controlViewModel = controlViewModel
}
func getItemByValue(_ value: Any) -> (AnyHashable, String)? {
if value is AnyHashable {
let found = items.value.filter {$0.value == value as! AnyHashable}
if found.count >= 1 {
return found[0]
}
}
return nil
}
}
extension FormPickerViewDelegate:ComboBoxNodeDelegate {
func getCustomOption(data: Bool) {
customText = data
}
}
Instead of setting PickerNodeDelegate = self in didSet {} closure
var items = Variable([(value: AnyHashable, text: String)]()) {
didSet {
PickerNodeDelegate = self
self.setDefaultValues()
}
}
Assign it in your init() function instead
init(controlViewModel: FormControlViewModel) {
self.controlViewModel = controlViewModel
PickerNodeDelegate = self
}
Note, your should declare your delegate to be weak also, since it's a delegate, your protocol should conform to be a class type in order to be weakified.
protocol ComboBoxNodeDelegate: class
...
weak var delegate: ComboBoxNodeDelegate?
Here is an example, hope it helps!
protocol ComboBoxNodeDelegate {
func getCustomOption(data:Bool) -> String
}
class ViewOne:ComboBoxNodeDelegate {
var foo:Bool = false
var bar:String = "it works!"
/** Return: String */
func getCustomOption(data:Bool) -> String { //conform here to protocol
// do whatever you wanna do here ...example
self.foo = data // you can set
return bar // even return what you want
}
//initialize
func initalizeViewTwo() {
let v2 = ViewTwo()
v2.delegate = self //since `self` conforms to the ComboBoxNodeDelegate protcol you are allowed to set
}
}
class ViewTwo {
var delegate:ComboBoxNodeDelegate?
func getCustomOption_forV1() {
let view2_foo = delegate.getCustomOption(data:true)
print(view2_foo) // should print "it works!"
}
}
All parameters passed around in Swift are constants -- so you cannot change them.
If you want to change them in a function, you must declare your protocol to pass by reference with inout:
protocol ComboBoxNodeDelegate {
func getCustomOption(data: inout Bool)
}
Note: you cannot pass a constant (let) to this function. It must be a variable -- which I see you are doing!

iOS swift singleton clear data

class ShareData {
class var sharedInstance: ShareData {
struct Static {
static var instance: ShareData?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = ShareData()
}
return Static.instance!
}
var someString : String! //Some String
var selectedTheme : AnyObject! //Some Object
var someBoolValue : Bool!
}
This is my singleton design.However , I want to know how I can clear all its data as and when required?
Also can i have more than one singleton Class??
Since you've only got 3 properties on your singleton it would be far easier just to set up a method that nils each property in turn.
Once you start getting in to how to destroy and recreate your singleton, you get in to the realm of do you actually even want a singleton or should you just be using a regular object.
You are creating a Singleton with the syntax available in... 2014
Today there's a better syntax to define a Singleton class
final class SharedData {
static let sharedInstance = SharedData()
private init() { }
var someString: String?
var selectedTheme: AnyObject?
var someBoolValue: Bool?
func clear() {
someString = nil
selectedTheme = nil
someBoolValue = nil
}
}
As you can see I also added the clearData() method you were looking for.

Resources