Swift - auto add a class instance to an array when initialising - swift

My code is setup in the following way:
class MyClass {
let text: String
init(text: String) {
self.text = text
}
}
var items = [MyClass]()
let item = MyClass(text: "sometext")
The idea is to get item into the items array, such as with:
items.append(item)
The main problem with this is that I initialise these items in a swift file, and I would get the error
Expressions are not allowed at the top level
Also, I'd prefer not to use this somewhere such as viewDidLoad() because there may be too many MyClass items.
I've tried this
class MyClass {
let text: String
static var items = [MyClass]()
init(text: String) {
self.text = text
MyClass.items.append(self)
}
}
as suggested here
However, after intialising several MyClass objects, items is still empty.
Is there any way I can do this?
EDIT: thank you for the suggestions so far. These were typos (using item rather than self, and items, rather than MyClass.items. This is how I've used it in my code. It compiles & still doesn't work, sadly.

Your code does not compile. This is the error-free code for the class.
class MyClass {
let text: String
static var items = [MyClass]()
init(text: String) {
self.text = text
MyClass.items.append(self)
}
}
The two things that were wrong in your code.
You should be appending self to items, because self is the instance of Item that you create which is available inside init.
static methods should be accessed with their class name.
Example:
let blah = MyClass(text: "blah")
let bleh = MyClass(text: "bleh")
print(MyClass.items.count) // Prints 2

Related

How to stop variable from going back to default value when read in different file

I have a DiscoveredSerialNumbers class that I want to access from various swift files:
class DiscoveredSerialNumbers {
var snConnect: String = ""
}
In my ViewController I change the value of snConnect based on the selection from a Picker View.
class ViewController: UIViewController, UIPickerViewDataSource,UIPickerViewDelegate {
#IBOutlet weak var SerialNumbers: UIPickerView!
var serialNums: [String] = [String]()
...
override func viewDidLoad() {
...
SerialNumbers.dataSource = self
SerialNumbers.delegate = self
}
...
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let global = DiscoveredSerialNumbers()
global.snConnect = serialNums[row]
print(serialNums[row])
print(global.snConnect)
}
}
When I print out the new value of snConnect set in the following line:
global.snConnect = serialNums[row]
Immediately afterward I get the new updated value of snConnect.
However, when I try to access the updated value of snConnect in a different swift file that controls a different ViewController in the following code:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
let global = DiscoveredSerialNumbers()
var sn = global.snConnect
...
}
The value of snConnect reverts back to the original value which is "".
How do I stop the value from reverting back to the initial value? I think it has something to do with me initializing the class DiscoveredSerialNumbers but I do not know how to access the value of snConnect in a different swift file otherwise.
Edit: Thanks to Don's comments, I am trying to have the snConnect value persist between instances of the application launching. I want to set the value of snConnect in the main app and access it when I launch an extension to the main app, in this case a custom keyboard extension.
Update: Question was a bit misleading you actually need to save the variable. I'm not sure if UserDefaults for app and keyboard extension are the same, you can try this.
class DiscoveredSerialNumbers {
static var main = DiscoveredSerialNumbers()
var snConnect: String {
get {
// Read from UserDefaults
return UserDefaults.standard.string(forKey: "snConnect") ?? ""
}
set {
// Save to UserDefaults
UserDefaults.standard.set(newValue, forKey: "snConnect")
}
}
init() {
print("New instance of DiscoveredSerialNumbers initialized.")
}
}
You can do this with a number of different ways,
however easiest one is creating a singleton of DiscoveredSerialNumbers() object, so your object and values can be used globally through it.
(although this method should be used with caution, it can cause a number of problems)
class DiscoveredSerialNumbers {
static var main = DiscoveredSerialNumbers()
var snConnect: String = ""
init() {
print("New instance of DiscoveredSerialNumbers initialized.")
}
}
now whenever you call DiscoveredSerialNumbers.main.snConnect old value will be kept and can be used/changed from anywhere.
Edit: Here's a sample Playground code for you to test out how singletons work
class Singleton
{
var someVariable = ""
static var main = Singleton()
}
class ClassA
{
init() {
Singleton.main.someVariable = "Hey I was changed in Class A"
}
}
class ClassB
{
init() {
print(Singleton.main.someVariable)
Singleton.main.someVariable = "And now I'm changed in class B"
}
}
let _ = ClassA()
let _ = ClassB()
print(Singleton.main.someVariable)
For an app extension to access data stored through it's container app, both the application and extension need to be part of the same app group. App groups are set in Signing & Capabilities section of Xcode for your project.
Once your app and extension are part of the same app group, you can use the following code to set the value of a global variable:
let defaults = UserDefaults(suiteName:"group.dataShare")
defaults?.set(serialNums[row], forKey: "snConnect")
Where group.dataShare is the name of your App group.
To retrieve the value, you can use the following code in your extension:
let defaults = UserDefaults(suiteName:"group.dataShareImada")
var sn = defaults?.string(forKey: "snConnect")

