I am saving an Int in UserDefaults and this is reduced by one by clicking a button. I don't know if that is important but I have added an extension to UserDefaults to load an initial value if the app starts for the first time:
extension UserDefaults {
public func optionalInt(forKey defaultName: String) -> Int? {
let defaults = self
if let value = defaults.value(forKey: defaultName) {
return value as? Int
}
return nil
}
}
The UserDefaults are used as ObservableObject and accessed as EnvironmentObject within the app like this:
class Preferences: ObservableObject {
#Published var counter: Int = UserDefaults.standard.optionalInt(forKey: COUNTER_KEY) ?? COUNTER_DEFAULT_VALUE {
didSet {
UserDefaults.standard.set(counter, forKey: COUNTER_KEY)
}
}
}
I am now trying to test that the value in the UserDefaults decreases when the button is clicked.
I am trying to read the UserDefaults in the test with:
XCTAssertEqual(UserDefaults.standard.integer(forKey: "COUNTER_KEY"), 9)// default is 10
I have tried it with normal UnitTests where the methods behind the Button are called and with UITests but both do not work. In the UnitTests I get back the COUNTER_DEFAULT_VALUE and in the UiTests I get back 0.
I am trying to access the UserDefaults directly in the test, instead of using the Preferences object, because I have not found a way to access that as it is an ObservableObject.
I have checked in the Emulator that the UserDefaults are saved/loaded correctly when using the app. Is it not possible to access the UserDefaults in the tests or am I doing it wrong?
The key to success is Dependency injection. Instead of directly accessing the shared user defaults object (UserDefaults.standard), declare an object of type UserDefaults within the class:
let userDefaults: UserDefaults
In the view where you declare the model, you are free to use the shared object:
#StateObject var model = Preferences(userDefaults: UserDefaults.standard)
But inside your test, create an dedicated UserDefaults object and pass it to the initializer like so:
let userDefaults = UserDefaults(suiteName: #file)!
userDefaults.removePersistentDomain(forName: #file)
let model = Preferences(userDefaults: userDefaults)
The benefit is clear: You control the state of UserDefaults. And that means the code works in every environment. To keep it simple, I haven't incorporated your extension, yet. But I'm sure you will manage to get it working.
TL;DR
Please see my working example below:
ContentView.swift
import SwiftUI
import Foundation
class Preferences: ObservableObject {
let userDefaults: UserDefaults
#Published var counter: Int {
didSet {
self.userDefaults.set(counter, forKey: "myKey")
}
}
init(userDefaults: UserDefaults) {
self.userDefaults = userDefaults
self.counter = userDefaults.integer(forKey: "myKey")
}
func decreaseCounter() {
self.counter -= 1
}
}
struct ContentView: View {
#StateObject var model = Preferences(userDefaults: UserDefaults.standard)
var body: some View {
HStack {
Text("Value: \(self.model.counter)")
Button("Decrease") {
self.model.decreaseCounter()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
InjectionTests.swift
import XCTest
#testable import Injection
class InjectionTests: XCTestCase {
func testPreferences() throws {
// arrange
let userDefaults = UserDefaults(suiteName: #file)!
userDefaults.removePersistentDomain(forName: #file)
let model = Preferences(userDefaults: userDefaults)
// act
let valueBefore = userDefaults.integer(forKey: "myKey")
model.decreaseCounter()
let valueAfter = userDefaults.integer(forKey: "myKey")
// assert
XCTAssertEqual(valueBefore - 1, valueAfter)
}
}
Core data in my custom framework stores data separately for its widget and main app. The widget doesn't load data stored from the main app. Is there any solution to fetch data from a widget that is stored from main app?
Code in framework:
import Foundation
import CoreData
public class CoreDataManager {
public static let shared = CoreDataManager()
let identifier = "com.Appnap.DataBaseFramework"
let model = "CoreDataModel"
lazy var persistentContainer: NSPersistentContainer = {
let messageKitBundle = Bundle(identifier: self.identifier)
let modelURL = messageKitBundle!.url(forResource: self.model, withExtension: "momd")!
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
let container = NSPersistentContainer(name: self.model, managedObjectModel: managedObjectModel!)
container.loadPersistentStores { (storeDescription, error) in
if let err = error{
fatalError("❌ Loading of store failed:\(err)")
}
}
return container
}()
//MARK: - Append data
public func createTask(task: String){
let context = persistentContainer.viewContext
let contact = NSEntityDescription.insertNewObject(forEntityName: "Task", into: context) as! Task
contact.id = UUID()
contact.taskdescription = task
do {
try context.save()
print("✅ Person saved succesfuly")
} catch let error {
print("❌ Failed to create Person: \(error.localizedDescription)")
}
}
//MARK: - Fetch data
public func fetch() -> [String] {
var data = [String]()
let context = persistentContainer.viewContext
let fetchRequest = NSFetchRequest<Task>(entityName: "Task")
do{
let allTask = try context.fetch(fetchRequest)
for (_,task) in allTask.enumerated() {
print(task.taskdescription!)
data.append(task.taskdescription!)
}
}catch let fetchErr {
print("❌ Failed to fetch Person:",fetchErr)
}
return data
}
}
I just called the "createTask" & "fetch" functions from main app and widget. But main app and widget stores data in different container.
Good evening,
I'm implementing a Realm Database with SwiftUI.
The Database is made of a table containing "Projects" and a table containing "Measures" (relation one-to-many).
The main view displays the project list and "Measureview" displays the measures related to the project selected.
When I select a project, the measures list is displayed and then impossible to go back, the app crashes (simulator and real device).
Xcode points AppDelegate file : Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffedfd5cfd8)
I know that 4/5 months ago, some developers experienced this issue but I suppose that currently Apple fix the problem.
Please find below the relevant code :
Realm part :
import Foundation
import RealmSwift
import Combine
class Project : Object, Identifiable {
#objc dynamic var ProjectName = "" // primary key
#objc dynamic var ProjectCategorie = ""
#objc dynamic var ProjectCommentaire = ""
let Measures = List<Measure>() // one to many
override static func primaryKey() -> String? {
return "ProjectName"
}
}
class Measure : Object, Identifiable {
// #objc dynamic var id_Measure = UUID().uuidString // primary key
#objc dynamic var MeasureName = ""
#objc dynamic var MeasureDetail = ""
#objc dynamic var MeasureResult = ""
override static func primaryKey() -> String? {
return "MeasureName"
}
}
func createProject (_ title:String,_ categorie:String, _ commentaire:String) {
let realm = try! Realm()
let proj = Project()
proj.ProjectName = title
proj.ProjectCategorie = categorie
proj.ProjectCommentaire = commentaire
try! realm.write {
realm.add(proj)
}
}
//****************************************************************
class BindableResults<Element>: ObservableObject where Element: RealmSwift.RealmCollectionValue {
let didChange = PassthroughSubject<Void, Never>()
let results: Results<Element>
private var token: NotificationToken!
init(results: Results<Element>) {
self.results = results
lateInit()
}
func lateInit() {
token = results.observe { _ in
self.didChange.send(())
}
}
deinit {
token.invalidate()
}
}
Contentview :
struct ContentView : View {
#ObservedObject var Proj = BindableResults(results: try! Realm().objects(Project.self))
var body: some View {
NavigationView{
List(Proj.results) { item in
NavigationLink(destination: MeasureView(Pro: item) ){
ContenRowUI(Proj :item)
}
}
}
.navigationBarTitle(Text("Project List"))
}
}
Measure view :
struct MeasureView: View {
var Pro = Project() //= Project()
var body: some View {
NavigationView {
List(Pro.Measures) { item in
Text("Detail: \(item.MeasureDetail)")
}
.navigationBarTitle(Text("Measure"))
}
}
}
Additional information, if I replace Measureview by a simple textview, the behaviour is very weird :
I select a Project, the application shows the textview and goes back automatically to the main list (project list)
If someone could help me, I would be grateful.
Thanks a lot for your support.
Jeff
I'm getting one compiler error when I try to build one object in my xcode project. This is the code:
import UIKit
class Rectangulo: NSObject {
var ladoA : Int
var ladoB : Int
var area: Int {
get {
return ladoA*ladoB
}
}
init (ladoA:Int,ladoB:Int) {
self.ladoA = ladoA
self.ladoB = ladoB
}
func description() -> NSString {
return "El area es \(area)"
}
}
The error in compilation time is:
Rectangulo.swift:26:10: Method 'description()' with Objective-C selector 'description' conflicts with getter for 'description' from superclass 'NSObject' with the same Objective-C selector
What I need to do to override this function without issues?
description is a (computed) property of NSObjectProtocol, not a method.
Its Swift view returns a String, not NSString.
Since you are overriding a property of a superclass, you must specify override explicitly.
Together:
// main.swift:
import Foundation
class Rectangulo: NSObject {
var ladoA : Int
var ladoB : Int
var area: Int {
get {
return ladoA*ladoB
}
}
init (ladoA:Int,ladoB:Int) {
self.ladoA = ladoA
self.ladoB = ladoB
}
override var description : String {
return "El area es \(area)"
}
}
let r = Rectangulo(ladoA: 2, ladoB: 3)
print(r) // El area es 6
I begin my project with a split view controller as initial view controller and start it automatically from storyboard.
Generally, an app with this UI have one and only one split view controller as root, so I create a static variable in the subclass and set it when initialisation was done.
So I want try this behaviour with swift.
I read the Swift programming language guide book on iBook about Type properties (with static and class keyword) and trying a piece of code to the job:
import UIKit
class SplitViewController: UISplitViewController {
class func sharedInstance() -> SplitViewController {
return SplitViewController.instance
}
class let instance: SplitViewController = nil
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.initialization()
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder);
self.initialization()
}
func initialization() {
SplitViewController.instance = self;
}
}
but I figured out when Xcode say the class keyword for type properties wasn't supported yet.
Did you have a solution to do this ?
Embedding a struct can work just fine as a workaround:
class SomeClass
{
// class var classVariable: Int = 0
// "Class variables not yet supported." Weird.
// Workaround:
private struct SubStruct { static var staticVariable: Int = 0 }
class var workaroundClassVariable: Int
{
get { return SubStruct.staticVariable }
set { SubStruct.staticVariable = newValue }
}
}
The SomeClass.workaroundClassVariable computed type property can then be used as if it were a stored type property.
Swift now has support for static variables in classes. This is not exactly the same as a class variable (because they aren't inherited by subclasses), but it gets you pretty close:
class X {
static let y: Int = 4
static var x: Int = 4
}
println(X.x)
println(X.y)
X.x = 5
println(X.x)
It seems to be possible to declare variables with static storage duration in file scope (as in C):
var sharedInstance: SplitViewController? = nil
class SplitViewController: UISplitViewController {
....
func initialization() {
sharedInstance = self
}
}
My preferred method is to just use a private file scope var outside of the class and then implement class/static getters and setters:
private var _classVar: Int = 0;
class SomeClass
{
public class var classVar: Int
{
get { return _classVar }
set { _classVar = newValue }
}
}
As of Swift 1.2 (available with Xcode 6.3b1 and onwards), static class properties and methods are supported.
class SomeClass
{
static var someVariable: Int = 0
}
Using a dispatch_once singleton model in Swift
Seems to be the best answer so far, avoiding the use of a global variable.
A solution enough similar than var in file scope but more customisable and near singleton is to use a struct which support static var as property of class
struct PersonSharedData {
static var backstore = ""
var data: String {
get { return PersonSharedData.backstore }
set { PersonSharedData.backstore = newValue }
}
}
class Person {
var shared=PersonSharedData() //<< pseudo class var
var family: String {
get { return shared.data }
set { shared.data=newValue }
}
var firstname = ""
var lastname = ""
var sexe: Sexe = .Unknown
}
Ok, with the solution of Nikolai that do the work. I post my changes in this thread for information
var instance: SplitViewController? = nil
class SplitViewController: UISplitViewController {
class func sharedInstance() -> SplitViewController? {
return instance;
}
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.initialization()
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder);
self.initialization()
}
func initialization() {
instance = self
}
}
and for example, in my appDelegate, I can access this static method like this
SplitViewController.sharedInstance()!.presentsWithGesture = false
The wording in the error heavily implies this will be a language feature in the future.
You may want to resort temporarily to declaring a property variable in the Application Delegate and retrieve it from there. Not ideal, definitely an anti-pattern, but would give you a central place to retrieve the UISplitViewController when needed.
You have to wrap the class variables inside an inner struct variable
class Store{
var name:String
var address:String
var lat:Int
var long:Int
init(name:String, address:String, lat:Int, long:Int){
self.name = name
self.address = address
self.lat = lat
self.long=long
}
private struct FACTORY_INITIALIZED_FLAG { static var initialized: Bool = false
static var myStoreList:[Store]?
static func getMyStoreList()->[Store]{
if !initialized{
println("INITIALIZING")
myStoreList = [
Store(name: "Walmart", address: "abcd", lat: 10, long: 20),
Store(name: "JCPenny", address: "kjfnv", lat: 23, long: 34)
]
initialized = true
}
return myStoreList!
}
}
}
var a = Store.FACTORY_INITIALIZED_FLAG.getMyStoreList()
var b = Store.FACTORY_INITIALIZED_FLAG.getMyStoreList()
// only prints INITIALIZING once
Try this:
class var instance: SplitViewController {
return nil
}
It is called Type Property in Swift.
You define type properties with the static keyword. For computed type properties for class types, you can use the class keyword instead to allow subclasses to override the superclass’s implementation. The example below shows the syntax for stored and computed type properties:
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
Read more at link below,
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID254