I'm learning Swift and I'm wondering how can I create a data structure with multiple values and pass descriptions values from UITableViewController to another viewController? I have tried like this
struct faculty {
var name = String()
var descriptions = (String)[]
}
let faculties = [name: "Faculties", description: ["Study1", "Study2"]]
I have successfully managed to list an array ["Test1", "Test2"] in tableView.
There are a couple of issues
An empty string array is [String]().
description is not equal to descriptions.
An instance must be created with Type(parameter1:parameter2:).
And structs are supposed to be named with starting capital letter.
struct Faculty {
var name = String()
var descriptions = [String]()
}
let faculties = [Faculty(name: "Faculties", descriptions: ["Study1", "Study2"])]
However default values are not needed. This is also valid
struct Faculty {
let name : String
var descriptions : [String]
}
Related
I have a Swift Realm database that I’m attempting to find a specific record which will occupy a number of Labels in a UIViewController - no Tableview. In essence, I want to search the database for a record based on a String variable consisting of a date and time. The string format looks like this “Feb19,21-15:47” but changes with each new record added - hence why I need to use a string variable as a search parameter.
Once the record is found I want to then grab the entire record associated with the search string and parse out each field to fill the five Labels on the VC. I’ve been trying for hours to get this to work but I’m just not getting the result I need.
My questions are:
How do I format the search parameter to find the record using tempPhotoTitle?
Once the search finds the object property (tempPhotoTitle) in the database what code needs to be employed to grab all the associated properties in the same record/row so I can bind each property to its associated Label in the VC.
A couple notes: I did employ an auto updating primary key in each record named ‘id’. When using a Tableview I can access each record by using indexPath.row but since I’m not using a TV this isn’t available. The tempPhotoTitle string value is fed from another VC via a segue. Also only one record in the DB will have the search value. Here is some abbreviated code to provide the gist of my issue. The search doesn’t work (parsing issue) and as a result I can’t test the remaining code. I sure would appreciate some assistance on this. Many thanks and frustrated Roger
import UIKit
import RealmSwift
class Snapshot: Object {
#objc dynamic var id = 0
#objc dynamic var date: String = ""
#objc dynamic var cTime: String = ""
#objc dynamic var airTemp: String = ""
#objc dynamic var humidity: String = ""
#objc dynamic var photoTitle: String = ""
override class func primaryKey() -> String {
return "id"
}
convenience init( ….)
}
class RecordsVC: UIViewController {
var tempPhotoTitle: String = ""
var tempImage = UIImage()
#IBOutlet weak var eDateHolder: UILabel!
#IBOutlet weak var eTimeHolder: UILabel!
#IBOutlet weak var eAirTempHolder: UILabel!
#IBOutlet weak var eHumidityHolder: UILabel!
var editSnapShotItems: Results<Snapshot>?
override func viewDidLoad() {
super.viewDidLoad()
queryRecords()
}}
func queryRecords() {
let realm = try! Realm()
let allRecords = realm.objects(editSnapShotItems.self)
let recordResult = allRecords.filter("photoTitle CONTAINS[cd] %#", tempPhotoTitle)
let recordResults = allRecords.filter(tempPhotoTitle)
for record in recordResults {
eDateHolder.text = record.date
eTimeHolder.text = record.cTime
eAirTempHolder.text = record.airTemp
eHumidityHolder.text = record.humidity
}}}
Here's a simplified version of your object for this answer
class Snapshot: Object {
#objc dynamic var date: String = ""
}
if you create an object
let snap = Snapshot()
snap.date = "Feb19,21-15:47"
and then store it in Realm
let realm = try! Realm()
realm.write {
realm.add(snap)
}
and then you want to find that later
let snapResults = realm.object(Snapshot.self).filter("date == %#", "Feb19,21-15:47")
for snap in snapResults {
print(snap.date)
}
the console output will be
Feb19,21-15:47
But...
There are a number of issues with storing dates in that format. For example, what if you want to sort three dates Jan20,21-15:57, Jan21,21-15:57, Feb19,21-15:57. Logically they are in order but because they are strings, they will sort with Feb19 first (F is before J in the alphabet).
There are a number of solutions: Realm fully supports NSDate so you can just store them as an actual date or if you really want to use strings, store them in a sortable format
202102191547
would be Feb 19th 2021 at 15:47, making sure single digits are padded with a 0. This allows them to be ordered, filtered etc correctly
My solution requires that I hard-code placeholder state vars field_1, field_2 that are then passed to my TextField dynamically via an array.
How can I achieve this more dynamically? I would like to have arbitrary Firestore documents from my database map to matching arbitrary TextFields. I use this to generate a settings screen that varies by user.
Here is my current solution, referenced above:
import SwiftUI
struct ParentView: View {
var data = [
["field": "Account Number", "title": "Account Number"],
["field": "Account Number", "title": "Account Number"]
]
#State var settings: [Dictionary<String,Any>] = []
var fields: [Binding<String>] = []
#State var field_1: String = "1"
#State var field_2: String = "2"
init(){
self.fields.append(self.$field_1)
self.fields.append(self.$field_2)
}
func getSettingsObjects() { for item in data { settings.append(item)}}
var body: some View {
VStack {
ForEach(self.$settings.wrappedValue.indices, id: \.self) { i in
TextField(self.settings[i]["title"] as! String, text: self.fields[i])
}
}
.padding(.all,20)
.navigationBarTitle("Settings")
.onAppear {
_ = self.getSettingsObjects()
}
}
}
To handle firebase fields, I like to set up a global struct. Then, every time instead of typing the string, you can user variable DatabaseField.field1 instead.
struct DatabaseField { //Titles for fields in database
static let field1 = "field_one_title"
static let field2 = "field_two_title"
}
Here, you can access the String by calling...
let field: String = DatabaseField.field1
Separately, you could also create a global enum to handle states, items, etc. with a rawValue of type String. I would use this if the State is going to work like a Bool in your viewController.
enum FieldState: String { //ViewController states can be used in functions
case field1 = "field_one_title"
case field2 = "field_two_title"
}
Here, you can set variables to this new type...
var currentState: FieldState = .field1
You can access the String value of this type with...
let currentField: String = currentState.rawValue
As you have realized, you should try to avoid hard-coding variable Strings and database field names whenever possible. I usually make a separate Swift File and store all of the global Structs or Enums for my project there. This way you can hard code it once and never have to touch it again. If you ever have to change the field title, you only need to change it within the Struct/Enum! Side note: your project may include several Structs and Enums. If this is the case, try not to make both a Struct/Enum for the same field/string or it might get confusing to decipher which is which.
I have a protocol, and some structs that conform to it, basically in the format shown below. I'm facing an issue where if I append different structs to an array of type [Protocol], the values of the structs are changing in a weird way. However, if I change the type of the array to [Struct1] or [Struct2], and only append the appropriate types, there's no problem.
protocol Protocol {
var id: String { get set }
var name: String { get set }
}
struct Struct1: Protocol {
var id: String = "1"
var name: String = "Struct1"
var uniqueProperty1: String = "uniqueProperty1"
}
struct Struct2: Protocol {
var id: String = "2"
var name: String = "Struct2"
var uniqueProperty2: String = "uniqueProperty2"
}
var structs: [Protocol] = []
let struct1 = Struct1()
let struct2 = Struct2()
structs.append(struct1)
structs.append(struct2)
And I should add, the above code works as expected. It's my project that has a protocol and some structs however that are behaving strangely. What could be causing this issue?
I discovered that if you look at the value of an element within an array of type [Protocol] in the Variables View within the Debug Area, it's possible that it won't reflect that element's actual values.
Here's an example:
You can see that itemsList in cards[2] is nil, but when I print out the same value in the Debugger Output of the Console, it's not nil (has a length of 4):
(lldb) po (cards[2] as? RBListCard)?.itemsList?.count
▿ Optional<Int>
- some : 4
I guess the moral of the story is don't trust the values that show up within the Variables View.
I hope the code and comments below illustrate what I'm trying to learn how to do.
Accessing Struct property named iphone, I can, it's valid to :
IconSizes().iphone
Accessing Struct property named iphone, I can't and want to access it using a variable containing a String value "iPhone" :
IconSizes().selectedIconType
In more context :
selectedIconType = "iphone" // already set as String
let sizesNamesArray = IconSizes().selectedIconType // obviously raises error.
The Struct :
struct IconSizes {
var typesList: Array<String>
var iphone: [Dictionary<String, Any>]
init() {
self.typesList = ["iPhone"]
self.iphone = [
["size":16,"name":"icon_small.png"],
["size":32,"name":"icon_small#2x.png"],
["size":32,"name":"icon_medium.png"],
["size":64,"name":"icon_medium#2x.png"],
["size":64,"name":"icon_large.png"],
["size":128,"name":"icon_large#2x.png"],
["size":128,"name":"icon.png"],
["size":256,"name":"icon#2x.png"]
]
}
}
Referencing names of variables at run time as if they were strings is usually easy in interpreted languages and less so as you move to compiled, static languages. Being explicit is likely to work better.
struct IconSizes {
var typesList: Array<String>
var iphone: [Dictionary<String, Any>]
var types = [String : [Dictionary<String, Any>]]()
init() {
self.typesList = ["iPhone"]
self.iphone = [
["size":16,"name":"icon_small.png"],
["size":32,"name":"icon_small#2x.png"],
["size":32,"name":"icon_medium.png"],
["size":64,"name":"icon_medium#2x.png"],
["size":64,"name":"icon_large.png"],
["size":128,"name":"icon_large#2x.png"],
["size":128,"name":"icon.png"],
["size":256,"name":"icon#2x.png"]
]
self.types["iPhone"] = self.iphone
}
}
let selectedIconType = "iPhone"
print(IconSizes().types[selectedIconType])
I'm banging my head against the wall trying to figure this out. I have a very simple app with a class Person, an NSTableView and an NSArrayController "PersonController".
Person:
class Person: NSObject {
var firstName = ""
var lastName = ""
}
PersonController:
Added outlet to ViewController
Attributes Inspector > Object Controller > Class Name = Person
TableView:
Attributes Inspector > Table View > Content Mode = Cell based
1st Table Column bound to Person Controller with Controller Key: arrangedObjects and Model key path: firstName
2nd Table Column bound to Person Controller with Controller Key: arrangedObjects and Model key path: lastName
ViewController:
class ViewController: NSViewController {
#IBOutlet var personController: NSArrayController!
override func viewDidLoad() {
super.viewDidLoad()
let person = Person()
var people = [Person]()
person.firstName = "John"
person.lastName = "Snow"
people.append(person)
person.firstName = "Kate"
person.lastName = "Dawson"
people.append(person)
person.firstName = "Tom"
person.lastName = "Anderson"
people.append(person)
personController.addObjects(people)
}
override var representedObject: AnyObject? {
didSet {}
}
}
Output:
What am I doing wrong here? I've read many SO posts and a ton of tutorials etc and from what I can tell I'm doing it correctly but I can't for the life of me get this to work.
You only have one Person object. When you append it to the array, that doesn't make a copy. The array just contains a reference to that one object. You're appending the same object three times, so the array contains three references to that one object.
Each time you change that one Person's firstName and lastName, you're changing that one object's properties. So, at each index of the array, the table finds that one object with the last values set for its name properties.
You need to create three separate Person objects.
As a separate matter, you need to mark the properties of Person as dynamic if you want them to be Key-Value-Observing-compliant and thus Bindings-compatible.