Why would I return an item from a function if I append to a global array within the function?

I'm writing an application from a tutorial that is a to-do list but I'm a little confused on why its setup this way and could use other perspectives.
Why would I return an item from a function if I append to a global array within the function? Why wouldn't we just query the array for this item. I'm not catching onto why this item is being returned or how its being used.
import Foundation
import UIKit
class ItemStore {
var allItems = [Item]()
func createItem() -> Item {
//use a constant to initialize a new item using the Item class's syntax
let newItem = Item.init(random: true)
//now append it to this class's allItems array
allItems.append(newItem)
//now according to this function we need to return an Item object. Now return newItem....but why are we returning it if we just appended it to the allItems array?? Can't we just query the array for this item? I wonder how this returned item is going to be used now
return newItem
}
//now I'm going to add a designated initializer to this class which will add five random items to the allItems array....arrgghh, whhyyy??
init() {
for _ in 0..<5 {
createItem()
}
}
}
Your createItem function should not do any appending. It should do just what is says - create and return a new item. Let the caller determine what to do with the new item. I would refactor your code as:
class ItemStore {
var allItems = [Item]()
func createItem() -> Item {
let newItem = Item(random: true)
return newItem
}
init() {
for _ in 0..<5 {
let item = createItem()
allItems.append(item)
}
}
}
Or you can refactor as:
class ItemStore {
var allItems = [Item]()
func loadItems() {
for _ in 0..<5 {
let item = Item(random: true)
allItems.append(item)
}
}
init() {
loadItems()
}
}
It's best to avoid non-obvious side effects. It's not obvious that a method named createItem would also add that newly created item to some list. Keep methods focused. Put functionality where it belongs.
One reason is to be able to setup other properties in the class.
Let's assume the class has an index property und you want to assign consecutive indices.
init() {
for i in 0..<5 {
let newItem = createItem()
newItem.index = i
}
}
I don't think this is a wrong approach the developer may want to use the added item directly like this
let store = ItemStore()
let item = store.createItem()
this may confuses with how init method is written put he may want the function to do more than one thing

trouble acsessing values from class

i am learning swift atm and the use of classes kept me busy the last two weeks.
thank you for any help
for a project in xcode i created a swift file containing a class that initializes empty strings/arrays of strings.
then a function in that class is retrieving data from google firebase to fill those strings.
than in a viewcontroller class is want to retrieve those two strings. how do it do it right?
so far i tried many different ideas but i either get empty strings (as initialized in the beginning of the class) or i get errors
ideally everytime the viewdidload() (in this case) of the viewcontroller class is called i want it to create an instance of the class with uptodate data.
class RetrieveDatabase {
//static let sharedInstance = RetrieveDatabase()
var allMessages = [[String]]()
var messages = [String]()
var categorieNames = [String]()
func loadGoogleValue() -> (allMessages: [[String]], categorieNames: [String]) {
//this function works, the arrays contain type [string] and string
return (self.allMessages, self.categorieNames)
}
/*
-> i tried initializers in so many variations...
init(allMessages: [[String]], categorieNames: [String]) {
self.allMessages = loadGoogleValue().allMessages
self.categorieNames = loadGoogleValue().categorieNames
messages = []
}
*/
}
here the code from the viewcontroller class:
class SettingsView: UIViewController {
let retrieveDatabase = RetrieveDatabase.//tried everything here, only errors
override func viewDidLoad() {
super.viewDidLoad()
label1.text = retrieveDatabase.categorieNames[0]
}
}
I've simplified your code to something that you can easily try out in a playground. If you start with a working version, perhaps you can see the difference between it and the one that's giving you trouble.
class RetrieveDatabase {
var allMessages = [[String]]()
var messages = [String]()
var categorieNames = [String]()
init() {
messages.append("Hi")
messages.append("Hello")
allMessages.append(messages)
categorieNames.append("SALUTATIONS")
}
func loadGoogleValue() -> (allMessages: [[String]], categorieNames: [String]) {
return (self.allMessages, self.categorieNames)
}
}
class SettingsView {
let retrieveDatabase = RetrieveDatabase()
func caller() {
print(retrieveDatabase.loadGoogleValue().allMessages)
print(retrieveDatabase.loadGoogleValue().categorieNames)
}
}
let test = SettingsView().caller()
Since there's very little difference between what I've done and the code you added in your comment above, I suspect it's something you've left out concerning how strings are added to the arrays.

Store Swift Type in variable

I have a data model for a UITableViewCell that looks like this:
class SettingsContentRow {
var title: String
var cellType: Type // How do i do this?
var action:((sender: UITableViewCell.Type) -> ())?
var identifier: String {
get { return NSStringFromClass(cellType) }
}
init(title: String, cellType: Type) {
self.title = title
self.cellType= cellType
}
}
The idea is to put these in an array to facilitate building a settings view using a UITableViewController, and when requesting a cell i can just query the model for both the identifier and the cell Type. But i cannot figure out what keyword to use instead of Type. I have tried Type, AnyClass, UITableViewCell.Type and they all give rise to type assignment errors when i try to instantiate the model class.
The syntax you want is UITableViewCell.Type. This is the type of something that is a subclass of UITableViewCell. You can accept the type of any class using AnyClass, but you should usually avoid that. Most of the time, if you think you want AnyClass, you really want a generic.
When you try to pass your type to this init, it'll be something like:
SettingsContentRow("title", cellType: MyCell.self)
Referring to types directly is a little uncommon, so Swift requires that you be explicit by adding .self to it.
You may in fact want a generic here anyway. I'd probably write it this way:
final class SettingsContentRow<Cell: UITableViewCell> {
typealias Action = (Cell) -> ()
let title: String
let action: Action?
var identifier: String {
get { return NSStringFromClass(Cell.self) }
}
init(title: String, action: Action?) {
self.title = title
self.action = action
}
}
class MyCell: UITableViewCell {}
let row = SettingsContentRow(title: "Title", action: { (sender: MyCell) in } )

Realm Cannot invoke 'add' with an argument list of type '(Person)'

This error is so frustrating. I'm just trying to add an object to my Realm database and I have gotten to the point of just copying and pasting example code and it will not work. So I have an add person method that does this:
func addPerson(person person:Person){
realm.beginWrite()
realm.add(person)
realm.commitWrite()
}
And the realm variable is stored like this in the class header:
private var realm:Realm
Being initialized in the init() method as such:
realm = Realm()
My actual person class looks like this:
import UIKit
class Person {
var name:String?
var relation: Relations?
var title: String?
var importance:Double?
var events:Array<Event>?
var image:UIImage?
init(name:String,relation:Relations?,events:Array<Event>?,image:UIImage?){
self.relation = relation
if relation != nil{
self.title = relation!.title
self.importance = relation!.importance
}
else{
self.title = nil
self.importance = nil
}
self.events = events
self.image = image
self.name = name
}
init() {
}
}
The error is so frustrating because it seems like the Person class does not conform to the Object superclass, but in reality, that's the only option
This is just a summary of what I said in the comments:
Use Object as a subclass as it is necessary for a class to be a model in Realm.